问题工单后台管理
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.

528 lines
14 KiB

<!--
功能功能描述
作者Aaron.Wu
时间2023年05月25日 17:00:26
版本v1.0
修改记录
修改内容
修改人员
修改时间
-->
<template>
<!-- <QuillEditor theme="snow" /> -->
<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 theme="snow" v-model:content="formModel[field]" 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,
} from '@/api/issue';
import { computed, nextTick, ref } 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 content = ref('222');
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: '保存',
},
});
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(',');
}
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 === '结束',
},
{
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(',');
}
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 handleAdd = () => {
formRef?.setFieldsValue(curRecord.value);
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,
};
});
}
curRecord.value = res;
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 res = await fetchIssuePageList({
...params,
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>