first commit

This commit is contained in:
cieanyau committed 2026-11-06 00:04:38 +08:00
commit db5762abd4
687 files changed
+171307

No files matched your search

+778
View File
@@ -0,0 +1,778 @@
<script>
import "~/assets/styles/TicketAttachments.DtbJGLkE.css";
import "~/assets/styles/RichTextEditor.NkmgPT5m.css";
import "~/assets/styles/_id_.C-0--Lx-.css";
definePageMeta({
key: (e) => e.fullPath,
});
import { toDisplayString } from "vue";
import { renderList } from "vue";
import { onMounted } from "vue";
import { normalizeClass } from "vue";
import { createBlock } from "vue";
import { ref } from "vue";
import { withCtx } from "vue";
import { createCommentVNode } from "vue";
import { computed } from "vue";
import { isRef } from "vue";
import { createElementBlock } from "vue";
import { Fragment } from "vue";
import { createElementVNode } from "vue";
import { createTextVNode } from "vue";
import { unref } from "vue";
import { defineComponent } from "vue";
import { createVNode } from "vue";
import { openBlock } from "vue";
import { normalizeStyle } from "vue";
import { useRoute } from "#app";
import { NuxtLink } from "#components";
import { useApi } from "~/lib/auth.js";
import button from "primevue/button";
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
import message from "primevue/message";
import { r as dict_r } from "~/lib/dict.js";
import skeleton from "primevue/skeleton";
import { n as sanitize_n } from "~/lib/sanitize.js";
import RichTextEditor from "~/components/RichTextEditor.vue";
import { n as ticketAttachments_n } from "~/lib/ticketAttachments.js";
import TicketAttachments from "~/components/TicketAttachments.vue";
import TicketNotice from "~/components/TicketNotice.vue";
import { t as composables_t } from "~/lib/composables-2.js";
var le = {
class: `page`,
},
ue = {
class: `page-head`,
},
de = {
key: 0,
class: `panel`,
},
fe = {
class: `panel head-panel`,
},
pe = {
class: `ticket-head`,
},
me = {
class: `ticket-title`,
},
he = {
class: `ticket-num`,
},
ge = {
class: `ticket-meta`,
},
w = {
class: `panel thread-panel`,
},
T = {
class: `thread`,
},
E = {
class: `message-head`,
},
D = {
class: `sender`,
},
O = {
key: 0,
class: `badge`,
},
k = {
class: `time`,
},
A = [`innerHTML`],
j = {
key: 0,
class: `message-quote`,
},
M = [`innerHTML`],
N = {
class: `reply-box`,
},
P = {
key: 0,
class: `reply-quote`,
role: `status`,
},
F = [`innerHTML`],
I = {
class: `reply-attachments`,
},
L = {
class: `reply-upload`,
for: `reply-files`,
},
_e = [`disabled`],
R = {
key: 0,
class: `muted`,
},
ve = {
key: 1,
class: `reply-file-list`,
},
ye = {
class: `reply-actions`,
},
be = {
class: `buttons`,
},
z = _plugin_vue_export_helper_t(
defineComponent({
__name: `[id]`,
setup(m) {
let y = useRoute(),
z = useApi(),
B = String(y.params.id ?? y.query.id ?? ``);
composables_t({
key: (e) => e.fullPath,
});
let V = ref(null),
H = ref(!0),
U = ref(``),
W = ref(``),
G = ref(!1),
K = ref(!1),
q = ref([]),
J = ref(!1),
Y = ref(0);
async function X() {
try {
let e = await z.get(`/ticket/${B}`);
if (!e.ticket || !e.ticket.id) {
U.value = `工单不存在或已删除`;
return;
}
V.value = e.ticket;
} catch (e) {
U.value = e instanceof Error ? e.message : `加载失败`;
} finally {
H.value = !1;
}
}
let Z = computed(() => {
let e = V.value;
return e
? [
{
id: 0,
content: e.content,
attachment: e.attachment || [],
create_time: e.create_time,
type: `Client`,
client_name: e.username,
admin_name: ``,
client_id: e.client_id,
quote_reply_id: 0,
quote_info: null,
},
...(e.replies || []).filter((e) => e.id !== 0),
]
.sort((e, t) => e.create_time - t.create_time)
.map((e) => ({
...e,
files: ticketAttachments_n(e.attachment),
}))
: [];
});
function Q(e) {
return e.type === `Admin`
? e.admin_name || `客服`
: e.client_name || `我`;
}
let $ = computed(() => Z.value.find((e) => e.id === Y.value));
async function xe(e) {
if (J.value || G.value || V.value?.status === `已关闭`) return;
let t = e.target,
n = Array.from(t.files ?? []);
if (n.length) {
((J.value = !0), (U.value = ``));
try {
for (let e of n) {
let t = await z.upload(e);
if (!t?.save_name)
throw Error(`附件「${e.name}」上传失败,请重新选择文件`);
q.value.push({
save_name: t.save_name,
name: e.name,
});
}
} catch (e) {
U.value = e instanceof Error ? e.message : `附件上传失败`;
} finally {
((J.value = !1), (t.value = ``));
}
}
}
function Se(e) {
G.value || q.value.splice(e, 1);
}
function Ce(e) {
e.id > 0 && V.value?.status !== `已关闭` && (Y.value = e.id);
}
function we(e) {
return Number(e.quote_info?.is_deleted) === 1
? `该回复已删除`
: e.quote_info?.content ||
Z.value.find((t) => t.id === e.quote_reply_id)?.content ||
`原回复 #${e.quote_reply_id}(内容暂不可用)`;
}
function Te(e) {
let t = e.quote_info,
n = Z.value.find((t) => t.id === e.quote_reply_id);
return (
t?.sender_name ||
t?.admin_name ||
t?.client_name ||
(n ? Q(n) : `回复 #${e.quote_reply_id}`)
);
}
function Ee(e) {
return Number(e.quote_info?.is_deleted) === 1
? []
: ticketAttachments_n(
e.quote_info?.attachment ||
Z.value.find((t) => t.id === e.quote_reply_id)?.attachment ||
[],
);
}
async function De() {
if (!(
G.value ||
!W.value.trim() ||
J.value ||
V.value?.status === `已关闭`
)) {
G.value = !0;
try {
(await z.post(`/ticket/${B}/reply`, {
content: W.value.trim(),
attachment: q.value.map((e) => e.save_name),
quote_reply_id: Y.value,
}),
(W.value = ``),
(q.value = []),
(Y.value = 0),
await X());
} catch (e) {
U.value = e instanceof Error ? e.message : `回复失败`;
} finally {
G.value = !1;
}
}
}
async function Oe() {
K.value = !0;
try {
(await z.put(`/ticket/${B}/urge`), await X());
} catch (e) {
U.value = e instanceof Error ? e.message : `催单失败`;
} finally {
K.value = !1;
}
}
async function ke() {
K.value = !0;
try {
(await z.put(`/ticket/${B}/close`), await X());
} catch (e) {
U.value = e instanceof Error ? e.message : `关闭失败`;
} finally {
K.value = !1;
}
}
return (
onMounted(X),
(n, a) => {
let c = TicketNotice,
m = NuxtLink,
_ = skeleton,
v = message,
y = button,
S = TicketAttachments,
C = RichTextEditor;
return (
openBlock(),
createElementBlock(`div`, le, [
createVNode(c),
createElementVNode(`div`, ue, [
createVNode(
m,
{
to: `/tickets`,
class: `back`,
},
{
default: withCtx(() => [
...(a[2] ||= [
createElementVNode(
`i`,
{
class: `pi pi-arrow-left`,
},
null,
-1,
),
createTextVNode(` 返回工单列表 `, -1),
]),
]),
_: 1,
},
),
]),
unref(H)
? (openBlock(),
createElementBlock(`div`, de, [
createVNode(_, {
height: `220px`,
}),
]))
: unref(U) && !unref(V)
? (openBlock(),
createBlock(
v,
{
key: 1,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(U)), 1),
]),
_: 1,
},
))
: unref(V)
? (openBlock(),
createElementBlock(
Fragment,
{
key: 2,
},
[
createElementVNode(`div`, fe, [
createElementVNode(`div`, pe, [
createElementVNode(`div`, me, [
createElementVNode(
`span`,
he,
`#` + toDisplayString(unref(V).ticket_num),
1,
),
createElementVNode(
`h2`,
null,
toDisplayString(unref(V).title),
1,
),
]),
unref(V).status === `已关闭`
? createCommentVNode(``, !0)
: (openBlock(),
createBlock(
y,
{
key: 0,
label: `关闭工单`,
severity: `danger`,
outlined: ``,
size: `small`,
loading: unref(K),
onClick: ke,
},
null,
8,
[`loading`],
)),
createElementVNode(
`span`,
{
class: `status-tag`,
style: normalizeStyle({
color: `var(--p-text-color)`,
borderColor: unref(V).color,
}),
},
toDisplayString(unref(V).status),
5,
),
]),
createElementVNode(`div`, ge, [
createElementVNode(
`span`,
null,
`提交时间:` +
toDisplayString(
(`formatTime` in n
? n.formatTime
: unref(dict_r))(unref(V).create_time),
),
1,
),
]),
]),
createElementVNode(`div`, w, [
createElementVNode(`div`, T, [
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(Z),
(t, a) => (
openBlock(),
createElementBlock(
`div`,
{
key: `${t.id}-${a}`,
class: normalizeClass([
`message`,
{
admin: t.type === `Admin`,
},
]),
},
[
createElementVNode(`div`, E, [
createElementVNode(
`span`,
{
class: normalizeClass([
`sender-avatar`,
{
staff: t.type === `Admin`,
},
]),
},
toDisplayString(Q(t).slice(0, 1)),
3,
),
createElementVNode(
`span`,
D,
toDisplayString(Q(t)),
1,
),
t.type === `Admin`
? (openBlock(),
createElementBlock(
`span`,
O,
`客服`,
))
: createCommentVNode(``, !0),
createElementVNode(
`span`,
k,
toDisplayString(
(`formatTime` in n
? n.formatTime
: unref(dict_r))(
t.create_time,
),
),
1,
),
]),
createElementVNode(
`div`,
{
class: `message-body`,
innerHTML: (`sanitizeHtml` in n
? n.sanitizeHtml
: unref(sanitize_n))(t.content),
},
null,
8,
A,
),
t.quote_reply_id
? (openBlock(),
createElementBlock(`div`, j, [
createElementVNode(
`span`,
null,
`引用 ` +
toDisplayString(Te(t)) +
`:`,
1,
),
createElementVNode(
`div`,
{
innerHTML:
(`sanitizeHtml` in n
? n.sanitizeHtml
: unref(sanitize_n))(
we(t),
),
},
null,
8,
M,
),
createVNode(
S,
{
files: Ee(t),
},
null,
8,
[`files`],
),
]))
: createCommentVNode(``, !0),
createVNode(
S,
{
files: t.files,
},
null,
8,
[`files`],
),
t.id > 0 &&
unref(V).status !== `已关闭`
? (openBlock(),
createBlock(
y,
{
key: 1,
label: `引用`,
icon: `pi pi-reply`,
text: ``,
size: `small`,
class: `quote-button`,
onClick: (e) => Ce(t),
},
null,
8,
[`onClick`],
))
: createCommentVNode(``, !0),
],
2,
)
),
),
128,
)),
]),
createElementVNode(`div`, N, [
unref($)
? (openBlock(),
createElementBlock(`div`, P, [
createElementVNode(`div`, null, [
createElementVNode(
`span`,
null,
`引用 ` +
toDisplayString(Q(unref($))) +
`:`,
1,
),
createElementVNode(
`div`,
{
innerHTML: (`sanitizeHtml` in n
? n.sanitizeHtml
: unref(sanitize_n))(
unref($).content,
),
},
null,
8,
F,
),
]),
createVNode(y, {
icon: `pi pi-times`,
text: ``,
rounded: ``,
size: `small`,
"aria-label": `取消引用`,
onClick: (a[0] ||= (e) =>
(Y.value = 0)),
}),
]))
: createCommentVNode(``, !0),
createVNode(
C,
{
modelValue: unref(W),
"onUpdate:modelValue": (a[1] ||= (e) =>
isRef(W) ? (W.value = e) : null),
label: `回复内容`,
"min-height": 140,
placeholder: `输入回复内容…`,
disabled: unref(V).status === `已关闭`,
},
null,
8,
[`modelValue`, `disabled`],
),
createElementVNode(`div`, I, [
createElementVNode(`label`, L, [
(a[3] ||= createElementVNode(
`i`,
{
class: `pi pi-paperclip`,
},
null,
-1,
)),
(a[4] ||= createTextVNode(`添加附件`, -1)),
createElementVNode(
`input`,
{
id: `reply-files`,
type: `file`,
multiple: ``,
disabled:
unref(G) ||
unref(J) ||
unref(V).status === `已关闭`,
onChange: xe,
},
null,
40,
_e,
),
]),
unref(J)
? (openBlock(),
createElementBlock(
`span`,
R,
`正在上传…`,
))
: createCommentVNode(``, !0),
unref(q).length
? (openBlock(),
createElementBlock(`ul`, ve, [
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(q),
(t, n) => (
openBlock(),
createElementBlock(
`li`,
{
key: t.save_name,
},
[
createElementVNode(
`span`,
null,
toDisplayString(t.name),
1,
),
createVNode(
y,
{
icon: `pi pi-times`,
text: ``,
rounded: ``,
size: `small`,
"aria-label": `移除附件 ${t.name}`,
disabled: unref(G),
onClick: (e) => Se(n),
},
null,
8,
[
`aria-label`,
`disabled`,
`onClick`,
],
),
],
)
),
),
128,
)),
]))
: createCommentVNode(``, !0),
]),
createElementVNode(`div`, ye, [
unref(U)
? (openBlock(),
createBlock(
v,
{
key: 0,
severity: `error`,
closable: !1,
class: `inline-error`,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(unref(U)),
1,
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
createElementVNode(`div`, be, [
unref(V).status === `已关闭`
? createCommentVNode(``, !0)
: (openBlock(),
createBlock(
y,
{
key: 0,
label: `催单`,
severity: `secondary`,
outlined: ``,
size: `small`,
loading: unref(K),
onClick: Oe,
},
null,
8,
[`loading`],
)),
createVNode(
y,
{
label: `提交回复`,
loading: unref(G),
disabled:
unref(V).status === `已关闭` ||
!unref(W).trim() ||
unref(J),
onClick: De,
},
null,
8,
[`loading`, `disabled`],
),
]),
]),
]),
]),
],
64,
))
: createCommentVNode(``, !0),
])
);
}
);
},
}),
[[`__scopeId`, `data-v-3743a20f`]],
);
export {};
export default z;
</script>
+675
View File
@@ -0,0 +1,675 @@
<script>
import "~/assets/styles/RichTextEditor.NkmgPT5m.css";
import "~/assets/styles/create.gX6gpD00.css";
import { toDisplayString } from "vue";
import { renderList } from "vue";
import { onMounted } from "vue";
import { createBlock } from "vue";
import { ref } from "vue";
import { withCtx } from "vue";
import { createCommentVNode } from "vue";
import { computed } from "vue";
import { reactive } from "vue";
import { createElementBlock } from "vue";
import { Fragment } from "vue";
import { createElementVNode } from "vue";
import { createTextVNode } from "vue";
import { unref } from "vue";
import { defineComponent } from "vue";
import { createVNode } from "vue";
import { openBlock } from "vue";
import { withModifiers } from "vue";
import { useRouter } from "#app";
import { NuxtLink } from "#components";
import { useApi } from "~/lib/auth.js";
import button from "primevue/button";
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
import message from "primevue/message";
import radiobutton from "primevue/radiobutton";
import inputtext from "primevue/inputtext";
import { n as product_name_n } from "~/lib/product-name.js";
import select from "primevue/select";
import RichTextEditor from "~/components/RichTextEditor.vue";
import TicketNotice from "~/components/TicketNotice.vue";
import chip from "primevue/chip";
import multiselect from "primevue/multiselect";
var le = {
class: `ticket-create`,
},
ue = {
class: `ticket-section`,
},
de = {
class: `section-fields`,
},
fe = {
key: 0,
role: `status`,
},
C = {
class: `type-options`,
},
w = {
class: `field`,
},
T = {
key: 2,
class: `field`,
},
E = {
class: `field`,
},
D = {
class: `host-opt`,
},
O = {
class: `host-opt-meta`,
},
k = {
class: `ticket-section`,
},
A = {
class: `section-fields`,
},
j = {
class: `field`,
},
M = {
class: `field`,
},
N = {
class: `upload-drop`,
for: `files`,
},
P = [`disabled`],
F = {
key: 0,
class: `files`,
},
I = {
class: `fname`,
},
L = [`aria-label`, `disabled`, `onClick`],
R = {
class: `actions`,
},
z = _plugin_vue_export_helper_t(
defineComponent({
__name: `create`,
setup(m) {
let b = useApi(),
z = useRouter(),
B = ref([]),
V = ref([]),
H = ref([]),
U = reactive({
title: ``,
ticket_type_id: null,
admin_role_id: null,
host_ids: [],
content: ``,
}),
W = ref(!1),
G = ref(``),
K = ref(!1),
q = ref(!1);
function J(e) {
return e.dedicate_ip || e.assign_ip || ``;
}
function pe(e) {
return [`#${e.id}`, e.name, J(e)].filter(Boolean).join(` / `);
}
let Y = computed(() => new Map(H.value.map((e) => [e.id, e])));
function me(e) {
let t = Y.value.get(e);
return t
? `${product_name_n(t.product_name) || t.name} (#${t.id})`
: `#${e}`;
}
let X = ref([]),
Z = ref(!1);
async function he(e) {
if (Z.value || W.value) return;
let t = e.target,
n = Array.from(t.files ?? []);
if (n.length) {
((Z.value = !0), (G.value = ``));
try {
for (let e of n) {
let t = await b.upload(e);
if (!t?.save_name)
throw Error(`附件「${e.name}」上传失败,请重新选择文件`);
X.value.push({
save_name: t.save_name,
name: e.name,
});
}
} catch (e) {
G.value = e instanceof Error ? e.message : `上传失败`;
} finally {
((Z.value = !1), (t.value = ``));
}
}
}
function ge(e) {
W.value || X.value.splice(e, 1);
}
async function Q() {
if (!K.value) {
((K.value = !0), (q.value = !1), (G.value = ``));
try {
let [e, t] = await Promise.all([
b.get(`/ticket/type`),
b.get(`/ticket/department`),
]);
((B.value = e.list ?? []), (V.value = t.list ?? []));
try {
let e = [],
t = 1,
n = 0;
do {
let r = await b.get(`/host`, {
page: t,
limit: 200,
});
(e.push(...(r.list ?? [])),
(n = Number(r.count ?? e.length)),
(t += 1));
} while (e.length < n && t <= 100);
H.value = e;
} catch {
H.value = [];
}
((U.ticket_type_id = B.value[0]?.id ?? null),
(U.admin_role_id = V.value[0]?.id ?? null),
(q.value = !0));
} catch (e) {
G.value =
e instanceof Error ? e.message : `加载工单选项失败,请重试`;
} finally {
K.value = !1;
}
}
}
async function $() {
if (!(W.value || Z.value || K.value || !q.value)) {
if (((G.value = ``), !U.title.trim())) {
G.value = `请输入工单标题`;
return;
}
if (!U.content.trim()) {
G.value = `请输入工单内容`;
return;
}
W.value = !0;
try {
let e = await b.post(`/ticket`, {
title: U.title.trim(),
content: U.content.trim(),
ticket_type_id: U.ticket_type_id,
admin_role_id: U.admin_role_id,
host_ids: U.host_ids,
attachment: X.value.map((e) => e.save_name),
});
z.push(`/tickets/${e.id}`);
} catch (e) {
G.value = e instanceof Error ? e.message : `提交失败`;
} finally {
W.value = !1;
}
}
}
return (
onMounted(Q),
(n, i) => {
let s = TicketNotice,
c = button,
m = radiobutton,
_ = inputtext,
y = select,
b = chip,
z = multiselect,
J = RichTextEditor,
Y = message,
_e = NuxtLink;
return (
openBlock(),
createElementBlock(`div`, le, [
createVNode(s),
(i[18] ||= createElementVNode(`h2`, null, `创建工单`, -1)),
createElementVNode(
`form`,
{
onSubmit: withModifiers($, [`prevent`]),
},
[
createElementVNode(`section`, ue, [
(i[9] ||= createElementVNode(
`aside`,
null,
[
createElementVNode(`h3`, null, `基本信息`),
createElementVNode(
`p`,
null,
`请提供一些基本信息,这有助于我们快速定位您的问题并将其转交给正确的团队处理。`,
),
],
-1,
)),
createElementVNode(`div`, de, [
unref(K)
? (openBlock(),
createElementBlock(`div`, fe, `正在加载工单选项…`))
: unref(q)
? createCommentVNode(``, !0)
: (openBlock(),
createBlock(c, {
key: 1,
label: `重新加载工单选项`,
severity: `secondary`,
outlined: ``,
onClick: Q,
})),
createElementVNode(`fieldset`, C, [
(i[5] ||= createElementVNode(
`legend`,
null,
`工单类型`,
-1,
)),
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(B),
(t) => (
openBlock(),
createElementBlock(
`label`,
{
key: t.id,
},
[
createVNode(
m,
{
modelValue: unref(U).ticket_type_id,
"onUpdate:modelValue": (i[0] ||= (e) =>
(unref(U).ticket_type_id = e)),
"input-id": `ticket-type-${t.id}`,
value: t.id,
disabled: !unref(q),
},
null,
8,
[
`modelValue`,
`input-id`,
`value`,
`disabled`,
],
),
createElementVNode(
`span`,
null,
toDisplayString(t.name),
1,
),
],
)
),
),
128,
)),
]),
createElementVNode(`div`, w, [
(i[6] ||= createElementVNode(
`label`,
{
for: `title`,
},
`工单标题`,
-1,
)),
createVNode(
_,
{
id: `title`,
modelValue: unref(U).title,
"onUpdate:modelValue": (i[1] ||= (e) =>
(unref(U).title = e)),
placeholder: `输入工单标题`,
maxlength: `100`,
fluid: ``,
},
null,
8,
[`modelValue`],
),
]),
unref(V).length > 1
? (openBlock(),
createElementBlock(`div`, T, [
(i[7] ||= createElementVNode(
`label`,
{
for: `department`,
},
`服务部门`,
-1,
)),
createVNode(
y,
{
"input-id": `department`,
modelValue: unref(U).admin_role_id,
"onUpdate:modelValue": (i[2] ||= (e) =>
(unref(U).admin_role_id = e)),
options: unref(V),
optionLabel: `name`,
optionValue: `id`,
disabled: !unref(q),
fluid: ``,
},
null,
8,
[`modelValue`, `options`, `disabled`],
),
]))
: createCommentVNode(``, !0),
createElementVNode(`div`, E, [
(i[8] ||= createElementVNode(
`label`,
{
for: `ticket-hosts`,
},
`关联产品`,
-1,
)),
createVNode(
z,
{
"input-id": `ticket-hosts`,
modelValue: unref(U).host_ids,
"onUpdate:modelValue": (i[3] ||= (e) =>
(unref(U).host_ids = e)),
options: unref(H),
optionLabel: `name`,
optionValue: `id`,
filter: ``,
filterFields: [
`product_name`,
`name`,
`dedicate_ip`,
`assign_ip`,
`id`,
],
filterPlaceholder: `输入关键字`,
placeholder: `选择关联产品`,
display: `chip`,
fluid: ``,
},
{
option: withCtx(({ option: t }) => [
createElementVNode(`div`, D, [
createElementVNode(
`span`,
null,
toDisplayString(
unref(product_name_n)(t.product_name) ||
t.name,
),
1,
),
createElementVNode(
`span`,
O,
toDisplayString(pe(t)),
1,
),
]),
]),
chip: withCtx(
({ value: e, removeCallback: t }) => [
createVNode(
b,
{
label: me(e),
removable: ``,
onRemove: (n) => t(n, e),
},
null,
8,
[`label`, `onRemove`],
),
],
),
_: 1,
},
8,
[`modelValue`, `options`],
),
]),
]),
]),
createElementVNode(`section`, k, [
(i[17] ||= createElementVNode(
`aside`,
null,
[
createElementVNode(`h3`, null, `详细信息`),
createElementVNode(`p`, null, [
createTextVNode(
`请尽可能详细地描述您的问题,包括您已经尝试过的解决方案。`,
),
createElementVNode(`br`),
createTextVNode(
`如果您有相关的文件或图片,请上传它们,这将有助于我们更快地诊断问题并为您提供解决方案。`,
),
]),
],
-1,
)),
createElementVNode(`div`, A, [
createElementVNode(`div`, j, [
(i[10] ||= createElementVNode(
`label`,
{
for: `content`,
},
`工单内容`,
-1,
)),
createVNode(
J,
{
id: `content`,
modelValue: unref(U).content,
"onUpdate:modelValue": (i[4] ||= (e) =>
(unref(U).content = e)),
label: `工单内容`,
placeholder: `请描述问题、复现步骤和报错信息`,
disabled: unref(W),
},
null,
8,
[`modelValue`, `disabled`],
),
]),
createElementVNode(`div`, M, [
(i[15] ||= createElementVNode(
`label`,
{
for: `files`,
},
`附件`,
-1,
)),
createElementVNode(`label`, N, [
(i[11] ||= createElementVNode(
`i`,
{
class: `pi pi-upload`,
},
null,
-1,
)),
(i[12] ||= createElementVNode(
`span`,
null,
[
createElementVNode(`b`, null, `点击此处`),
createTextVNode(`上传相关文件`),
],
-1,
)),
createElementVNode(
`small`,
null,
toDisplayString(
unref(Z)
? `正在上传,请稍候…`
: `支持多选;上传完成后再提交工单`,
),
1,
),
createElementVNode(
`input`,
{
id: `files`,
type: `file`,
multiple: ``,
disabled: unref(Z) || unref(W),
onChange: he,
},
null,
40,
P,
),
]),
unref(X).length
? (openBlock(),
createElementBlock(`ul`, F, [
(openBlock(!0),
createElementBlock(
Fragment,
null,
renderList(
unref(X),
(t, n) => (
openBlock(),
createElementBlock(
`li`,
{
key: t.save_name,
},
[
(i[14] ||= createElementVNode(
`i`,
{
class: `pi pi-paperclip`,
},
null,
-1,
)),
createElementVNode(
`span`,
I,
toDisplayString(t.name),
1,
),
createElementVNode(
`button`,
{
type: `button`,
class: `rm`,
"aria-label": `移除附件 ${t.name}`,
disabled: unref(W),
onClick: (e) => ge(n),
},
[
...(i[13] ||= [
createElementVNode(
`i`,
{
class: `pi pi-times`,
},
null,
-1,
),
]),
],
8,
L,
),
],
)
),
),
128,
)),
]))
: createCommentVNode(``, !0),
]),
unref(G)
? (openBlock(),
createBlock(
Y,
{
key: 0,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(G)), 1),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
createElementVNode(`div`, R, [
createVNode(
_e,
{
to: `/tickets`,
class: `cancel-link`,
},
{
default: withCtx(() => [
...(i[16] ||= [createTextVNode(`取消`, -1)]),
]),
_: 1,
},
),
createVNode(
c,
{
type: `submit`,
label: `创建工单`,
loading: unref(W),
disabled: unref(Z) || unref(K) || !unref(q),
},
null,
8,
[`loading`, `disabled`],
),
]),
]),
]),
],
32,
),
])
);
}
);
},
}),
[[`__scopeId`, `data-v-710c7c49`]],
);
export {};
export default z;
</script>
+623
View File
@@ -0,0 +1,623 @@
<script>
import "~/assets/styles/tickets.Dz8JrDhQ.css";
import { toDisplayString } from "vue";
import { onMounted } from "vue";
import { createBlock } from "vue";
import { ref } from "vue";
import { withCtx } from "vue";
import { createCommentVNode } from "vue";
import { computed } from "vue";
import { isRef } from "vue";
import { createElementBlock } from "vue";
import { createElementVNode } from "vue";
import { createTextVNode } from "vue";
import { unref } from "vue";
import { defineComponent } from "vue";
import { createVNode } from "vue";
import { openBlock } from "vue";
import { normalizeStyle } from "vue";
import { withKeys } from "vue";
import { useRoute } from "#app";
import { NuxtLink } from "#components";
import { useApi } from "~/lib/auth.js";
import button from "primevue/button";
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
import message from "primevue/message";
import inputtext from "primevue/inputtext";
import { r as dict_r } from "~/lib/dict.js";
import datatable from "primevue/datatable";
import paginator from "primevue/paginator";
import select from "primevue/select";
import column from "primevue/column";
import ListEmpty from "~/components/ListEmpty.vue";
import { t as useListPage_t } from "~/lib/useListPage.js";
var ie = {
class: `list-page`,
},
D = {
class: `list-head`,
},
O = {
class: `list-head-actions`,
},
ae = {
class: `list-toolbar`,
},
oe = {
class: `list-toolbar-right`,
},
k = {
class: `list-table`,
},
A = {
key: 3,
class: `list-foot`,
},
j = _plugin_vue_export_helper_t(
defineComponent({
__name: `index`,
setup(f) {
let v = useApi(),
j = useRoute(),
M = ref([]),
N = ref([]),
P = ref({}),
F = ref(``),
I = ref(!1),
L = ref(``),
R = ref(!1),
z = ref(``),
B = ref(``),
V = Number(j.query.status),
H = ref(V > 0 ? V : null),
U = ref(null),
{
list: W,
total: G,
loading: K,
error: q,
page: J,
limit: Y,
onPage: se,
reload: X,
} = useListPage_t(`/ticket`, {
limit: 10,
params: () => ({
keywords: B.value,
status: H.value ? [H.value] : [],
ticket_type_id: U.value ?? ``,
}),
});
async function Z() {
if (!I.value) {
((I.value = !0), (F.value = ``));
try {
let [e, t] = await Promise.allSettled([
v.get(`/ticket/status`),
v.get(`/ticket/type`),
]);
(e.status === `fulfilled` && (M.value = e.value.list ?? []),
t.status === `fulfilled` && (N.value = t.value.list ?? []));
let n = [e, t].flatMap((e, t) =>
e.status === `rejected`
? [
`${t === 0 ? `工单状态` : `工单类型`}加载失败:${e.reason instanceof Error ? e.reason.message : `请稍后重试`}`,
]
: [],
);
F.value = n.join(`;`);
} finally {
I.value = !1;
}
}
}
async function Q() {
if (!R.value) {
((R.value = !0), (L.value = ``));
try {
P.value = await v.get(`/ticket/statistic`);
} catch (e) {
L.value = e instanceof Error ? e.message : `工单统计加载失败`;
} finally {
R.value = !1;
}
}
}
computed(() =>
M.value
.filter((e) => P.value[e.id] !== void 0)
.map((e) => ({
...e,
count: P.value[e.id] ?? 0,
})),
);
let ce = computed(() => [
{
label: `全部状态`,
value: null,
},
...M.value.map((e) => ({
label: e.name,
value: e.id,
})),
]),
le = computed(() => [
{
label: `全部类型`,
value: null,
},
...N.value.map((e) => ({
label: e.name,
value: e.id,
})),
]),
$ = computed(() => !!B.value || !!H.value || !!U.value);
function ue() {
((B.value = z.value.trim()), X());
}
function de() {
((z.value = ``),
(B.value = ``),
(H.value = null),
(U.value = null),
X());
}
return (
onMounted(() => {
(Z(), Q());
}),
(t, r) => {
let o = message,
f = button,
g = NuxtLink,
_ = inputtext,
v = select,
E = ListEmpty,
j = column,
M = datatable,
N = paginator;
return (
openBlock(),
createElementBlock(`div`, ie, [
createVNode(
o,
{
severity: `info`,
closable: !1,
class: `support-notice`,
},
{
default: withCtx(() => [
...(r[3] ||= [
createElementVNode(
`div`,
null,
[
createTextVNode(
`尊敬的客户您好:为了更好地为您服务,请详细描述您遇到的问题,并提供相关截图或报错信息。`,
),
createElementVNode(`br`),
createTextVNode(
`请勿在工单中发送账户密码等敏感信息。我们会尽快为您处理。`,
),
],
-1,
),
]),
]),
_: 1,
},
),
createElementVNode(`div`, D, [
(r[4] ||= createElementVNode(`h2`, null, `工单`, -1)),
createElementVNode(`div`, O, [
createVNode(
g,
{
to: `/tickets/create`,
},
{
default: withCtx(() => [
createVNode(f, {
label: `创建工单`,
icon: `pi pi-plus`,
}),
]),
_: 1,
},
),
]),
]),
unref(L)
? (openBlock(),
createBlock(
o,
{
key: 0,
severity: `warn`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(
` 工单统计暂不可用:` +
toDisplayString(unref(L)) +
` `,
1,
),
createVNode(
f,
{
label: `重试统计`,
size: `small`,
severity: `secondary`,
text: ``,
loading: unref(R),
onClick: Q,
},
null,
8,
[`loading`],
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
unref(F)
? (openBlock(),
createBlock(
o,
{
key: 1,
severity: `warn`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(F)) + ` `, 1),
createVNode(
f,
{
label: `重试筛选选项`,
size: `small`,
severity: `secondary`,
text: ``,
loading: unref(I),
onClick: Z,
},
null,
8,
[`loading`],
),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
createElementVNode(`div`, ae, [
createElementVNode(`div`, oe, [
createVNode(
_,
{
modelValue: unref(z),
"onUpdate:modelValue": (r[0] ||= (e) =>
isRef(z) ? (z.value = e) : null),
class: `list-search`,
placeholder: `输入关键字`,
onKeyup: withKeys(ue, [`enter`]),
},
null,
8,
[`modelValue`],
),
createVNode(
v,
{
modelValue: unref(U),
"onUpdate:modelValue": (r[1] ||= (e) =>
isRef(U) ? (U.value = e) : null),
class: `list-select`,
options: unref(le),
optionLabel: `label`,
optionValue: `value`,
onChange: unref(X),
},
null,
8,
[`modelValue`, `options`, `onChange`],
),
createVNode(
v,
{
modelValue: unref(H),
"onUpdate:modelValue": (r[2] ||= (e) =>
isRef(H) ? (H.value = e) : null),
class: `list-select`,
options: unref(ce),
optionLabel: `label`,
optionValue: `value`,
onChange: unref(X),
},
null,
8,
[`modelValue`, `options`, `onChange`],
),
createVNode(f, {
label: `清除筛选`,
icon: `pi pi-filter-slash`,
severity: `secondary`,
text: ``,
size: `small`,
onClick: de,
}),
]),
]),
createElementVNode(`div`, k, [
createVNode(
M,
{
value: unref(W),
loading: unref(K),
lazy: ``,
rows: unref(Y),
totalRecords: unref(G),
first: (unref(J) - 1) * unref(Y),
dataKey: `id`,
},
{
empty: withCtx(() => [
createVNode(
E,
{
icon: `pi pi-comments`,
title: unref(K) ? `加载中…` : `还没有工单`,
description: unref($)
? `换个筛选条件试试`
: `遇到问题可以提交工单联系我们`,
},
{
default: withCtx(() => [
unref($)
? createCommentVNode(``, !0)
: (openBlock(),
createBlock(
g,
{
key: 0,
to: `/tickets/create`,
},
{
default: withCtx(() => [
createVNode(f, {
label: `创建工单`,
icon: `pi pi-plus`,
size: `small`,
}),
]),
_: 1,
},
)),
]),
_: 1,
},
8,
[`title`, `description`],
),
]),
default: withCtx(() => [
createVNode(
j,
{
header: `ID`,
style: {
width: `120px`,
},
},
{
body: withCtx(({ data: t }) => [
createVNode(
g,
{
to: `/tickets/${t.id}`,
class: `col-id`,
},
{
default: withCtx(() => [
createTextVNode(
`#` + toDisplayString(t.ticket_num),
1,
),
]),
_: 2,
},
1032,
[`to`],
),
]),
_: 1,
},
),
createVNode(
j,
{
header: `标题`,
style: {
"min-width": `220px`,
},
},
{
body: withCtx(({ data: t }) => [
createVNode(
g,
{
to: `/tickets/${t.id}`,
class: `title-link`,
},
{
default: withCtx(() => [
createTextVNode(
toDisplayString(t.title),
1,
),
]),
_: 2,
},
1032,
[`to`],
),
]),
_: 1,
},
),
createVNode(
j,
{
header: `工单类型`,
style: {
width: `130px`,
},
},
{
body: withCtx(({ data: t }) => [
createTextVNode(
toDisplayString(t.name || `—`),
1,
),
]),
_: 1,
},
),
createVNode(
j,
{
header: `提交时间`,
style: {
"min-width": `160px`,
},
},
{
body: withCtx(({ data: n }) => [
createTextVNode(
toDisplayString(
(`formatTime` in t
? t.formatTime
: unref(dict_r))(n.post_time),
),
1,
),
]),
_: 1,
},
),
createVNode(
j,
{
header: `最近回复`,
style: {
"min-width": `160px`,
},
},
{
body: withCtx(({ data: n }) => [
createTextVNode(
toDisplayString(
n.last_reply_time
? (`formatTime` in t
? t.formatTime
: unref(dict_r))(n.last_reply_time)
: `—`,
),
1,
),
]),
_: 1,
},
),
createVNode(
j,
{
header: `状态`,
style: {
width: `110px`,
},
},
{
body: withCtx(({ data: t }) => [
createElementVNode(
`span`,
{
class: `status-tag`,
style: normalizeStyle({
color: `var(--p-text-color)`,
borderColor: t.color,
}),
},
toDisplayString(t.status),
5,
),
]),
_: 1,
},
),
]),
_: 1,
},
8,
[`value`, `loading`, `rows`, `totalRecords`, `first`],
),
]),
unref(q)
? (openBlock(),
createBlock(
o,
{
key: 2,
severity: `error`,
closable: !1,
},
{
default: withCtx(() => [
createTextVNode(toDisplayString(unref(q)), 1),
]),
_: 1,
},
))
: createCommentVNode(``, !0),
unref(G) > 0
? (openBlock(),
createElementBlock(`div`, A, [
(r[5] ||= createElementVNode(
`div`,
{
class: `list-bulk`,
},
null,
-1,
)),
createVNode(
N,
{
rows: unref(Y),
totalRecords: unref(G),
first: (unref(J) - 1) * unref(Y),
rowsPerPageOptions: [10, 20, 50],
onPage: unref(se),
},
null,
8,
[`rows`, `totalRecords`, `first`, `onPage`],
),
]))
: createCommentVNode(``, !0),
])
);
}
);
},
}),
[[`__scopeId`, `data-v-1ff3e04d`]],
);
export {};
export default j;
</script>
+17
View File
@@ -0,0 +1,17 @@
<script>
import "~/assets/styles/TicketAttachments.DtbJGLkE.css";
import "~/assets/styles/RichTextEditor.NkmgPT5m.css";
import "~/assets/styles/_id_.C-0--Lx-.css";
import { createBlock } from "vue";
import { defineComponent } from "vue";
import { openBlock } from "vue";
import _id_ from "~/pages/tickets/[id].vue";
var i = defineComponent({
__name: `ticket`,
setup(t) {
return (t, i) => (openBlock(), createBlock(_id_));
},
});
export {};
export default i;
</script>