|
|
|
<!--
|
|
|
|
功能:功能描述
|
|
|
|
作者:Aaron.Wu
|
|
|
|
时间:2023年05月25日 17:00:26
|
|
|
|
版本:v1.0
|
|
|
|
修改记录:
|
|
|
|
修改内容:
|
|
|
|
修改人员:
|
|
|
|
修改时间:
|
|
|
|
-->
|
|
|
|
<template>
|
|
|
|
<DynamicTable
|
|
|
|
size="small"
|
|
|
|
showIndex
|
|
|
|
headerTitle="问题工单列表"
|
|
|
|
titleTooltip=""
|
|
|
|
:data-request="loadData"
|
|
|
|
:columns="columns"
|
|
|
|
row-key="id"
|
|
|
|
@resize-column="handleResizeColumn"
|
|
|
|
: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>
|
|
|
|
<DraggableModal
|
|
|
|
v-model:visible="visible"
|
|
|
|
:width="700"
|
|
|
|
:bodyStyle="{
|
|
|
|
height: '70vh',
|
|
|
|
}"
|
|
|
|
:force-render="true"
|
|
|
|
:title="`${curRecord.id ? '编辑' : '新增'}问题工单`"
|
|
|
|
@ok="handleOk"
|
|
|
|
@cancel="handleCancel"
|
|
|
|
>
|
|
|
|
<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>
|
|
|
|
</DraggableModal>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="tsx" setup>
|
|
|
|
import { QuillEditor } from '@vueup/vue-quill';
|
|
|
|
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 } from 'vue';
|
|
|
|
import { Modal, message, Alert } from 'ant-design-vue';
|
|
|
|
import { useRouter } from 'vue-router';
|
|
|
|
import { useFormModal } from '@/hooks/useModal/index';
|
|
|
|
import { getEditFormSchema, getFlowFormSchema } from './formSchemas';
|
|
|
|
import { ExclamationCircleOutlined, DeleteOutlined } 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';
|
|
|
|
|
|
|
|
defineOptions({
|
|
|
|
name: 'issue',
|
|
|
|
});
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
const curRecord = ref<Partial<TableListItem>>({});
|
|
|
|
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', 'image'], // 添加图片按钮
|
|
|
|
],
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
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) => {
|
|
|
|
formRef?.setFieldsValue(newVal);
|
|
|
|
},
|
|
|
|
{
|
|
|
|
immediate: true,
|
|
|
|
deep: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
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: () => handleEdit(record),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
icon: 'init',
|
|
|
|
size: '15',
|
|
|
|
title: '初始化',
|
|
|
|
label: '初始化',
|
|
|
|
ifShow: stateText === '退回',
|
|
|
|
onClick: () => changeState(record, 0),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
icon: 'dev',
|
|
|
|
size: '15',
|
|
|
|
title: '开发',
|
|
|
|
label: '开发',
|
|
|
|
ifShow: stateText === '待处理',
|
|
|
|
onClick: () => changeState(record, 2),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
icon: 'back',
|
|
|
|
size: '20',
|
|
|
|
title: '退回',
|
|
|
|
label: '退回',
|
|
|
|
ifShow: stateText === '待处理',
|
|
|
|
onClick: () => changeState(record, 1),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
icon: 'test',
|
|
|
|
size: '15',
|
|
|
|
title: '测试',
|
|
|
|
label: '测试',
|
|
|
|
ifShow: stateText === '开发中',
|
|
|
|
onClick: () => changeState(record, 3),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
icon: 'end',
|
|
|
|
size: '15',
|
|
|
|
title: '结束',
|
|
|
|
label: '结束',
|
|
|
|
ifShow: stateText === '测试中',
|
|
|
|
onClick: () => changeState(record, 4),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
icon: 'knowledge',
|
|
|
|
size: '15',
|
|
|
|
title: '添加到知识库',
|
|
|
|
label: '添加到知识库',
|
|
|
|
ifShow: stateText === '已解决',
|
|
|
|
onClick: () => handleAddToKnowledge(record),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
icon: 'delete',
|
|
|
|
color: '#ec6f6f',
|
|
|
|
label: '删除',
|
|
|
|
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(`${flowTitle}成功`);
|
|
|
|
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 = () => {
|
|
|
|
visible.value = true;
|
|
|
|
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);
|
|
|
|
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();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleShow = async (record: TableListItem) => {
|
|
|
|
openIssueModal(record, true);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleDelete = (id: string) => {
|
|
|
|
delRowConfirm(id);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description 表格删除行
|
|
|
|
*/
|
|
|
|
const delRowConfirm = async (id: string | string[]) => {
|
|
|
|
Modal.confirm({
|
|
|
|
title: '确定要删除所选的问题工单吗?',
|
|
|
|
icon: <ExclamationCircleOutlined />,
|
|
|
|
centered: true,
|
|
|
|
onOk: async () => {
|
|
|
|
if (Array.isArray(id)) {
|
|
|
|
// 多个删除
|
|
|
|
await deleteBatchIssueById(id).finally(dynamicTableInstance?.reload);
|
|
|
|
} else {
|
|
|
|
await deleteIssueById({ 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">
|
|
|
|
.action-divider {
|
|
|
|
margin: 0 5px;
|
|
|
|
}
|
|
|
|
.ql-box {
|
|
|
|
.ql-editor {
|
|
|
|
height: 400px !important;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|