Files
console-dev/app/pages/tickets/[id].vue
T
2026-11-06 00:04:38 +08:00

779 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>