18 changed files with 1176 additions and 51 deletions
@ -0,0 +1,179 @@ |
|||||
|
<template> |
||||
|
<div class="knowledge-base"> |
||||
|
<a-row :gutter="16"> |
||||
|
<!-- 左侧目录树 --> |
||||
|
<a-col :span="6"> |
||||
|
<a-card class="tree-card"> |
||||
|
<template #title> |
||||
|
<span class="card-title"> |
||||
|
<folder-outlined /> 知识库目录 |
||||
|
</span> |
||||
|
</template> |
||||
|
<a-tree |
||||
|
v-model:selectedKeys="selectedKeys" |
||||
|
v-model:expandedKeys="expandedKeys" |
||||
|
:tree-data="treeData" |
||||
|
@select="onSelect" |
||||
|
/> |
||||
|
</a-card> |
||||
|
</a-col> |
||||
|
|
||||
|
<!-- 右侧内容区 --> |
||||
|
<a-col :span="18"> |
||||
|
<a-card class="content-card"> |
||||
|
<template #title> |
||||
|
<span class="card-title"> |
||||
|
<book-outlined /> 知识库内容 |
||||
|
</span> |
||||
|
</template> |
||||
|
|
||||
|
<!-- 快捷操作区域 --> |
||||
|
<div class="quick-actions"> |
||||
|
<a-space> |
||||
|
<a-button type="primary" v-for="action in quickActions" :key="action.title" @click="action.onClick"> |
||||
|
<template #icon><component :is="action.icon" /></template> |
||||
|
{{ action.title }} |
||||
|
</a-button> |
||||
|
</a-space> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 内容展示区域 --> |
||||
|
<div class="content-area"> |
||||
|
<template v-if="selectedKeys.length"> |
||||
|
<!-- 这里可以根据选中的目录显示具体内容 --> |
||||
|
<div class="selected-content"> |
||||
|
已选择: {{ selectedKeys[0] }} |
||||
|
</div> |
||||
|
</template> |
||||
|
<template v-else> |
||||
|
<a-empty description="请选择左侧目录" /> |
||||
|
</template> |
||||
|
</div> |
||||
|
</a-card> |
||||
|
</a-col> |
||||
|
</a-row> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref } from 'vue'; |
||||
|
import { |
||||
|
FolderOutlined, |
||||
|
BookOutlined, |
||||
|
FileTextOutlined, |
||||
|
TeamOutlined, |
||||
|
SettingOutlined, |
||||
|
PlusOutlined, |
||||
|
EditOutlined, |
||||
|
DeleteOutlined, |
||||
|
ExportOutlined |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
|
||||
|
// 目录树数据 |
||||
|
const treeData = ref([ |
||||
|
{ |
||||
|
title: '产品文档', |
||||
|
key: '1', |
||||
|
children: [ |
||||
|
{ title: '使用指南', key: '1-1' }, |
||||
|
{ title: '常见问题', key: '1-2' }, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
title: '技术文档', |
||||
|
key: '2', |
||||
|
children: [ |
||||
|
{ title: 'API文档', key: '2-1' }, |
||||
|
{ title: '开发规范', key: '2-2' }, |
||||
|
], |
||||
|
}, |
||||
|
]); |
||||
|
|
||||
|
// 树选择状态 |
||||
|
const selectedKeys = ref<string[]>([]); |
||||
|
const expandedKeys = ref<string[]>(['1', '2']); |
||||
|
|
||||
|
// 功能卡片数据 |
||||
|
const featureCards = ref([ |
||||
|
{ |
||||
|
title: '文档管理', |
||||
|
description: '集中管理所有知识文档', |
||||
|
icon: FileTextOutlined, |
||||
|
color: '#1890ff', |
||||
|
}, |
||||
|
{ |
||||
|
title: '团队协作', |
||||
|
description: '支持多人协同编辑', |
||||
|
icon: TeamOutlined, |
||||
|
color: '#52c41a', |
||||
|
}, |
||||
|
{ |
||||
|
title: '系统设置', |
||||
|
description: '自定义知识库配置', |
||||
|
icon: SettingOutlined, |
||||
|
color: '#722ed1', |
||||
|
}, |
||||
|
]); |
||||
|
|
||||
|
// 快捷操作按钮 |
||||
|
const quickActions = ref([ |
||||
|
{ |
||||
|
title: '新建文档', |
||||
|
icon: PlusOutlined, |
||||
|
onClick: () => console.log('新建文档'), |
||||
|
}, |
||||
|
{ |
||||
|
title: '编辑文档', |
||||
|
icon: EditOutlined, |
||||
|
onClick: () => console.log('编辑文档'), |
||||
|
}, |
||||
|
{ |
||||
|
title: '删除文档', |
||||
|
icon: DeleteOutlined, |
||||
|
onClick: () => console.log('删除文档'), |
||||
|
}, |
||||
|
{ |
||||
|
title: '导出文档', |
||||
|
icon: ExportOutlined, |
||||
|
onClick: () => console.log('导出文档'), |
||||
|
}, |
||||
|
]); |
||||
|
|
||||
|
// 树节点选择事件 |
||||
|
const onSelect = (selectedKeys: string[], info: any) => { |
||||
|
console.log('selected', selectedKeys, info); |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.knowledge-base { |
||||
|
padding: 24px; |
||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #e4e7eb 100%); |
||||
|
min-height: 100vh; |
||||
|
|
||||
|
.tree-card, |
||||
|
.content-card { |
||||
|
min-height: calc(100vh - 50px); |
||||
|
background: #fff; |
||||
|
border-radius: 8px; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
||||
|
|
||||
|
.quick-actions { |
||||
|
margin-bottom: 16px; |
||||
|
padding: 16px 0; |
||||
|
border-bottom: 1px solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.content-area { |
||||
|
padding: 16px 0; |
||||
|
min-height: 300px; |
||||
|
|
||||
|
.selected-content { |
||||
|
padding: 16px; |
||||
|
background: #fafafa; |
||||
|
border-radius: 4px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,62 @@ |
|||||
|
import type { TableColumn } from '@/components/core/dynamic-table'; |
||||
|
import { DictEnum } from '@/enums/dictEnum'; |
||||
|
import { getDictionaryByTypeName } from '@/utils/dict'; |
||||
|
export type TableListItem = API.KnowledgeBaseType; |
||||
|
export type TableColumnItem = TableColumn<TableListItem>; |
||||
|
|
||||
|
// const questionTypeList = await getDictionaryByTypeName(DictEnum.QUESTION_TYPE);
|
||||
|
|
||||
|
// 数据项类型
|
||||
|
// export type ListItemType = typeof tableData[number];
|
||||
|
// 使用TableColumn<ListItemType> 将会限制dataIndex的类型,但换来的是dataIndex有类型提示
|
||||
|
export const baseColumns: TableColumnItem[] = [ |
||||
|
{ |
||||
|
title: '标题', |
||||
|
align: 'center', |
||||
|
dataIndex: 'title', |
||||
|
// sorter: true,
|
||||
|
width: 150, |
||||
|
resizable: true, |
||||
|
formItemProps: { |
||||
|
defaultValue: '', |
||||
|
required: false, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: '描述', |
||||
|
align: 'center', |
||||
|
dataIndex: 'description', |
||||
|
width: 150, |
||||
|
resizable: true, |
||||
|
formItemProps: { |
||||
|
defaultValue: '', |
||||
|
required: false, |
||||
|
}, |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: '标签', |
||||
|
align: 'center', |
||||
|
dataIndex: 'tags', |
||||
|
width: 200, |
||||
|
hideInSearch: true, |
||||
|
customRender: ({ record }) => { |
||||
|
const tags = record.tags?.join(', '); |
||||
|
return <div>{{ tags }}</div>; |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: '创建时间', |
||||
|
align: 'center', |
||||
|
width: 200, |
||||
|
dataIndex: 'createTime', |
||||
|
formItemProps: { |
||||
|
defaultValue: '', |
||||
|
required: false, |
||||
|
component: 'RangePicker', |
||||
|
componentProps: { |
||||
|
valueFormat: 'YYYY-MM-DD', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
]; |
||||
@ -0,0 +1,32 @@ |
|||||
|
export const stateTypeList: any = [ |
||||
|
// Init(0,"初始化"),
|
||||
|
// Back(1,"退回"),
|
||||
|
// Develop(2,"开发"),
|
||||
|
// Test(3,"测试"),
|
||||
|
// End(4,"结束"),
|
||||
|
{ |
||||
|
label: '初始化', |
||||
|
value: 0, |
||||
|
color: '#f50', |
||||
|
}, |
||||
|
{ |
||||
|
label: '退回', |
||||
|
value: 1, |
||||
|
color: '#2db7f5', |
||||
|
}, |
||||
|
{ |
||||
|
label: '开发', |
||||
|
value: 2, |
||||
|
color: '#87d068', |
||||
|
}, |
||||
|
{ |
||||
|
label: '测试', |
||||
|
value: 3, |
||||
|
color: '#108ee9', |
||||
|
}, |
||||
|
{ |
||||
|
label: '结束', |
||||
|
value: 4, |
||||
|
color: '#f50', |
||||
|
}, |
||||
|
]; |
||||
@ -0,0 +1,156 @@ |
|||||
|
<!-- |
||||
|
功能:功能描述 |
||||
|
作者:Aaron |
||||
|
时间:2023年07月31日 11:51:23 |
||||
|
版本:v1.0 |
||||
|
修改记录: |
||||
|
修改内容: |
||||
|
修改人员: |
||||
|
修改时间: |
||||
|
--> |
||||
|
<template> |
||||
|
<div class="bg-[#fff] p-5"> |
||||
|
<a-spin :spinning="loading"> |
||||
|
<div class="my-3"> |
||||
|
<a-steps :current="current" size="small"> |
||||
|
<a-step title="初始化"> |
||||
|
<template #icon> |
||||
|
<SvgIcon :size="22" name="init" /> |
||||
|
</template> |
||||
|
</a-step> |
||||
|
|
||||
|
<a-step title="退回"> |
||||
|
<template #icon> |
||||
|
<SvgIcon :size="24" name="back" /> |
||||
|
</template> |
||||
|
</a-step> |
||||
|
<a-step title="开发"> |
||||
|
<template #icon> |
||||
|
<SvgIcon :size="24" name="dev" /> |
||||
|
</template> |
||||
|
</a-step> |
||||
|
<a-step title="测试"> |
||||
|
<template #icon> |
||||
|
<SvgIcon :size="24" name="test" /> |
||||
|
</template> |
||||
|
</a-step> |
||||
|
<a-step title="结束"> |
||||
|
<template #icon> |
||||
|
<SvgIcon :size="24" name="end" /> |
||||
|
</template> |
||||
|
</a-step> |
||||
|
</a-steps> |
||||
|
</div> |
||||
|
|
||||
|
<a-tabs default-active-key="1"> |
||||
|
<a-tab-pane key="1" tab="基础信息"> |
||||
|
<SchemaForm> |
||||
|
<template #solution="{ formModel, field }"> |
||||
|
<div class="ql-box"> |
||||
|
<QuillEditor |
||||
|
theme="snow" |
||||
|
v-model:content="formModel[field]" |
||||
|
:options="editorOptions" |
||||
|
:readOnly="true" |
||||
|
contentType="html" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
|
</SchemaForm> |
||||
|
</a-tab-pane> |
||||
|
<a-tab-pane key="2" tab="处理历史"> |
||||
|
<a-steps progress-dot :current="historyList.length - 1" direction="vertical"> |
||||
|
<a-step |
||||
|
v-for="(item, index) in historyList" |
||||
|
:key="index" |
||||
|
:title="item.title" |
||||
|
:description="item.remark" |
||||
|
/> |
||||
|
</a-steps> |
||||
|
</a-tab-pane> |
||||
|
</a-tabs> |
||||
|
</a-spin> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { QuillEditor } from '@vueup/vue-quill'; |
||||
|
import '@vueup/vue-quill/dist/vue-quill.snow.css'; |
||||
|
import { useForm } from '@/components/core/schema-form'; |
||||
|
import { getEditFormSchema } from './formSchemas'; |
||||
|
import { useRoute } from 'vue-router'; |
||||
|
import { onMounted, ref } from 'vue'; |
||||
|
import { findOneById } from '@/api/issue'; |
||||
|
import { SvgIcon } from '@/components/basic/svg-icon'; |
||||
|
import { stateTypeList } from './data'; |
||||
|
const route = useRoute(); |
||||
|
const editorOptions = ref({ |
||||
|
theme: 'snow', |
||||
|
modules: { |
||||
|
toolbar: [ |
||||
|
[{ header: '1' }, { header: '2' }, { font: [] }], |
||||
|
[{ list: 'ordered' }, { list: 'bullet' }], |
||||
|
['bold', 'italic', 'underline'], |
||||
|
['link', 'image'], // 添加图片按钮 |
||||
|
], |
||||
|
}, |
||||
|
}); |
||||
|
const loading = ref(false); |
||||
|
|
||||
|
const historyList = ref< |
||||
|
Array<{ |
||||
|
state: number; |
||||
|
remark: string; |
||||
|
title?: string; |
||||
|
}> |
||||
|
>([]); |
||||
|
|
||||
|
const current = ref(0); |
||||
|
|
||||
|
const { id } = route.query; |
||||
|
|
||||
|
const [SchemaForm, formRef] = useForm({ |
||||
|
labelWidth: 150, |
||||
|
schemas: getEditFormSchema({}, true), |
||||
|
showActionButtonGroup: false, |
||||
|
actionColOptions: { |
||||
|
span: 24, |
||||
|
}, |
||||
|
submitButtonOptions: { |
||||
|
text: '保存', |
||||
|
}, |
||||
|
disabled: true, |
||||
|
}); |
||||
|
|
||||
|
onMounted(async () => { |
||||
|
loading.value = true; |
||||
|
const res = await findOneById({ id: id as string }); |
||||
|
loading.value = false; |
||||
|
|
||||
|
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.historys && Array.isArray(res.historys) && res.historys.length) { |
||||
|
historyList.value = res.historys.map((e) => { |
||||
|
return { |
||||
|
state: e.state, |
||||
|
remark: `${e.createUserName}于${e.createTime} : ${e.remark}`, |
||||
|
title: stateTypeList.find((item) => item.value === e.state)?.label, |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
current.value = res?.state; |
||||
|
|
||||
|
formRef?.setFieldsValue(res); |
||||
|
formRef?.clearValidate(); |
||||
|
}); |
||||
|
</script> |
||||
|
<style lang="less" scoped></style> |
||||
@ -0,0 +1,83 @@ |
|||||
|
import type { FormSchema } from '@/components/core/schema-form/'; |
||||
|
import { TableListItem } from './columns'; |
||||
|
import { commonUpload } from '@/api/upload'; |
||||
|
import { message, Button } from 'ant-design-vue'; |
||||
|
import { getFileExtension } from '@/utils/fileUtils'; |
||||
|
import { UploadOutlined } from '@ant-design/icons-vue'; |
||||
|
import { stateTypeList } from './data'; |
||||
|
import { DictEnum } from '@/enums/dictEnum'; |
||||
|
import { getDictionaryByTypeName } from '@/utils/dict'; |
||||
|
import { fetchProdList, fetchVersionPageList } from '@/api/prodVersion'; |
||||
|
const questionTypeList = await getDictionaryByTypeName(DictEnum.QUESTION_TYPE); |
||||
|
// 编辑页字段
|
||||
|
export const getEditFormSchema: ( |
||||
|
row?: Partial<TableListItem>, |
||||
|
isDetail?: boolean, |
||||
|
) => FormSchema[] = (record = {}, isDetail = false) => { |
||||
|
console.log('questionTypeList: ', questionTypeList); |
||||
|
|
||||
|
return [ |
||||
|
{ |
||||
|
field: 'title', |
||||
|
component: 'Input', |
||||
|
componentProps: { |
||||
|
showCount: true, |
||||
|
maxlength: 150, |
||||
|
}, |
||||
|
label: '问题标题', |
||||
|
colProps: { |
||||
|
span: 12, |
||||
|
}, |
||||
|
rules: [{ required: true }], |
||||
|
}, |
||||
|
{ |
||||
|
field: 'versionId', |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: [], |
||||
|
}, |
||||
|
label: '版本', |
||||
|
colProps: { |
||||
|
span: 12, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'description', |
||||
|
component: 'InputTextArea', |
||||
|
componentProps: { |
||||
|
rows: 4, |
||||
|
placeholder: '请输入问题描述', |
||||
|
showCount: true, |
||||
|
maxlength: 150, |
||||
|
}, |
||||
|
label: '问题描述', |
||||
|
colProps: { |
||||
|
span: 24, |
||||
|
}, |
||||
|
rules: [{ required: true }], |
||||
|
}, |
||||
|
{ |
||||
|
label: '解决方案', |
||||
|
field: 'solution', |
||||
|
colProps: { |
||||
|
span: 24, |
||||
|
}, |
||||
|
// vShow: !isDetail,
|
||||
|
// rules: [{ required: true, type: 'array' }],
|
||||
|
slot: 'solution', |
||||
|
}, |
||||
|
] as any; |
||||
|
}; |
||||
|
|
||||
|
export const getFlowFormSchema: (row?: Partial<TableListItem>) => FormSchema[] = (record = {}) => { |
||||
|
return [ |
||||
|
{ |
||||
|
field: 'remark', |
||||
|
component: 'InputTextArea', |
||||
|
label: '文字补充内容', |
||||
|
colProps: { |
||||
|
span: 24, |
||||
|
}, |
||||
|
}, |
||||
|
]; |
||||
|
}; |
||||
@ -0,0 +1,520 @@ |
|||||
|
<!-- |
||||
|
功能:功能描述 |
||||
|
作者: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 |
||||
|
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 } 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 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: '保存', |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
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: 100, |
||||
|
dataIndex: 'ACTION', |
||||
|
hideInSearch: true, |
||||
|
align: 'center', |
||||
|
fixed: 'right', |
||||
|
actions: ({ record }) => { |
||||
|
const { state } = record; |
||||
|
return [ |
||||
|
{ |
||||
|
icon: 'searchoutlined', |
||||
|
color: '#3b82f6', |
||||
|
label: '查看', |
||||
|
onClick: () => handleView(record), |
||||
|
}, |
||||
|
{ |
||||
|
icon: 'edit', |
||||
|
color: '#3b82f6', |
||||
|
size: '15', |
||||
|
label: '修改', |
||||
|
onClick: () => handleEdit(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(','); |
||||
|
} |
||||
|
|
||||
|
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 = () => { |
||||
|
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 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> |
||||
Loading…
Reference in new issue