Browse Source

feat:添加产品版本管理模块,问题工单模块需求处理

master
AaronWu 1 year ago
parent
commit
1ed4f8d380
  1. 10
      src/api/dict/index.ts
  2. 13
      src/api/issue/index.ts
  3. 3
      src/api/issue/model.d.ts
  4. 104
      src/api/knowledgeBase/index.ts
  5. 57
      src/api/knowledgeBase/model.d.ts
  6. 2
      src/api/login/index.ts
  7. 106
      src/api/prodVersion/index.ts
  8. 23
      src/api/prodVersion/model.d.ts
  9. 2
      src/api/typings.d.ts
  10. 1
      src/assets/icons/back.svg
  11. 1
      src/assets/icons/dev.svg
  12. 1
      src/assets/icons/end.svg
  13. 1
      src/assets/icons/init.svg
  14. 1
      src/assets/icons/knowledge.svg
  15. 1
      src/assets/icons/test.svg
  16. 5
      src/components/core/dynamic-table/src/components/table-action.vue
  17. 3
      src/enums/dictEnum.ts
  18. 1
      src/locales/lang/en-US/routes/question.ts
  19. 1
      src/locales/lang/en-US/routes/system.ts
  20. 1
      src/locales/lang/zh-CN/routes/question.ts
  21. 1
      src/locales/lang/zh-CN/routes/system.ts
  22. 12
      src/router/staticModules/question.ts
  23. 11
      src/router/staticModules/system.ts
  24. 18
      src/store/modules/user.ts
  25. 20
      src/utils/dict.ts
  26. 37
      src/views/question/issue/columns.tsx
  27. 150
      src/views/question/issue/detail.vue
  28. 131
      src/views/question/issue/formSchemas.tsx
  29. 355
      src/views/question/issue/index.vue
  30. 325
      src/views/system/prodVersion/Tree.vue
  31. 30
      src/views/system/prodVersion/columns.tsx
  32. 48
      src/views/system/prodVersion/formSchemas.ts
  33. 241
      src/views/system/prodVersion/index.vue
  34. 14
      src/views/system/prodVersion/mockData.ts

10
src/api/dict/index.ts

@ -55,11 +55,11 @@ export function deleteDict(data: API.DeleteDictParams) {
* @param { fieldTypeId:string} data * @param { fieldTypeId:string} data
* @returns * @returns
*/ */
export function fetchDictValueListByType(data: { fieldTypeName: string }) {
export function fetchDictValueListByType(params: { typeName: string }) {
return request({ return request({
url: `/dict/query`,
method: 'post',
data,
url: `/dict/list`,
method: 'get',
params,
}); });
} }
@ -143,3 +143,5 @@ export function deleteBatchDictValueById(data: API.DeleteBatchDictValueParams) {
data, data,
}); });
} }

13
src/api/issue/index.ts

@ -70,6 +70,19 @@ export function updateIssue(data: API.IssueType) {
}); });
} }
/**
* @description
* @param {IssueType} data
* @returns
*/
export function updateIssueState(data: { id: string; state: number; remark: string }) {
return request({
url: 'question/changeState',
method: 'post',
data,
});
}
/** /**
* @description * @description
*/ */

3
src/api/issue/model.d.ts

@ -14,6 +14,9 @@ declare namespace API {
description?: string; // 问题描述 description?: string; // 问题描述
dr?: number; // 删除标记 dr?: number; // 删除标记
product?: string; // 产品 product?: string; // 产品
productId?: string; // 产品ID
version?: string; // 版本
versionId?: string; // 版本ID
serviceNumber?: string; // 服务号 serviceNumber?: string; // 服务号
state?: number; // 状态 state?: number; // 状态
title?: string; // 问题标题 title?: string; // 问题标题

104
src/api/knowledgeBase/index.ts

@ -0,0 +1,104 @@
/*
* @Author: AaronWu 2463371514@qq.com
* @Date: 2025-04-01 09:09:04
* @LastEditors: AaronWu 2463371514@qq.com
* @LastEditTime: 2025-04-01 11:34:26
* @FilePath: /KnowledgeBaseSupportManage/src/api/user/index.ts
* @Description: ,`customMade`, koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import type { BaseResponse } from '@/utils/request';
import { request } from '@/utils/request';
/**
* @description
* @param {SearchListParams} data
* @returns
*/
export function fetchKnowledgeBaseList(data: API.SearchListParams) {
return request<BaseResponse<API.SearchListResult>>(
{
url: 'question/list',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param {SearchPageListParams} data
* @returns
*/
export function fetchKnowledgeBasePageList(data: API.SearchPageListParams) {
return request<BaseResponse<API.SearchPageListResult>>(
{
url: 'question/list',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param {KnowledgeBaseType} data
* @returns
*/
export function createKnowledgeBase(data: API.KnowledgeBaseType) {
return request({
url: 'question/create',
method: 'post',
data,
});
}
/**
* @description
* @param {KnowledgeBaseType} data
* @returns
*/
export function updateKnowledgeBase(data: API.KnowledgeBaseType) {
return request({
url: 'question/update',
method: 'post',
data,
});
}
/**
* @description
*/
export function findOneById(params: { id: string }) {
return request({
url: `question/getById`,
method: 'get',
params,
});
}
/**
* @description ?id=${data.id}
*/
export function deleteKnowledgeBaseById(params: API.DeleteKnowledgeBaseParams) {
return request({
url: `question/delById`,
method: 'post',
params,
});
}
/**
* @description
*/
export function deleteBatchKnowledgeBaseById(data: API.DeleteBatchKnowledgeBaseParams) {
return request({
url: `question/delete`,
method: 'delete',
data,
});
}

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

@ -0,0 +1,57 @@
declare namespace API {
/** 问题工单类型 */
type KnowledgeBaseType = {
id?: string;
agent?: string; // 代理商
attribute?: string; // 问题属性
billcode?: string; // 问题号
contacts?: string; // 联系人
contactsEmail?: string; // 联系人邮箱
contactsMobile?: string; // 联系人手机号
createTime?: string; // 创建时间
createUserid?: number; // 创建人ID
customer?: string; // 客户
description?: string; // 问题描述
dr?: number; // 删除标记
product?: string; // 产品
productId?: string; // 产品ID
version?: string; // 版本
versionId?: string; // 版本ID
serviceNumber?: string; // 服务号
state?: number; // 状态
title?: string; // 问题标题
updateTime?: string; // 更新时间
updateUserid?: number; // 更新人ID
version?: string; // 版本
fileList?: any[]; // 附件列表
};
type CreateKnowledgeBaseParams = {
id?: string;
agent?: string; // 代理商
attribute?: string; // 问题属性
billcode?: string; // 问题号
contacts?: string; // 联系人
contactsEmail?: string; // 联系人邮箱
contactsMobile?: string; // 联系人手机号
createTime?: string; // 创建时间
createUserid?: number; // 创建人ID
customer?: string; // 客户
description?: string; // 问题描述
dr?: number; // 删除标记
product?: string; // 产品
serviceNumber?: string; // 服务号
state?: number; // 状态
title?: string; // 问题标题
updateTime?: string; // 更新时间
updateUserid?: number; // 更新人ID
version?: string; // 版本
fileList?: any[]; // 附件列表
};
type DeleteKnowledgeBaseParams = {
id: string;
};
type DeleteBatchKnowledgeBaseParams = string[];
}

2
src/api/login/index.ts

@ -31,7 +31,7 @@ export function register(data: API.RegisterParams) {
*/ */
export function getUserInfo() { export function getUserInfo() {
return request({ return request({
url: 'userInfo',
url: 'currentuser',
method: 'get', method: 'get',
}); });
} }

106
src/api/prodVersion/index.ts

@ -0,0 +1,106 @@
import type { BaseResponse } from '@/utils/request';
import { request } from '@/utils/request';
/**
* @description
* @param {SearchPageListParams} data
* @returns
*/
export function fetchProdList(data) {
return request<BaseResponse<API.SearchPageListResult>>({
url: `/product/list`,
method: 'post',
data,
});
}
/**
* @description
* @param {ProdType} data
* @returns
*/
export function createProd(data: API.ProdType) {
return request({
url: `/product/create`,
method: 'post',
data,
});
}
/**
* @description
* @param {ProdType} data
* @returns
*/
export function updateProd(data: API.ProdType) {
return request({
url: `/product/update`,
method: 'post',
data,
});
}
/**
* @description
*/
export function deleteProd(data: API.DeleteProdParams) {
return request({
url: `/product/delete?id=${data.id}`,
method: 'delete',
});
}
/**
* @description
* @param {SearchPageListParams} params
* @returns
*/
export function fetchVersionPageList(params: API.SearchPageListParams) {
return request<BaseResponse<API.SearchPageListResult>>(
{
url: `/productVersion/list`,
method: 'get',
params,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param {VersionType} data
* @returns
*/
export function createVersion(data: API.VersionType) {
return request({
url: `/productVersion/create`,
method: 'post',
data,
});
}
/**
* @description
* @param {VersionType} data
* @returns
*/
export function updateVersion(data: API.VersionType) {
return request({
url: `/productVersion/update`,
method: 'post',
data,
});
}
/**
* @description
*/
export function deleteBatchVersionById(data: API.DeleteBatchVersionParams) {
return request({
url: `/productVersion/delete`,
method: 'delete',
data,
});
}

23
src/api/prodVersion/model.d.ts

@ -0,0 +1,23 @@
declare namespace API {
type ProdType = {
id?: string;
name: string;
};
type DeleteProdParams = {
id: string;
};
type VersionType = {
id?: string;
productId: string;
name: string;
remark?: string;
};
type DeleteVersionParams = {
id: string;
};
type DeleteBatchVersionParams = string[];
}

2
src/api/typings.d.ts

@ -18,7 +18,7 @@ declare namespace API {
/** 查询分页列表参数 */ /** 查询分页列表参数 */
type SearchPageListParams = { type SearchPageListParams = {
[key: string]: any; [key: string]: any;
model: SearchListParams;
model?: SearchListParams;
current: number; current: number;
size: number; size: number;
sort?: string; sort?: string;

1
src/assets/icons/back.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744095269520" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8447" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M523.73504 319.29344h-204.8c-16.896 0-30.72-13.824-30.72-30.72s13.824-30.72 30.72-30.72h204.8c16.896 0 30.72 13.824 30.72 30.72s-13.824 30.72-30.72 30.72zM605.65504 452.41344h-286.72c-16.896 0-30.72-13.824-30.72-30.72s13.824-30.72 30.72-30.72h286.72c16.896 0 30.72 13.824 30.72 30.72s-13.824 30.72-30.72 30.72z" fill="#FFFFFF" p-id="8448"></path><path d="M624.64 862.72H215.04c-56.32 0-102.4-46.08-102.4-102.4v-522.24c0-56.32 46.08-102.4 102.4-102.4h409.6c56.32 0 102.4 46.08 102.4 102.4v522.24c0 56.32-46.08 102.4-102.4 102.4z" fill="#3889FF" p-id="8449"></path><path d="M532.48 309.76H225.28c-16.896 0-30.72-13.824-30.72-30.72s13.824-30.72 30.72-30.72h307.2c16.896 0 30.72 13.824 30.72 30.72s-13.824 30.72-30.72 30.72zM430.08 442.88H225.28c-16.896 0-30.72-13.824-30.72-30.72s13.824-30.72 30.72-30.72h204.8c16.896 0 30.72 13.824 30.72 30.72s-13.824 30.72-30.72 30.72zM368.64 576H225.28c-16.896 0-30.72-13.824-30.72-30.72s13.824-30.72 30.72-30.72h143.36c16.896 0 30.72 13.824 30.72 30.72s-13.824 30.72-30.72 30.72z" fill="#FFFFFF" p-id="8450"></path><path d="M526.12096 697.31328c0 68.23936 36.7104 131.28704 96.3072 165.40672a194.05824 194.05824 0 0 0 192.63488 0C874.6496 828.61056 911.36 765.55264 911.36 697.31328c0-68.23936-36.7104-131.29728-96.3072-165.41696a194.05824 194.05824 0 0 0-192.63488 0c-59.5968 34.12992-96.3072 97.18784-96.29696 165.41696z m0 0" fill="#40A0FF" p-id="8451"></path><path d="M819.968 722.91328l-202.69056 2.11968a26.112 26.112 0 0 1-26.4192-26.4192 27.45344 27.45344 0 0 1 26.97216-26.97216l202.69056-2.11968a26.112 26.112 0 0 1 26.4192 26.4192 27.45344 27.45344 0 0 1-26.97216 26.97216z" fill="#FFFFFF" p-id="8452"></path><path d="M598.03648 680.54016l100.28032-100.28032a25.8048 25.8048 0 0 1 36.38272 0l0.9728 0.9728a25.8048 25.8048 0 0 1 0 36.38272l-100.28032 100.28032a25.8048 25.8048 0 0 1-36.38272 0l-0.9728-0.9728a25.8048 25.8048 0 0 1 0-36.38272z" fill="#FFFFFF" p-id="8453"></path><path d="M636.19072 678.76864l97.45408 97.45408a25.8048 25.8048 0 0 1 0 36.38272l-1.76128 1.76128a25.8048 25.8048 0 0 1-36.38272 0l-97.45408-97.45408a25.8048 25.8048 0 0 1 0-36.38272l1.76128-1.76128a25.8048 25.8048 0 0 1 36.38272 0z" fill="#FFFFFF" p-id="8454"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

1
src/assets/icons/dev.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744095256377" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7418" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M529.0496 527.616l-30.7712-30.7456 85.0688-85.0944 30.7712 30.7712z" fill="#25AFFF" p-id="7419"></path><path d="M0 340.48l427.52 256 248.32 427.52L1024 0l-1024 340.48zM665.6 921.6l-207.36-355.84-355.84-212.48L911.36 81.92l-243.2 243.2 30.72 30.72 243.2-243.2L665.6 921.6z" fill="#25AFFF" p-id="7420"></path></svg>

After

Width:  |  Height:  |  Size: 646 B

1
src/assets/icons/end.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744095753934" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10499" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 0c282.752 0 512 229.248 512 512s-229.248 512-512 512S0 794.752 0 512 229.248 0 512 0z m0 85.333333C276.352 85.333333 85.333333 276.352 85.333333 512s191.018667 426.666667 426.666667 426.666667 426.666667-191.018667 426.666667-426.666667S747.648 85.333333 512 85.333333z m85.333333 256a85.333333 85.333333 0 0 1 85.333334 85.333334v170.666666a85.333333 85.333333 0 0 1-85.333334 85.333334h-170.666666a85.333333 85.333333 0 0 1-85.333334-85.333334v-170.666666a85.333333 85.333333 0 0 1 85.333334-85.333334h170.666666z" fill="#008df0" p-id="10500"></path></svg>

After

Width:  |  Height:  |  Size: 897 B

1
src/assets/icons/init.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744179079938" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12494" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M731.320963 365.811134V292.679037h160.671215A438.792578 438.792578 0 1 0 944.647288 585.207422h74.009681A512.070938 512.070938 0 0 1 0 512.075326a511.924674 511.924674 0 0 1 950.717252-263.787471V73.282748h73.132096v292.528386h-292.528385zM511.924674 365.811134a146.264193 146.264193 0 1 1 0 292.528385 146.264193 146.264193 0 0 1 0-292.528385z" fill="#0092FF" p-id="12495"></path></svg>

After

Width:  |  Height:  |  Size: 721 B

1
src/assets/icons/knowledge.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744096296383" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11505" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M372.363636 744.727273h-76.101818a54.225455 54.225455 0 0 1-50.036363-30.487273 40.727273 40.727273 0 0 1-4.887273-23.272727 151.738182 151.738182 0 0 0-23.272727-82.152728 152.436364 152.436364 0 0 0-20.945455-24.901818 351.883636 351.883636 0 0 1-41.890909-56.087272 302.545455 302.545455 0 0 1-27.927273-54.69091A180.363636 180.363636 0 0 1 116.363636 418.909091a246.923636 246.923636 0 0 1 6.050909-61.44 222.021818 222.021818 0 0 1 38.167273-84.712727A268.8 268.8 0 0 1 261.585455 186.181818a240.174545 240.174545 0 0 1 54.690909-18.385454 286.952727 286.952727 0 0 1 69.818181-5.585455 256 256 0 0 1 93.09091 21.178182 251.810909 251.810909 0 0 1 52.363636 32.349091 281.6 281.6 0 0 1 60.974545 67.723636 229.934545 229.934545 0 0 1 31.185455 76.334546 222.254545 222.254545 0 0 1 4.654545 66.792727 184.32 184.32 0 0 1-13.963636 55.621818 312.32 312.32 0 0 1-44.683636 77.265455 328.145455 328.145455 0 0 1-25.134546 28.858181 114.269091 114.269091 0 0 0-23.272727 34.676364 165.701818 165.701818 0 0 0-14.196364 40.261818c-1.163636 8.610909-1.163636 16.989091-2.094545 25.367273a124.741818 124.741818 0 0 1-2.56 18.385455 51.665455 51.665455 0 0 1-24.901818 29.323636 58.88 58.88 0 0 1-29.323637 7.214545z m-26.996363-512c-7.68 0-15.592727 1.396364-23.272728 2.792727a151.738182 151.738182 0 0 0-52.596363 18.850909 171.752727 171.752727 0 0 0-39.330909 31.418182 165.003636 165.003636 0 0 0-30.72 45.149091A169.658182 169.658182 0 0 0 186.181818 397.963636a19.549091 19.549091 0 1 0 39.098182 2.094546v-9.774546a116.363636 116.363636 0 0 1 7.447273-34.676363 131.956364 131.956364 0 0 1 46.545454-59.578182 118.458182 118.458182 0 0 1 66.792728-24.436364 85.178182 85.178182 0 0 0 13.265454-1.163636 16.523636 16.523636 0 0 0 13.498182-10.938182 25.6 25.6 0 0 0 0-13.265454 16.523636 16.523636 0 0 0-14.894546-13.498182z m-73.541818 581.818182h201.076363a15.127273 15.127273 0 0 1 15.825455 13.730909v42.356363a15.127273 15.127273 0 0 1-15.825455 13.730909h-201.076363a15.127273 15.127273 0 0 1-15.825455-13.730909v-42.356363a15.127273 15.127273 0 0 1 15.825455-13.730909z" fill="#3F58FD" opacity=".3" p-id="11506"></path><path d="M605.090909 804.538182h-90.298182A63.767273 63.767273 0 0 1 455.447273 768a50.734545 50.734545 0 0 1-5.818182-26.763636 186.181818 186.181818 0 0 0-27.927273-99.374546 181.992727 181.992727 0 0 0-24.669091-30.254545 415.883636 415.883636 0 0 1-49.803636-67.723637 378.181818 378.181818 0 0 1-33.28-66.56 226.443636 226.443636 0 0 1-11.403636-68.421818 298.123636 298.123636 0 0 1 7.214545-74.705454 266.24 266.24 0 0 1 46.545455-101.003637 319.767273 319.767273 0 0 1 119.621818-103.796363 291.374545 291.374545 0 0 1 65.163636-23.272728 331.170909 331.170909 0 0 1 81.687273-6.516363 281.134545 281.134545 0 0 1 170.356363 65.629091 338.850909 338.850909 0 0 1 72.378182 82.152727 279.272727 279.272727 0 0 1 37.003637 93.090909 276.014545 276.014545 0 0 1 5.12 80.058182 235.054545 235.054545 0 0 1-16.523637 67.490909 389.12 389.12 0 0 1-83.083636 128.232727 147.083636 147.083636 0 0 0-27.229091 42.123637 202.24 202.24 0 0 0-16.756364 48.872727c-1.629091 10.24-1.629091 20.48-2.56 30.487273a147.781818 147.781818 0 0 1-3.258181 23.272727 60.043636 60.043636 0 0 1-29.556364 35.374545 66.327273 66.327273 0 0 1-34.443636 9.076364z m-18.618182-626.734546c-9.774545 0-19.549091 1.861818-29.090909 3.490909a192.930909 192.930909 0 0 0-65.396363 23.272728A209.454545 209.454545 0 0 0 442.181818 243.665455a194.327273 194.327273 0 0 0-37.934545 55.854545 211.781818 211.781818 0 0 0-16.756364 82.850909 25.134545 25.134545 0 0 0 8.378182 19.781818 23.272727 23.272727 0 0 0 20.712727 5.818182 25.134545 25.134545 0 0 0 19.549091-23.272727V372.363636a144.523636 144.523636 0 0 1 8.610909-43.054545A164.538182 164.538182 0 0 1 502.458182 256a146.850909 146.850909 0 0 1 82.850909-30.021818l16.756364-1.396364a20.48 20.48 0 0 0 16.523636-13.730909 25.134545 25.134545 0 0 0 0-16.523636 20.48 20.48 0 0 0-18.152727-16.523637zM488.727273 861.090909h232.727272a18.152727 18.152727 0 0 1 18.152728 18.152727v56.32a18.385455 18.385455 0 0 1-18.152728 18.618182H488.727273a18.385455 18.385455 0 0 1-18.152728-18.385454v-56.785455A18.152727 18.152727 0 0 1 488.727273 861.090909z" fill="#3F58FD" p-id="11507"></path></svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

1
src/assets/icons/test.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744095285211" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9477" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M640 896v64h-256v-64H64v-64h320v-64h256v64h320v64H640zM64 64h896v640H64V64z m128 512h640V192H192v384z" fill="#0590DF" p-id="9478"></path></svg>

After

Width:  |  Height:  |  Size: 476 B

5
src/components/core/dynamic-table/src/components/table-action.vue

@ -7,6 +7,8 @@
v-bind="actionItem.popConfirm" v-bind="actionItem.popConfirm"
> >
<div class="flex items-center"> <div class="flex items-center">
<a-tooltip>
<template #title>{{ actionItem.title || actionItem.label }}</template>
<SvgIcon <SvgIcon
v-if="actionItem.icon" v-if="actionItem.icon"
:size="actionItem.size" :size="actionItem.size"
@ -25,8 +27,9 @@
> >
{{ actionItem.label }} {{ actionItem.label }}
</a-button> </a-button>
</a-tooltip>
<!-- <Divider type="vertical" class="action-divider" v-if="index < actionFilters.length - 1" /> -->
<Divider type="vertical" class="action-divider" v-if="index < actionFilters.length - 1" />
</div> </div>
</component> </component>
</template> </template>

3
src/enums/dictEnum.ts

@ -0,0 +1,3 @@
export enum DictEnum {
QUESTION_TYPE = '问题属性',
}

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

@ -9,4 +9,5 @@
export default { export default {
question: 'question', question: 'question',
issue: 'Issue', issue: 'Issue',
issueDetail: 'Issue Detail',
}; };

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

@ -11,4 +11,5 @@ export default {
dictionary: 'Dictionary', dictionary: 'Dictionary',
user: 'User', user: 'User',
admin: 'Admin', admin: 'Admin',
prodVersion: 'Production Version',
}; };

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

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

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

@ -3,4 +3,5 @@ export default {
dictionary: '字典维护', dictionary: '字典维护',
user: '用户管理', user: '用户管理',
admin: '管理员管理', admin: '管理员管理',
prodVersion: '产品版本管理',
}; };

12
src/router/staticModules/question.ts

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

11
src/router/staticModules/system.ts

@ -55,6 +55,17 @@ const routes: Array<RouteRecordRaw> = [
component: () => component: () =>
import(/* webpackChunkName: "system-admin" */ '@/views/system/admin/index.vue'), import(/* webpackChunkName: "system-admin" */ '@/views/system/admin/index.vue'),
}, },
{
path: 'prodVersion',
name: `${moduleName}-prodVersion`,
meta: {
title: t('routes.system.prodVersion'),
icon: 'icon-ziyuan',
keepAlive: true,
},
component: () =>
import(/* webpackChunkName: "system-prodVersion" */ '@/views/system/prodVersion/index.vue'),
},
], ],
}, },
]; ];

18
src/store/modules/user.ts

@ -1,7 +1,7 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { store } from '@/store'; import { store } from '@/store';
import { login } from '@/api/login';
import { login, getUserInfo } from '@/api/login';
import { ACCESS_TOKEN_KEY } from '@/enums/cacheEnum'; import { ACCESS_TOKEN_KEY } from '@/enums/cacheEnum';
import { ACCESS_TOKEN_NAME } from '@/enums/cacheEnum'; import { ACCESS_TOKEN_NAME } from '@/enums/cacheEnum';
import { Storage } from '@/utils/Storage'; import { Storage } from '@/utils/Storage';
@ -73,7 +73,7 @@ export const useUserStore = defineStore({
async login(params: API.LoginParams) { async login(params: API.LoginParams) {
try { try {
const res = await login(params); const res = await login(params);
console.log('res: ', res);
console.log('res2222: ', res);
this.setToken(res.tokenValue, res.tokenName); this.setToken(res.tokenValue, res.tokenName);
return this.afterLogin(); return this.afterLogin();
@ -86,10 +86,16 @@ export const useUserStore = defineStore({
async afterLogin() { async afterLogin() {
try { try {
// const [userInfo, { perms, menus }] = await Promise.all([getInfo(), permmenu()]); // const [userInfo, { perms, menus }] = await Promise.all([getInfo(), permmenu()]);
this.perms = perms;
this.name = userInfo.name;
this.avatar = userInfo.headImg;
this.userInfo = userInfo as any;
const userInfo = await getUserInfo();
console.log('userInfo: ', userInfo);
// this.perms = perms;
this.name = userInfo.account;
// this.avatar = userInfo.headImg;
this.userInfo = {
...userInfo,
name: userInfo.account,
username: userInfo.username,
};
// 生成路由 // 生成路由
const generatorResult = await generatorDynamicRouter([]); const generatorResult = await generatorDynamicRouter([]);
console.timeEnd('generatorDynamicRouter'); console.timeEnd('generatorDynamicRouter');

20
src/utils/dict.ts

@ -0,0 +1,20 @@
import { fetchDictValueListByType } from '@/api/dict';
export const getDictionaryByTypeName = async (name: string) => {
const res = await fetchDictValueListByType({ typeName: name });
if (res && Array.isArray(res) && res.length) {
return res
.filter((e) => e.enable === 1)
.map((e) => {
return {
label: e.dictValue,
value: e.dictValue,
};
});
}
};
export const getLabelByDictId = (
options: Array<{ label: string; value: string }>,
id: string,
) => {};

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

@ -1,8 +1,13 @@
import type { TableColumn } from '@/components/core/dynamic-table'; import type { TableColumn } from '@/components/core/dynamic-table';
import { stateTypeList } from './data'; import { stateTypeList } from './data';
import { Tag } from 'ant-design-vue'; import { Tag } from 'ant-design-vue';
import { DictEnum } from '@/enums/dictEnum';
import { getDictionaryByTypeName } from '@/utils/dict';
export type TableListItem = API.IssueType; export type TableListItem = API.IssueType;
export type TableColumnItem = TableColumn<TableListItem>; export type TableColumnItem = TableColumn<TableListItem>;
// const questionTypeList = await getDictionaryByTypeName(DictEnum.QUESTION_TYPE);
// 数据项类型 // 数据项类型
// export type ListItemType = typeof tableData[number]; // export type ListItemType = typeof tableData[number];
// 使用TableColumn<ListItemType> 将会限制dataIndex的类型,但换来的是dataIndex有类型提示 // 使用TableColumn<ListItemType> 将会限制dataIndex的类型,但换来的是dataIndex有类型提示
@ -30,11 +35,30 @@ export const baseColumns: TableColumnItem[] = [
required: false, required: false,
}, },
}, },
// {
// title: '问题属性',
// align: 'center',
// dataIndex: 'attribute',
// width: 150,
// formItemProps: {
// defaultValue: undefined,
// required: false,
// component: 'Select',
// componentProps: {
// options: questionTypeList,
// },
// },
// customRender: ({ record }) => {
// const label = questionTypeList?.find((e) => e.value === record.attribute)?.label;
// return <div>{{ label }}</div>;
// },
// },
{ {
title: '问题号', title: '问题号',
align: 'center', align: 'center',
dataIndex: 'billcode', dataIndex: 'billcode',
width: 150,
width: 200,
formItemProps: { formItemProps: {
defaultValue: '', defaultValue: '',
required: false, required: false,
@ -62,6 +86,17 @@ export const baseColumns: TableColumnItem[] = [
required: false, required: false,
}, },
}, },
{
title: '版本',
align: 'center',
dataIndex: 'version',
width: 150,
hideInSearch: true,
formItemProps: {
defaultValue: '',
required: false,
},
},
{ {
title: '代理商', title: '代理商',
align: 'center', align: 'center',

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

@ -0,0 +1,150 @@
<!--
功能功能描述
作者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> </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.message"
/>
</a-steps>
</a-tab-pane>
</a-tabs>
</a-spin>
</div>
</template>
<script lang="ts" setup>
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 loading = ref(false);
const historyList = ref<
Array<{
status: number;
message: 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);
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,
};
});
}
current.value = res?.state;
formRef?.setFieldsValue(res);
formRef?.clearValidate();
});
</script>
<style lang="less" scoped></style>

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

@ -5,32 +5,53 @@ import { message, Button } from 'ant-design-vue';
import { getFileExtension } from '@/utils/fileUtils'; import { getFileExtension } from '@/utils/fileUtils';
import { UploadOutlined } from '@ant-design/icons-vue'; import { UploadOutlined } from '@ant-design/icons-vue';
import { stateTypeList } from './data'; import { stateTypeList } from './data';
import { url } from 'inspector';
import component from '@/locales/lang/en-US/component';
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: ( export const getEditFormSchema: (
row?: Partial<TableListItem>, row?: Partial<TableListItem>,
isDetail?: boolean, isDetail?: boolean,
) => FormSchema[] = (record = {}, isDetail = false) => { ) => FormSchema[] = (record = {}, isDetail = false) => {
console.log('questionTypeList: ', questionTypeList);
return [ return [
{ {
field: 'title',
field: 'billcode',
component: 'Input', component: 'Input',
label: '问题标题',
label: '问题号',
dynamicDisabled: true,
colProps: { colProps: {
span: 12,
span: 24,
}, },
rules: [{ required: true }],
}, },
{ {
field: 'billcode',
field: 'title',
component: 'Input', component: 'Input',
label: '问题号',
componentProps: {
showCount: true,
maxlength: 150,
},
label: '问题标题',
colProps: { colProps: {
span: 12, span: 12,
}, },
rules: [{ required: true }],
}, },
// {
// label: '问题属性',
// field: 'arrtibute',
// component: 'Select',
// componentProps: {
// options: questionTypeList,
// },
// colProps: {
// span: 12,
// },
// rules: [{ required: true, type: 'string' }],
// },
{ {
field: 'customer', field: 'customer',
component: 'Input', component: 'Input',
@ -40,29 +61,70 @@ export const getEditFormSchema: (
}, },
}, },
{ {
field: 'product',
component: 'Input',
field: 'productId',
component: 'Select',
componentProps: ({ formModel, formInstance }) => {
return {
request: async () => {
const data = (await fetchProdList({})) as any;
return data.map((e) => {
return {
label: e.name,
value: e.id,
};
});
},
onChange: async (e) => {
console.log('e: ', e);
const { data } = await fetchVersionPageList({
productId: e,
current: 1,
size: 999,
});
if (data && Array.isArray(data) && data.length) {
formInstance?.setFieldsValue({
versionId: data[0].id,
});
formInstance.updateSchema({
field: 'versionId',
componentProps: {
options: data.map((e) => {
return {
label: e.name,
value: e.id,
};
}),
},
});
}
},
};
},
label: '产品', label: '产品',
colProps: { colProps: {
span: 12, span: 12,
}, },
}, },
{ {
field: 'agent',
component: 'Input',
label: '代理商',
field: 'versionId',
component: 'Select',
componentProps: {
options: [],
},
label: '版本',
colProps: { colProps: {
span: 12, span: 12,
}, },
}, },
{ {
field: 'attribute',
field: 'agent',
component: 'Input', component: 'Input',
label: '问题属性',
label: '代理商',
colProps: { colProps: {
span: 12, span: 12,
}, },
}, },
{ {
field: 'serviceNumber', field: 'serviceNumber',
component: 'Input', component: 'Input',
@ -94,6 +156,14 @@ export const getEditFormSchema: (
colProps: { colProps: {
span: 12, span: 12,
}, },
rules: [
{
required: false,
message: '请输入正确格式的电话号码',
pattern:
/^(1(3[0-9]|4[01456879]|5[0-3,5-9]|6[2567]|7[0-8]|8[0-9]|9[0-3,5-9])\d{8})$|^0\d{2,3}-?\d{7,8}$/,
},
],
}, },
{ {
field: 'state', field: 'state',
@ -113,6 +183,8 @@ export const getEditFormSchema: (
componentProps: { componentProps: {
rows: 4, rows: 4,
placeholder: '请输入问题描述', placeholder: '请输入问题描述',
showCount: true,
maxlength: 150,
}, },
label: '问题描述', label: '问题描述',
colProps: { colProps: {
@ -120,9 +192,10 @@ export const getEditFormSchema: (
}, },
rules: [{ required: true }], rules: [{ required: true }],
}, },
{ {
label: '描述附件', label: '描述附件',
field: 'fileList',
field: 'files',
component: 'Upload', component: 'Upload',
componentProps: { componentProps: {
disabled: isDetail, disabled: isDetail,
@ -138,7 +211,7 @@ export const getEditFormSchema: (
console.log('file: ', file); console.log('file: ', file);
// 限制允许上传的文件类型 // 限制允许上传的文件类型
const allowedTypes = ['xlsx', 'xls', 'doc', 'docx', 'pdf'];
const allowedTypes = ['xlsx', 'xls', 'doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png'];
// 检查文件类型 // 检查文件类型
const fileType = getFileExtension(file.name) || 'unknown'; const fileType = getFileExtension(file.name) || 'unknown';
@ -167,5 +240,27 @@ export const getEditFormSchema: (
}, },
rules: [{ required: false }], rules: [{ required: false }],
}, },
{
label: '解决方案',
field: 'solution',
colProps: {
span: 24,
},
// rules: [{ required: true, type: 'array' }],
slot: 'solution',
},
] as any; ] as any;
}; };
export const getFlowFormSchema: (row?: Partial<TableListItem>) => FormSchema[] = (record = {}) => {
return [
{
field: 'remark',
component: 'InputTextArea',
label: '文字补充内容',
colProps: {
span: 24,
},
},
];
};

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

@ -31,7 +31,7 @@
</Alert> </Alert>
</template> </template>
<template #toolbar> <template #toolbar>
<a-button type="primary" @click="openIssueModal({})">新增</a-button>
<a-button type="primary" @click="handleAdd">新增</a-button>
<a-button <a-button
type="danger" type="danger"
:disabled="!isCheckRows" :disabled="!isCheckRows"
@ -41,10 +41,29 @@
</a-button> </a-button>
</template> </template>
</DynamicTable> </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> </template>
<script lang="tsx" setup> <script lang="tsx" setup>
// import { QuillEditor } from '@vueup/vue-quill';
import { QuillEditor } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css'; import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { type TableListItem, baseColumns } from './columns'; import { type TableListItem, baseColumns } from './columns';
import { useTable, type OnChangeCallbackParams } from '@/components/core/dynamic-table'; import { useTable, type OnChangeCallbackParams } from '@/components/core/dynamic-table';
@ -54,33 +73,82 @@
updateIssue, updateIssue,
deleteIssueById, deleteIssueById,
deleteBatchIssueById, deleteBatchIssueById,
findOneById,
updateIssueState,
} from '@/api/issue'; } from '@/api/issue';
import { computed, ref } from 'vue';
import { computed, nextTick, ref } from 'vue';
import { Modal, message, Alert } from 'ant-design-vue'; import { Modal, message, Alert } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import { useFormModal } from '@/hooks/useModal/index'; import { useFormModal } from '@/hooks/useModal/index';
import { getEditFormSchema } from './formSchemas';
import { getEditFormSchema, getFlowFormSchema } from './formSchemas';
import { ExclamationCircleOutlined, DeleteOutlined } from '@ant-design/icons-vue'; 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({ defineOptions({
name: 'issue', name: 'issue',
}); });
// const content = ref('');
// const editorOptions = {
// theme: 'snow', //
// modules: {
// toolbar: [
// [{ header: '1' }, { header: '2' }],
// ['bold', 'italic'],
// ['link'],
// ['blockquote', 'code-block'],
// ],
// },
// };
const router = useRouter();
const curRecord = ref<Partial<TableListItem>>({});
const visible = ref(false);
const content = ref('222');
const [showModal] = useFormModal(); 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 = [ const columns: any = [
...baseColumns, ...baseColumns,
{ {
@ -90,20 +158,71 @@
hideInSearch: true, hideInSearch: true,
align: 'center', align: 'center',
fixed: 'right', fixed: 'right',
actions: ({ record }) => [
actions: ({ record }) => {
const { state } = record;
const stateText = stateTypeList.find((item) => item.value === state)?.label;
return [
{ {
icon: 'searchoutlined', icon: 'searchoutlined',
color: '#3b82f6', color: '#3b82f6',
label: '查看', label: '查看',
onClick: () => handleShow(record),
onClick: () => handleView(record),
}, },
{ {
icon: 'edit', icon: 'edit',
color: '#3b82f6', color: '#3b82f6',
size: '15', size: '15',
label: '修改', label: '修改',
ifShow: stateText !== '结束',
onClick: () => handleEdit(record), 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', icon: 'delete',
color: '#ec6f6f', color: '#ec6f6f',
@ -113,9 +232,14 @@
onConfirm: () => handleDelete(record.id), onConfirm: () => handleDelete(record.id),
}, },
}, },
],
];
},
}, },
]; ];
const changeState = async (record: Partial<TableListItem>, state: number) => {
await openFlowModal(record, state);
};
/** /**
* @description 打开问题工单弹窗 * @description 打开问题工单弹窗
*/ */
@ -128,8 +252,8 @@
console.log('新增/编辑问题工单', values); console.log('新增/编辑问题工单', values);
values.id = record.id; values.id = record.id;
if (values.fileList && Array.isArray(values.fileList) && values.fileList.length) {
values.fileList = values.fileList.map((e) => {
if (values.files && Array.isArray(values.files) && values.files.length) {
values.files = values.files.map((e) => {
if (e.response) { if (e.response) {
return { return {
name: e.name, name: e.name,
@ -141,10 +265,9 @@
...e, ...e,
}; };
}); });
values.fileIds = values.files.map((e) => e.id).join(',');
} }
values.fileIds = values.fileList.map((e) => e.id).join(',');
await (record.id ? updateIssue : createIssue)(values); await (record.id ? updateIssue : createIssue)(values);
message.success(`${record.id ? '编辑' : '新增'}成功`); message.success(`${record.id ? '编辑' : '新增'}成功`);
dynamicTableInstance?.reload(); dynamicTableInstance?.reload();
@ -160,17 +283,126 @@
}); });
formRef?.setFieldsValue(record); formRef?.setFieldsValue(record);
// if (record?.id) {
// const { roles } = await getIssueInfo({ issueId: record.id });
// formRef?.setFieldsValue({ roles });
// }
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,
}; };
}),
},
});
}
}
const handleEdit = (record: TableListItem) => {
openIssueModal(record);
nextTick(() => {
openModal();
});
}
}; };
const handleShow = (record: TableListItem) => {
const handleShow = async (record: TableListItem) => {
openIssueModal(record, true); openIssueModal(record, true);
}; };
@ -234,32 +466,34 @@
// }, // },
// }, // },
// ]); // ]);
return new Promise((resolve) => {
setTimeout(() => {
resolve({
list: [
{
id: '1',
name: '1',
state: 0,
fileList: [
{
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);
});
// 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({ const res = await fetchIssuePageList({
...params, ...params,
current: params.page, current: params.page,
@ -281,8 +515,13 @@
}; };
</script> </script>
<style lang="less" scoped>
<style lang="less" >
.action-divider { .action-divider {
margin: 0 5px; margin: 0 5px;
} }
.ql-box {
.ql-editor {
height: 400px !important;
}
}
</style> </style>

325
src/views/system/prodVersion/Tree.vue

@ -0,0 +1,325 @@
<template>
<div class="bg-white m-4 mr-2 overflow-hidden h-full">
<BasicTree
ref="treeRef"
:actionList="actionList"
:beforeRightClick="getRightMenuList"
:checkable="false"
:clickRowToExpand="false"
:treeData="treeData"
checkStrictly
:defaultExpandAll="true"
search
toolbar
:highlight="true"
@select="handleSelect"
:toolbarStrictly="false"
>
<!-- <template #titleBefore="item"> </template> -->
</BasicTree>
<div v-if="query" class="m-4 flex justify-center">
<a-button class="w-[80%]" type="primary" @click="handleReset()">重置</a-button>
<!-- <Checkbox v-model:checked="recursion" @change="handleQuery()">本级及子级</Checkbox> -->
</div>
<div v-else class="m-4 flex justify-center">
<a-button type="primary" class="w-[80%]" @click="openTypeModal({})">
{{ '新增产品' }}
</a-button>
<!-- <a-button
v-permission="{ action: RoleEnum.DICT_DELETE, effect: 'disabled' }"
class="mr-2"
@click="handleBatchDelete()"
>
{{ '删除' }}
</a-button> -->
</div>
<!-- <OrgRole @register="registerModal" @success="handleSuccess" /> -->
</div>
</template>
<script lang="tsx">
import { defineComponent, h, onMounted, ref, unref } from 'vue';
import { Checkbox, message, Tag } from 'ant-design-vue'; // antd
import { useI18n } from '@/hooks/useI18n';
import {
BasicTree,
ContextMenuItem,
TreeActionItem,
TreeActionType,
TreeItem,
} from '@/components/core/Tree';
import { eachTree, findChildrenByParentId, findNodeByKey } from '@/utils/helper/treeHelper';
import { RoleEnum } from '@/enums/roleEnum';
import { OrgTypeEnum } from '@/enums/biz/base';
import { useFormModal } from '@/hooks/useModal/index';
import { createProd, deleteProd, fetchProdList, updateProd } from '@/api/prodVersion';
// import { useModal } from '@/components/Modal';
// import OrgRole from './orgRole/index.vue';
import { useMessage } from '@/hooks/useMessage';
import { mockTree } from './mockData';
import { Nullable } from '@/utils/types';
import { prodFormSchema } from './formSchemas';
import { TableListItem } from './columns';
import { omit } from 'lodash-es';
import { isArray } from '@/utils/is';
export default defineComponent({
name: 'BaseProdValueManagement',
components: {
BasicTree,
// OrgRole,
Checkbox,
Tag,
},
props: {
query: {
type: Boolean,
default: false,
},
},
emits: ['select', 'add', 'edit', 'change', 'reset'],
setup(props, { emit }) {
const { t } = useI18n();
const { createMessage, createConfirm } = useMessage();
const treeRef = ref<Nullable<TreeActionType>>(null);
const treeData = ref<TreeItem[]>([]);
const recursion = ref<boolean>(false);
//
// const [registerModal, { openModal }] = useModal();
const [showModal] = useFormModal();
/**
* @description 打开弹窗
*/
const openTypeModal = async (record: Partial<TableListItem> = {}, isReadOnly = false) => {
const [formRef] = await showModal<any>({
modalProps: {
title: `${isReadOnly ? '查看' : record.id ? '编辑' : '新增'}产品`,
width: 500,
onFinish: async (values) => {
values.id = record.id;
await (record.id ? updateProd : createProd)(values);
message.success(`${record.id ? '编辑' : '新增'}成功`);
fetch();
},
},
formProps: {
labelWidth: 100,
schemas: prodFormSchema,
autoSubmitOnEnter: true,
},
});
formRef?.setFieldsValue(record);
};
function getTree() {
const tree = unref(treeRef);
if (!tree) {
throw new Error('树结构加载失败,请刷新页面');
}
return tree;
}
onMounted(() => {
fetch();
});
//
async function fetch() {
const types = (await fetchProdList({})) as unknown as TreeItem[];
// const types = mockTree;
console.log('types: ', types);
treeData.value = types.map((e) => {
return {
...e,
name: e.name,
editable: true,
};
}) as unknown as TreeItem[];
eachTree(treeData.value, (item) => {
item.key = item.id;
item.title = item.name;
item.slots = { titleBefore: 'titleBefore' };
return item;
});
console.log('treeData.value: ', treeData.value);
console.log('[treeData.value[0].id]: ', [treeData.value[0].id]);
if (isArray(treeData.value) && treeData.value.length > 0) {
getTree().setSelectedKeys([treeData.value[0].id]);
handleSelect([treeData.value[0].id]);
}
// setTimeout(() => {
// getTree().filterByLevel(2);
// }, 0);
}
//
function handleSelect(keys: string[]) {
console.log('keys: ', keys);
if (keys[0]) {
const node = findNodeByKey(keys[0], treeData.value);
const parent = findNodeByKey(node?.parentId, treeData.value);
let childrenIds: string[] = [];
if (recursion.value) {
childrenIds = findChildrenByParentId(keys[0], treeData.value);
} else {
childrenIds = [node.id];
}
emit('select', parent, node, childrenIds);
} else {
emit('select', {}, {});
}
}
let actionList: TreeActionItem[] = [];
let getRightMenuList = (_: any): ContextMenuItem[] => {
return [];
};
if (!props.query) {
//
actionList = [
{
render: (node) => {
if (node.editable) {
return h(
'a',
{
class: 'ml-2',
onClick: (e: Event) => {
e?.stopPropagation();
e?.preventDefault();
const current = findNodeByKey(node?.id, treeData.value);
// const parent = findNodeByKey(node?.parentId, treeData.value);
// emit('edit', parent, current);
openTypeModal(current);
},
},
'编辑',
);
}
},
},
{
render: (node) => {
if (node.editable) {
return h(
'a',
{
class: 'ml-2',
onClick: (e: Event) => {
e?.stopPropagation();
e?.preventDefault();
batchDelete(node.id);
},
},
'删除',
);
}
},
},
];
//
// getRightMenuList = (node: any): ContextMenuItem[] => {
// return [
// {
// label: '',
// auth: RoleEnum.DICT_EDIT,
// handler: () => {
// const current = findNodeByKey(unref(node)?.id, treeData.value);
// const parent = findNodeByKey(unref(node)?.parentId, treeData.value);
// emit('edit', parent, current);
// },
// },
// {
// label: '',
// auth: RoleEnum.DICT_DELETE,
// handler: () => {
// batchDelete([unref(node).id]);
// },
// },
// ];
// };
}
//
async function batchDelete(id: string) {
createConfirm({
iconType: 'warning',
content: '选中节点及其子结点将被永久删除, 是否确定删除?',
onOk: async () => {
try {
await deleteProd({ id });
createMessage.success(t('common.tips.deleteSuccess'));
fetch();
} catch (e) {}
},
});
}
//
function handleAdd() {
emit('add', findNodeByKey('0', treeData.value));
}
//
// function handleBatchDelete() {
// const { checked } = getTree().getCheckedKeys() as {
// checked: string[];
// halfChecked: string[];
// };
// if (!checked || checked.length <= 0) {
// createMessage.warning(t('common.tips.pleaseSelectTheData'));
// return;
// }
// batchDelete(checked);
// }
//
function changeDisplay() {
emit('change', '2');
}
//
function handleReset() {
getTree().setSelectedKeys([]);
emit('reset');
}
//
function handleQuery() {
handleSelect(getTree().getSelectedKeys() as string[]);
}
//
function handleSuccess() {
fetch();
}
return {
t,
treeRef,
treeData,
fetch,
handleAdd,
// handleBatchDelete,
getRightMenuList,
actionList,
handleSelect,
changeDisplay,
handleReset,
handleQuery,
RoleEnum,
// registerModal,
handleSuccess,
recursion,
OrgTypeEnum,
openTypeModal,
};
},
});
</script>

30
src/views/system/prodVersion/columns.tsx

@ -0,0 +1,30 @@
/*
* @Author: AaronWu 2463371514@qq.com
* @Date: 2025-04-01 13:43:34
* @LastEditors: AaronWu 2463371514@qq.com
* @LastEditTime: 2025-04-01 13:52:02
* @FilePath: /IssueSupportManage/src/views/system/dictionary/columns.tsx
* @Description: ,`customMade`, koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import type { TableColumn } from '@/components/core/dynamic-table';
export type TableListItem = API.VersionType;
export type TableColumnItem = TableColumn<TableListItem>;
// 数据项类型
// export type ListItemType = typeof tableData[number];
// 使用TableColumn<ListItemType> 将会限制dataIndex的类型,但换来的是dataIndex有类型提示
export const baseColumns: TableColumnItem[] = [
{
title: '版本名称',
align: 'center',
dataIndex: 'name',
width: 150,
ellipsis: true,
resizable: true,
formItemProps: {
defaultValue: '',
required: false,
},
},
];

48
src/views/system/prodVersion/formSchemas.ts

@ -0,0 +1,48 @@
import type { FormSchema } from '@/components/core/schema-form/';
// 列表编辑页字段
export const editFormSchema: FormSchema[] = [
{
field: 'id',
label: 'ID',
component: 'Input',
vShow: false,
},
{
label: '版本名称',
field: 'name',
component: 'Input',
colProps: {
span: 24,
},
rules: [{ required: true }],
},
{
label:'备注',
field: 'remark',
component: 'InputTextArea',
colProps: {
span: 24,
},
rules: [{ required: false }],
}
];
// 新增产品表单
export const prodFormSchema: FormSchema[] = [
{
field: 'id',
label: 'ID',
component: 'Input',
vShow: false,
},
{
label: '名称',
field: 'name',
component: 'Input',
colProps: {
span: 24,
},
rules: [{ required: true }],
},
];

241
src/views/system/prodVersion/index.vue

@ -0,0 +1,241 @@
<!--
* @Author: AaronWu 2463371514@qq.com
* @Date: 2025-04-01 13:17:38
* @LastEditors: AaronWu 2463371514@qq.com
* @LastEditTime: 2025-04-01 14:43:11
* @FilePath: /IssueSupportManage/src/views/system/dictionary/index.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<SplitPanel>
<template #left-content>
<BaseTypeTree ref="treeRef" class="" @reset="handleReset" @select="handleTypeSelect" />
</template>
<template #right-content>
<DynamicTable
search-type="drawer"
size="small"
showIndex
headerTitle="版本列表"
titleTooltip=""
:data-request="loadData"
:columns="columns"
row-key="id"
@resize-column="handleResizeColumn"
:row-selection="rowSelection"
>
<template v-if="isCheckRows" #[`title`]>
<Alert class="w-full" type="info" show-icon>
<template #message>
已选 {{ rowSelection.selectedRowKeys.length }}
<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"
v-if="isCheckRows"
@click="delRowConfirm(rowSelection.selectedRowKeys)"
>
<DeleteOutlined /> 删除
</a-button>
</template>
</DynamicTable>
</template>
</SplitPanel>
</template>
<script lang="tsx" setup>
import { computed, h, ref } from 'vue';
import { SplitPanel } from '@/components/basic/split-panel';
import BaseTypeTree from './Tree.vue';
import { OnChangeCallbackParams, useTable } from '@/components/core/dynamic-table';
import { baseColumns } from './columns';
import { message, Modal, Alert } from 'ant-design-vue';
import { ExclamationCircleOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import { useFormModal } from '@/hooks/useModal';
import { editFormSchema } from './formSchemas';
import {
createVersion,
deleteBatchVersionById,
fetchVersionPageList,
updateVersion,
} from '@/api/prodVersion';
const columns: any = [
...baseColumns,
{
title: '操作',
width: 50,
dataIndex: 'ACTION',
hideInSearch: true,
align: 'center',
fixed: 'right',
actions: ({ record }) => [
{
color: '#3b82f6',
size: '15',
label: '编辑',
onClick: () => handleEdit(record),
},
{
color: '#ec6f6f',
label: '删除',
onClick: () => handleDelete(record.id),
},
],
},
];
const treeRef = ref<InstanceType<typeof BaseTypeTree>>();
const [showModal] = useFormModal();
const currentType = ref<API.DictType>({
id: '',
name: '',
});
const [DynamicTable, dynamicTableInstance] = useTable();
function handleTypeSelect(_parent = {}, record: API.DictType, childrenIds = []) {
console.log('record: ', record);
currentType.value = record;
dynamicTableInstance?.reload();
}
function handleReset() {
currentType.value = {
id: '',
name: '',
};
dynamicTableInstance?.reload();
}
/**
* @description 表格删除行
*/
const delRowConfirm = async (ids: string[]) => {
Modal.confirm({
title: '确定要删除所选的版本吗?',
icon: <ExclamationCircleOutlined />,
centered: true,
onOk: async () => {
await deleteBatchVersionById(ids).finally(dynamicTableInstance?.reload);
},
});
};
const handleAdd = () => {
// openDrawer(true, { type: ActionEnum.ADD, record: {} });
// modal
openModal({});
};
const handleEdit = (record: API.VersionType) => {
if (record.enable === 1) {
message.warning('启用状态不可编辑');
return;
}
console.log('record: ', record);
//modal
openModal(record);
};
const handleDelete = (id: string) => {
delRowConfirm([id]);
};
/**
* @description 打开弹窗
*/
const openModal = async (record: Partial<API.VersionType> = {}, isReadOnly = false) => {
if (!currentType.value.id) {
message.warning('请先选择左侧产品');
return;
}
const [formRef] = await showModal<any>({
modalProps: {
title: `${isReadOnly ? '查看' : record.id ? '编辑' : '新增'}版本`,
width: 700,
onFinish: async (values) => {
console.log('新增/编辑版本', values);
values.id = record.id;
values.productId = currentType.value.id;
await (record.id ? updateVersion : createVersion)(values);
message.success(`${record.id ? '编辑' : '新增'}成功`);
dynamicTableInstance?.reload();
},
footer: isReadOnly ? null : undefined,
},
formProps: {
labelWidth: 100,
schemas: editFormSchema,
autoSubmitOnEnter: true,
disabled: isReadOnly,
},
});
formRef?.setFieldsValue(record);
// if (record?.id) {
// const { roles } = await getTenantInfo({ employeeId: record.id });
// formRef?.setFieldsValue({ roles });
// }
};
const rowSelection = ref<any>({
selectedRowKeys: [] as string[],
onChange: (selectedRowKeys: string[], selectedRows: API.VersionType[]) => {
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);
if (!currentType.value.id) {
return {
list: [],
pagination: {
total: 0,
size: params.limit,
...params,
},
};
}
const res = await fetchVersionPageList({
...params,
productId: currentType.value.id,
current: params.page,
size: params.limit,
});
console.log('res: ', res);
rowSelection.value.selectedRowKeys = [];
return {
list: res.data,
pagination: {
total: Number(res.data.total),
...params,
},
};
};
const handleResizeColumn = (w, col) => {
// console.log('w', w, col);
col.width = w;
};
</script>
<style lang="less" scoped></style>

14
src/views/system/prodVersion/mockData.ts

@ -0,0 +1,14 @@
/*
* @Author: AaronWu 2463371514@qq.com
* @Date: 2025-04-01 13:24:31
* @LastEditors: AaronWu 2463371514@qq.com
* @LastEditTime: 2025-04-01 13:25:12
* @FilePath: /IssueSupportManage/src/views/system/dictionary/mockData.ts
* @Description: ,`customMade`, koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
export const mockTree: any[] = [
{
id: 1,
name: '字典类别',
},
];
Loading…
Cancel
Save