问题工单后台管理
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

700 lines
19 KiB

<!--
功能功能描述
作者Aaron.Wu
时间2023年05月25日 17:00:26
版本v1.0
修改记录
修改内容
修改人员
修改时间
-->
<template>
<div class="h-[100%]">
<div class="issue-container h-[55%] overflow-auto" ref="issueContainerRef">
<DynamicTable
size="small"
showIndex
headerTitle="问题工单列表"
titleTooltip=""
:data-request="loadData"
:columns="columns"
row-key="id"
@resize-column="handleResizeColumn"
:row-class-name="getRowClassName"
:customRow="customRow"
:row-selection="rowSelection"
:scroll="{ x: '100vw' }"
>
<template v-if="isCheckRows" #title>
<Alert class="w-full" type="info" show-icon>
<template #message>
已选 {{ isCheckRows }}
<a-button type="link" @click="rowSelection.selectedRowKeys = []">取消选择</a-button>
</template>
</Alert>
</template>
<template #toolbar>
<a-button type="primary" @click="handleAdd">新增</a-button>
<a-button
type="danger"
:disabled="!isCheckRows"
@click="delRowConfirm(rowSelection.selectedRowKeys)"
>
<DeleteOutlined /> 删除
</a-button>
</template>
</DynamicTable>
<Modal
v-model:visible="visible"
:width="1000"
:bodyStyle="{
height: '55vh',
overflow: 'auto',
}"
:title="`${curRecord.id ? '编辑' : '新增'}问题工单`"
@ok="handleOk"
@cancel="handleCancel"
:mask-closable="false"
:destroyOnClose="true"
:getContainer="() => issueContainerRef"
>
<SchemaForm>
<template #solution="{ formModel, field }">
<div class="ql-box">
<QuillEditor
ref="quillEditor"
theme="snow"
v-model:content="formModel[field]"
:options="editorOptions"
contentType="html"
/>
</div>
</template>
</SchemaForm>
</Modal>
</div>
<div class="detail-container bg-white flex justify-center items-center min-h-[200px]">
<Detail :id="curRowId" v-if="curRowId"></Detail>
<a-empty v-else>
<template #description>
<span> 请选择问题工单查看 </span>
</template>
</a-empty>
</div>
</div>
</template>
<script lang="tsx" setup>
import { QuillEditor } from '@vueup/vue-quill';
import Detail from './detail.vue';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { type TableListItem, baseColumns } from './columns';
import { useTable, type OnChangeCallbackParams } from '@/components/core/dynamic-table';
import {
fetchIssuePageList,
createIssue,
updateIssue,
deleteIssueById,
deleteBatchIssueById,
findOneById,
updateIssueState,
addToknowledge,
} from '@/api/issue';
import { computed, nextTick, ref, watch, onMounted } from 'vue';
import { message, Alert, Modal } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import { useFormModal } from '@/hooks/useModal/index';
import { getEditFormSchema, getFlowFormSchema } from './formSchemas';
import { ExclamationCircleOutlined, DeleteOutlinƒed } from '@ant-design/icons-vue';
import { stateTypeList } from './data';
import { fetchVersionPageList } from '@/api/prodVersion';
import { DraggableModal } from '@/components/core/draggable-modal';
import { useForm } from '@/components/core/schema-form';
import { quillImageUploadCustom } from '../commom/tools';
defineOptions({
name: 'issue',
});
const router = useRouter();
const issueContainerRef = ref<HTMLElement | null>(null);
const curRecord = ref<Partial<TableListItem>>({});
const curRowId = ref<any>('');
const visible = ref(false);
const quillEditor = ref<InstanceType<typeof QuillEditor> | null>(null);
const editorOptions = ref({
theme: 'snow',
modules: {
toolbar: [
[{ header: '1' }, { header: '2' }, { font: [] }],
[{ list: 'ordered' }, { list: 'bullet' }],
['bold', 'italic', 'underline'],
['link'], // 添加图片按钮
],
},
});
// 设置行的类名
const getRowClassName = (record) => {
return curRowId.value === record.id ? 'highlight-row' : '';
};
const customRow = (record: TableListItem) => {
return {
style: {
cursor: 'pointer',
},
onClick: () => {
// handleView(record);
curRowId.value = record.id;
curRecord.value = record;
},
};
};
const [showModal] = useFormModal();
const [showFlowModal] = useFormModal();
const [SchemaForm, formRef] = useForm({
labelWidth: 100,
labelAlign: 'right',
schemas: getEditFormSchema(curRecord.value),
showActionButtonGroup: false,
actionColOptions: {
span: 24,
},
submitButtonOptions: {
text: '保存',
},
});
// watch(
// () => curRecord.value,
// (newVal) => {
// console.log('newVal: ', newVal);
// formRef?.updateSchema(getEditFormSchema(newVal));
// },
// {
// immediate: true,
// deep: true,
// },
// );
const time = ref(0);
watch(
() => visible.value,
(newVal) => {
if (newVal) {
nextTick(() => {
if (time.value === 0) {
quillImageUploadCustom(quillEditor.value);
time.value++;
}
});
}
},
);
// 替换 Quill 编辑器的图片按钮操作
onMounted(() => {
// quillImageUploadCustom(quillEditor.value);
});
const handleOk = async () => {
const values = await formRef?.validate();
if (values) {
console.log('values: ', values);
values.id = curRecord.value.id;
if (values.files && Array.isArray(values.files) && values.files.length) {
values.files = values.files.map((e) => {
if (e.response) {
return {
name: e.name,
url: e.response.url,
id: e.response.id,
};
}
return {
...e,
};
});
values.fileIds = values.files.map((e) => e.id).join(',');
}
if (values?.tags && Array.isArray(values.tags) && values.tags.length) {
values.tags = values.tags.join(',');
}
await (values.id ? updateIssue : createIssue)(values);
message.success(`${values.id ? '编辑' : '新增'}成功`);
visible.value = false;
resetFormFields();
dynamicTableInstance?.reload();
}
};
const handleCancel = () => {
visible.value = false;
};
const columns: any = [
...baseColumns,
{
title: '操作',
width: 200,
dataIndex: 'ACTION',
hideInSearch: true,
align: 'center',
fixed: 'right',
actions: ({ record }) => {
const { state } = record;
const stateText = stateTypeList.find((item) => item.value === state)?.label;
return [
// {
// icon: 'searchoutlined',
// color: '#3b82f6',
// label: '查看',
// onClick: () => handleView(record),
// },
{
icon: 'edit',
color: '#3b82f6',
size: '15',
label: '修改',
ifShow: stateText !== '关闭',
onClick: (e) => {
e.stopPropagation();
handleEdit(record);
},
},
{
icon: 'init',
size: '15',
title: '处理',
label: '处理',
ifShow: stateText === '退回',
onClick: (e) => {
e.stopPropagation();
changeState(record, 0);
},
},
{
icon: 'dev',
size: '15',
title: '开发',
label: '开发',
ifShow: stateText === '待处理',
onClick: (e) => {
e.stopPropagation();
changeState(record, 2);
},
},
{
icon: 'back',
size: '20',
title: '退回',
label: '退回',
ifShow: stateText === '开发中' || stateText === '测试中',
onClick: (e) => {
e.stopPropagation();
changeState(record, 1);
},
},
{
icon: 'test',
size: '15',
title: '测试',
label: '测试',
ifShow: stateText === '开发中',
onClick: (e) => {
e.stopPropagation();
changeState(record, 3);
},
},
{
icon: 'end',
size: '15',
title: '结束',
label: '结束',
ifShow: stateText === '测试中',
onClick: (e) => {
e.stopPropagation();
changeState(record, 4);
},
},
{
icon: 'knowledge',
size: '15',
title: '添加到知识库',
label: '添加到知识库',
ifShow: stateText === '已解决',
onClick: (e) => {
e.stopPropagation();
handleAddToKnowledge(record);
},
},
{
icon: 'close',
size: '16',
title: '关闭',
label: '关闭',
ifShow: stateText !== '关闭',
onClick: (e) => {
e.stopPropagation();
changeState(record, 5);
},
},
{
icon: 'delete',
color: '#ec6f6f',
label: '删除',
ifShow: stateText !== '关闭',
onClick: (e) => {
e.stopPropagation();
handleDelete(record.id);
},
// popConfirm: {
// title: '确定要删除吗?',
// onConfirm: () => handleDelete(record.id),
// },
},
];
},
},
];
const changeState = async (record: Partial<TableListItem>, state: number) => {
await openFlowModal(record, state);
};
/**
* @description 打开问题工单弹窗
*/
const openIssueModal = async (record: Partial<TableListItem> = {}, isReadOnly = false) => {
const [formRef] = await showModal<any>({
modalProps: {
title: `${isReadOnly ? '查看' : record.id ? '编辑' : '新增'}问题工单`,
width: 700,
onFinish: async (values) => {
console.log('新增/编辑问题工单', values);
values.id = record.id;
if (values.files && Array.isArray(values.files) && values.files.length) {
values.files = values.files.map((e) => {
if (e.response) {
return {
name: e.name,
url: e.response.url,
id: e.response.id,
};
}
return {
...e,
};
});
values.fileIds = values.files.map((e) => e.id).join(',');
}
if (values?.tags && Array.isArray(values.tags) && values.tags.length) {
values.tags = values.tags.join(',');
}
await (record.id ? updateIssue : createIssue)(values);
message.success(`${record.id ? '编辑' : '新增'}成功`);
dynamicTableInstance?.reload();
},
footer: isReadOnly ? null : undefined,
},
formProps: {
labelWidth: 100,
schemas: getEditFormSchema(record, isReadOnly) as any,
autoSubmitOnEnter: true,
disabled: isReadOnly,
},
});
formRef?.setFieldsValue(record);
if (record?.productId) {
const { data } = await fetchVersionPageList({
productId: record?.productId,
current: 1,
size: 999,
});
if (data && Array.isArray(data) && data.length) {
formRef?.updateSchema({
field: 'versionId',
componentProps: {
options: data.map((e) => {
return {
label: e.name,
value: e.id,
};
}),
},
});
}
}
};
const openFlowModal = async (record: Partial<TableListItem> = {}, state) => {
const flowTitle = stateTypeList.find((item) => item.value === state)?.label;
const [formRef] = await showFlowModal<any>({
modalProps: {
title: `${flowTitle}问题工单`,
width: 700,
onFinish: async (values) => {
await updateIssueState({
id: record.id,
state: state,
...values,
});
message.success(`操作成功`);
dynamicTableInstance?.reload();
},
},
formProps: {
labelWidth: 100,
schemas: getFlowFormSchema(record) as any,
autoSubmitOnEnter: true,
},
});
formRef?.setFieldsValue(record);
};
const handleView = (record: TableListItem) => {
router.push({
path: '/question/issueDetail',
query: {
id: record.id,
},
});
};
const resetFormFields = () => {
formRef?.resetFields();
};
const openModal = (cb: any) => {
visible.value = true;
nextTick(() => {
cb();
});
formRef.clearValidate();
};
const handleAddToKnowledge = async (record: TableListItem) => {
console.log('record: ', record);
if (!record.id) return;
const res = await addToknowledge({
id: record.id,
});
if (res) {
message.success('添加到知识库成功');
dynamicTableInstance?.reload();
}
};
const handleAdd = () => {
curRecord.value = {};
// formRef?.setFieldsValue(curRecord.value);
formRef?.setFieldsValue(curRecord.value);
quillEditor.value?.setContents('');
resetFormFields();
openModal(() => {});
};
const handleEdit = async (record: TableListItem) => {
if (record?.id) {
const res = await findOneById({
id: record.id,
});
console.log('res: ', res);
if (res?.files && Array.isArray(res.files) && res.files.length) {
res.files = res.files.map((e) => {
return {
name: e.originalFilename,
url: e.url,
id: e.id,
};
});
}
if (res.tags) {
res.tags = res.tags.split(',') || [];
}
curRecord.value = res;
// quillEditor.value?.setContents(res?.solution);
// formRef?.setFieldsValue(res);
if (res?.productId) {
const { data } = await fetchVersionPageList({
productId: res?.productId,
current: 1,
size: 999,
});
if (data && Array.isArray(data) && data.length) {
formRef?.updateSchema({
field: 'versionId',
componentProps: {
options: data.map((e) => {
return {
label: e.name,
value: e.id,
};
}),
},
});
}
}
nextTick(() => {
openModal(() => {
formRef?.setFieldsValue(res);
quillEditor.value?.setContents(res?.solution);
});
});
}
};
const handleShow = async (record: TableListItem) => {
openIssueModal(record, true);
};
const handleDelete = (id: string) => {
delRowConfirm([id]);
};
/**
* @description 表格删除行
*/
const delRowConfirm = async (id: string[]) => {
Modal.confirm({
title: '确定要删除所选的问题工单吗?',
icon: <ExclamationCircleOutlined />,
centered: true,
onOk: async () => {
await deleteBatchIssueById(id).finally(dynamicTableInstance?.reload);
},
});
};
const [DynamicTable, dynamicTableInstance] = useTable();
const rowSelection = ref<any>({
selectedRowKeys: [] as string[],
onChange: (selectedRowKeys: string[], selectedRows: TableListItem[]) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
rowSelection.value.selectedRowKeys = selectedRowKeys;
},
});
// 是否勾选了表格行
const isCheckRows = computed(() => rowSelection.value.selectedRowKeys.length);
const loadData = async (
params,
onChangeParams?: OnChangeCallbackParams,
): Promise<API.TableListResult> => {
console.log('params', params);
console.log('onChangeParams', onChangeParams);
// 手动设置搜索表单的搜索项
// dynamicTableInstance?.getQueryFormRef()?.updateSchema?.([
// {
// field: 'price',
// componentProps: {
// options: [
// {
// label: '0-199',
// value: '0-199',
// },
// {
// label: '200-999',
// value: '200-999',
// },
// ],
// },
// },
// ]);
// return new Promise((resolve) => {
// setTimeout(() => {
// resolve({
// list: [
// {
// id: '1',
// name: '1',
// state: 0,
// files: [
// {
// name: '文件条款信息.xlsx',
// url: 'http://192.168.2.116:8089/oss/resource/2025-04-07/67f390f06f14d107a557e10f.xlsx',
// id: 'vc-upload-1744009547396-6',
// },
// {
// name: '文件条款信息 (1).xlsx',
// url: 'http://192.168.2.116:8089/oss/resource/2025-04-07/67f390fc6f14d107a557e110.xlsx',
// id: 'vc-upload-1744009547396-8',
// },
// ],
// },
// ],
// ...params,
// });
// }, 500);
// });
const dateRange = {
startDate: params.createTime
? params.createTime.length
? params.createTime[0]
: undefined
: undefined,
endDate: params.createTime
? params.createTime.length
? params.createTime[1]
: undefined
: undefined,
};
const res = await fetchIssuePageList({
...params,
...dateRange,
current: params.page,
size: params.limit,
});
console.log('res: ', res);
rowSelection.value.selectedRowKeys = [];
return {
list: res.data.records,
pagination: {
total: Number(res.data.total),
...params,
},
};
};
const handleResizeColumn = (w, col) => {
col.width = w;
};
</script>
<style lang="less">
/* 定义高亮行的样式 */
.highlight-row {
background-color: #e6f7ff !important; /* 设置高亮背景色 */
}
.action-divider {
margin: 0 5px;
}
.ql-box {
.ql-editor {
height: 400px !important;
}
}
</style>