Browse Source

feat: 知识库相关模块提交

master
AaronWu 8 months ago
parent
commit
9b02272ea9
  1. 11
      src/api/issue/index.ts
  2. 22
      src/api/knowledgeBase/index.ts
  3. 6
      src/api/knowledgeBase/model.d.ts
  4. 1
      src/locales/lang/en-US/routes/question.ts
  5. 1
      src/locales/lang/zh-CN/routes/question.ts
  6. 2
      src/router/constant.ts
  7. 13
      src/router/outsideLayout.ts
  8. 11
      src/router/staticModules/question.ts
  9. 179
      src/views/knowledgeBase/index.vue
  10. 3
      src/views/question/issue/columns.tsx
  11. 74
      src/views/question/issue/detail.vue
  12. 1
      src/views/question/issue/formSchemas.tsx
  13. 50
      src/views/question/issue/index.vue
  14. 62
      src/views/question/knowledge/columns.tsx
  15. 32
      src/views/question/knowledge/data.ts
  16. 156
      src/views/question/knowledge/detail.vue
  17. 83
      src/views/question/knowledge/formSchemas.tsx
  18. 520
      src/views/question/knowledge/index.vue

11
src/api/issue/index.ts

@ -94,6 +94,17 @@ export function findOneById(params: { id: string }) {
});
}
/**
* @description
*/
export function addToknowledge(params: { id: string }) {
return request({
url: `question/addKnowledge`,
method: 'get',
params,
});
}
/**
* @description ?id=${data.id}
*/

22
src/api/knowledgeBase/index.ts

@ -10,15 +10,15 @@ import type { BaseResponse } from '@/utils/request';
import { request } from '@/utils/request';
/**
* @description
* @param {SearchListParams} data
* @param {SearchListParams} params
* @returns
*/
export function fetchKnowledgeBaseList(data: API.SearchListParams) {
export function fetchKnowledgeBaseList(params: API.SearchListParams) {
return request<BaseResponse<API.SearchListResult>>(
{
url: 'question/list',
method: 'post',
data,
url: 'knowledge/list',
method: 'get',
params,
},
{
isGetDataDirectly: false,
@ -34,7 +34,7 @@ export function fetchKnowledgeBaseList(data: API.SearchListParams) {
export function fetchKnowledgeBasePageList(data: API.SearchPageListParams) {
return request<BaseResponse<API.SearchPageListResult>>(
{
url: 'question/list',
url: 'knowledge/page',
method: 'post',
data,
},
@ -51,7 +51,7 @@ export function fetchKnowledgeBasePageList(data: API.SearchPageListParams) {
*/
export function createKnowledgeBase(data: API.KnowledgeBaseType) {
return request({
url: 'question/create',
url: 'knowledge/create',
method: 'post',
data,
});
@ -64,7 +64,7 @@ export function createKnowledgeBase(data: API.KnowledgeBaseType) {
*/
export function updateKnowledgeBase(data: API.KnowledgeBaseType) {
return request({
url: 'question/update',
url: 'knowledge/update',
method: 'post',
data,
});
@ -75,7 +75,7 @@ export function updateKnowledgeBase(data: API.KnowledgeBaseType) {
*/
export function findOneById(params: { id: string }) {
return request({
url: `question/getById`,
url: `knowledge/getById`,
method: 'get',
params,
});
@ -86,7 +86,7 @@ export function findOneById(params: { id: string }) {
*/
export function deleteKnowledgeBaseById(params: API.DeleteKnowledgeBaseParams) {
return request({
url: `question/delById`,
url: `knowledge/delById`,
method: 'post',
params,
});
@ -97,7 +97,7 @@ export function deleteKnowledgeBaseById(params: API.DeleteKnowledgeBaseParams) {
*/
export function deleteBatchKnowledgeBaseById(data: API.DeleteBatchKnowledgeBaseParams) {
return request({
url: `question/delete`,
url: `knowledge/delete`,
method: 'delete',
data,
});

6
src/api/knowledgeBase/model.d.ts

@ -24,6 +24,9 @@ declare namespace API {
updateUserid?: number; // 更新人ID
version?: string; // 版本
fileList?: any[]; // 附件列表
tags?: string[]; // 标签
istop?: number; // 是否置顶
orderNO?: number; // 排序
};
type CreateKnowledgeBaseParams = {
@ -47,6 +50,9 @@ declare namespace API {
updateUserid?: number; // 更新人ID
version?: string; // 版本
fileList?: any[]; // 附件列表
tags?: string[]; // 标签
istop?: number; // 是否置顶
orderNO?: number; // 排序
};
type DeleteKnowledgeBaseParams = {

1
src/locales/lang/en-US/routes/question.ts

@ -10,4 +10,5 @@ export default {
question: 'question',
issue: 'Issue',
issueDetail: 'Issue Detail',
knowledge: 'Knowledge Base',
};

1
src/locales/lang/zh-CN/routes/question.ts

@ -10,4 +10,5 @@ export default {
question: '问题管理',
issue: '工单管理',
issueDetail: '工单详情',
knowledge: '知识库',
};

2
src/router/constant.ts

@ -1,5 +1,7 @@
export const LOGIN_NAME = 'Login';
export const KNOWLEDGE_NAME = 'Knowledge';
export const REDIRECT_NAME = 'Redirect';
export const PARENT_LAYOUT_NAME = 'ParentLayout';

13
src/router/outsideLayout.ts

@ -1,5 +1,5 @@
import type { RouteRecordRaw } from 'vue-router';
import { LOGIN_NAME } from '@/router/constant';
import { LOGIN_NAME, KNOWLEDGE_NAME } from '@/router/constant';
/**
* layout布局之外的路由
@ -13,4 +13,13 @@ export const LoginRoute: RouteRecordRaw = {
},
};
export default [LoginRoute];
export const KnowledgeRoute: RouteRecordRaw = {
path: '/knowledge',
name: KNOWLEDGE_NAME,
component: () => import(/* webpackChunkName: "login" */ '@/views/knowledgeBase/index.vue'),
meta: {
title: '知识库',
},
};
export default [LoginRoute, KnowledgeRoute];

11
src/router/staticModules/question.ts

@ -37,6 +37,17 @@ const routes: Array<RouteRecordRaw> = [
component: () =>
import(/* webpackChunkName: "question-issue" */ '@/views/question/issue/detail.vue'),
},
{
path: 'knowledge',
name: `${moduleName}-knowledge`,
meta: {
title: t('routes.question.knowledge'),
icon: 'icon-fenxiang',
keepAlive: true,
},
component: () =>
import(/* webpackChunkName: "question-knowledge" */ '@/views/question/knowledge/index.vue'),
},
],
},
];

179
src/views/knowledgeBase/index.vue

@ -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>

3
src/views/question/issue/columns.tsx

@ -128,6 +128,9 @@ export const baseColumns: TableColumnItem[] = [
defaultValue: '',
required: false,
component: 'RangePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD',
},
},
},
// state 问题状态

74
src/views/question/issue/detail.vue

@ -44,7 +44,19 @@
<a-tabs default-active-key="1">
<a-tab-pane key="1" tab="基础信息">
<SchemaForm> </SchemaForm>
<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">
@ -52,7 +64,7 @@
v-for="(item, index) in historyList"
:key="index"
:title="item.title"
:description="item.message"
:description="item.remark"
/>
</a-steps>
</a-tab-pane>
@ -62,6 +74,8 @@
</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';
@ -70,44 +84,26 @@
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<{
status: number;
message: string;
state: number;
remark: string;
title?: string;
}>
>([
{
status: 0,
message: '初始化',
},
{
status: 1,
message: '退回',
},
{
status: 2,
message: '开发',
},
{
status: 3,
message: '测试',
},
{
status: 4,
message: '结束',
},
]);
historyList.value = historyList.value.map((item) => {
return {
...item,
title: stateTypeList.find((e) => e.value === item.status)?.label,
};
});
>([]);
const current = ref(0);
@ -141,6 +137,16 @@
});
}
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);

1
src/views/question/issue/formSchemas.tsx

@ -246,6 +246,7 @@ export const getEditFormSchema: (
colProps: {
span: 24,
},
// vShow: !isDetail,
// rules: [{ required: true, type: 'array' }],
slot: 'solution',
},

50
src/views/question/issue/index.vue

@ -9,7 +9,6 @@
修改时间
-->
<template>
<!-- <QuillEditor theme="snow" /> -->
<DynamicTable
size="small"
showIndex
@ -55,7 +54,12 @@
<SchemaForm>
<template #solution="{ formModel, field }">
<div class="ql-box">
<QuillEditor theme="snow" v-model:content="formModel[field]" contentType="html" />
<QuillEditor
theme="snow"
v-model:content="formModel[field]"
:options="editorOptions"
contentType="html"
/>
</div>
</template>
</SchemaForm>
@ -75,6 +79,7 @@
deleteBatchIssueById,
findOneById,
updateIssueState,
addToknowledge,
} from '@/api/issue';
import { computed, nextTick, ref } from 'vue';
import { Modal, message, Alert } from 'ant-design-vue';
@ -96,7 +101,17 @@
const curRecord = ref<Partial<TableListItem>>({});
const visible = ref(false);
const content = ref('222');
const editorOptions = ref({
theme: 'snow',
modules: {
toolbar: [
[{ header: '1' }, { header: '2' }, { font: [] }],
[{ list: 'ordered' }, { list: 'bullet' }],
['bold', 'italic', 'underline'],
['link', 'image'], //
],
},
});
const [showModal] = useFormModal();
@ -222,6 +237,7 @@
title: '添加到知识库',
label: '添加到知识库',
ifShow: stateText === '结束',
onClick: () => handleAddToKnowledge(record),
},
{
icon: 'delete',
@ -350,6 +366,18 @@
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();
@ -494,8 +522,22 @@
// }, 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,
});
@ -515,7 +557,7 @@
};
</script>
<style lang="less" >
<style lang="less">
.action-divider {
margin: 0 5px;
}

62
src/views/question/knowledge/columns.tsx

@ -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',
},
},
},
];

32
src/views/question/knowledge/data.ts

@ -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',
},
];

156
src/views/question/knowledge/detail.vue

@ -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>

83
src/views/question/knowledge/formSchemas.tsx

@ -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,
},
},
];
};

520
src/views/question/knowledge/index.vue

@ -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…
Cancel
Save