Browse Source

feat: 知识库详情添加翻页功能

master
AaronWu 3 months ago
parent
commit
fb01325d8a
  1. 11
      src/api/user/index.ts
  2. 1
      src/assets/icons/resetPwd.svg
  3. 2
      src/utils/issueCache.ts
  4. 16
      src/views/client/issue/index.vue
  5. 382
      src/views/question/knowledge/detail.vue
  6. 32
      src/views/question/knowledge/index.vue
  7. 51
      src/views/system/user/index.vue
  8. 2
      vite.config.ts

11
src/api/user/index.ts

@ -135,3 +135,14 @@ export function deleteBatchUserById(data: API.DeleteBatchUserParams) {
data, data,
}); });
} }
/**
* @description
*/
export function resetUserPassword(params: { id: string }) {
return request({
url: `user/reset`,
method: 'post',
data: params,
});
}

1
src/assets/icons/resetPwd.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="1757572002300" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5392" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 23.04a257.6 257.6 0 0 1 257.6 257.6V384H864a68.64 68.64 0 0 1 68.64 68.64v461.76A68.64 68.64 0 0 1 864 983.04H160a68.64 68.64 0 0 1-68.64-68.64V452.64A68.64 68.64 0 0 1 160 384h93.12v-103.36A257.6 257.6 0 0 1 512 23.04z m342.88 438.08H169.12v444.8h685.76V461.28z m-258.72 76.16l8 5.12a34.4 34.4 0 0 1 28.32-16 34.24 34.24 0 0 1 34.88 33.6v64a34.24 34.24 0 0 1-33.76 34.24h-64.8a34.24 34.24 0 0 1-13.12-65.92l6.08 3.36a99.68 99.68 0 1 0 35.68 137.44v-1.44A34.24 34.24 0 0 1 657.6 768a168.16 168.16 0 1 1-61.44-229.92zM512 100.16a180.64 180.64 0 0 0-180.96 180.48V384h360.96v-103.36A180.48 180.48 0 0 0 512 100.16z" fill="#4592D8" p-id="5393"></path></svg>

After

Width:  |  Height:  |  Size: 992 B

2
src/utils/issueCache.ts

@ -27,7 +27,7 @@ export const saveIssueFormCache = (formData: Partial<IssueFormCache>) => {
'customer', 'customer',
'productId', 'productId',
'versionId', 'versionId',
'appVersion',
// 'appVersion',
'agent' 'agent'
] as const; ] as const;

16
src/views/client/issue/index.vue

@ -225,13 +225,13 @@
handleAdd(); handleAdd();
}, },
}, },
// {
// title: '',
// icon: EditOutlined,
// onClick: () => {
// handleEdit(curRow.value);
// },
// },
{
title: '编辑工单',
icon: EditOutlined,
onClick: () => {
handleEdit(curRow.value);
},
},
// { // {
// title: '', // title: '',
// icon: DeleteOutlined, // icon: DeleteOutlined,
@ -357,7 +357,7 @@
cachedData.customer = res.customer; cachedData.customer = res.customer;
cachedData.productId = res.productId; cachedData.productId = res.productId;
cachedData.versionId = res.versionId; cachedData.versionId = res.versionId;
cachedData.appVersion = res.appVersion;
// cachedData.appVersion = res.appVersion;
cachedData.agent = res.agent; cachedData.agent = res.agent;
} }

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

@ -9,26 +9,76 @@
修改时间 修改时间
--> -->
<template> <template>
<div class="bg-[#fff] p-5">
<a-spin :spinning="loading">
<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-tabs>
</a-spin>
<div class="bg-[#fff] p-5 relative">
<!-- 查询条件卡片 -->
<a-card v-if="hasQueryParams" class="query-params-card mb-4" :bordered="false">
<template #title>
<div class="query-title">
<SearchOutlined class="title-icon" />
<span>查询条件</span>
</div>
</template>
<div class="query-tags">
<a-tag v-if="queryParams.title" color="blue" class="query-tag">
<span class="tag-label">标题</span>
<span class="tag-value">{{ queryParams.title }}</span>
</a-tag>
<a-tag v-if="queryParams.description" color="cyan" class="query-tag">
<span class="tag-label">描述</span>
<span class="tag-value">{{ queryParams.description }}</span>
</a-tag>
<a-tag v-if="queryParams.createTime" color="orange" class="query-tag">
<span class="tag-label">创建时间</span>
<span class="tag-value">{{ formatDateRange(queryParams.createTime) }}</span>
</a-tag>
</div>
</a-card>
<div class="flex justify-between items-center">
<!-- 上一页按钮 -->
<a-button
type="primary"
shape="circle"
size="large"
class="nav-button prev-button"
:disabled="!prevId"
@click="handlePrev"
>
<template #icon>
<LeftOutlined />
</template>
</a-button>
<a-spin :spinning="loading">
<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-tabs>
</a-spin>
<a-button
type="primary"
shape="circle"
size="large"
class="nav-button next-button"
:disabled="!nextId"
@click="handleNext"
>
<template #icon>
<RightOutlined />
</template>
</a-button>
</div>
</div> </div>
</template> </template>
@ -38,10 +88,11 @@
import { useForm } from '@/components/core/schema-form'; import { useForm } from '@/components/core/schema-form';
import { getEditFormSchema } from './formSchemas'; import { getEditFormSchema } from './formSchemas';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { onMounted, ref } from 'vue';
import { findOneById } from '@/api/knowledgeBase';
import { onMounted, ref, computed, watch } from 'vue';
import { findOneById, fetchKnowledgeBasePageList } from '@/api/knowledgeBase';
import { SvgIcon } from '@/components/basic/svg-icon'; import { SvgIcon } from '@/components/basic/svg-icon';
import { stateTypeList } from './data'; import { stateTypeList } from './data';
import { LeftOutlined, RightOutlined, SearchOutlined } from '@ant-design/icons-vue';
const route = useRoute(); const route = useRoute();
const editorOptions = ref({ const editorOptions = ref({
theme: 'snow', theme: 'snow',
@ -66,7 +117,47 @@
const current = ref(0); const current = ref(0);
const { id } = route.query;
// id watch
const currentId = ref<string | undefined>(route.query.id as string | undefined);
// id
const prevId = ref<string | null>(null);
const nextId = ref<string | null>(null);
// id- route.query
const queryParams = computed(() => {
const params: Record<string, string> = {};
Object.keys(route.query).forEach((key) => {
// id
if (
key !== 'id' &&
route.query[key] !== undefined &&
route.query[key] !== null &&
route.query[key] !== ''
) {
params[key] = String(route.query[key]);
}
});
return params;
});
//
const hasQueryParams = computed(() => {
return Object.keys(queryParams.value).length > 0;
});
//
const formatDateRange = (dateStr: string) => {
if (!dateStr) return '';
//
if (dateStr.includes(',')) {
const dates = dateStr.split(',');
if (dates.length === 2) {
return `${dates[0]}${dates[1]}`;
}
}
return dateStr;
};
const [SchemaForm, formRef] = useForm({ const [SchemaForm, formRef] = useForm({
labelWidth: 150, labelWidth: 150,
@ -81,35 +172,232 @@
disabled: true, 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,
};
//
const loadListData = async () => {
try {
//
const searchParams: any = {
current: 1,
size: 1000, // 便
};
// - route.query
const currentQuery = route.query;
if (currentQuery.title) {
searchParams.title = String(currentQuery.title);
}
if (currentQuery.description) {
searchParams.description = String(currentQuery.description);
}
if (currentQuery.createTime) {
const dateStr = String(currentQuery.createTime);
if (dateStr.includes(',')) {
const dates = dateStr.split(',');
if (dates.length === 2) {
searchParams.startDate = dates[0];
searchParams.endDate = dates[1];
}
}
}
//
Object.keys(currentQuery).forEach((key) => {
if (key !== 'id' && key !== 'title' && key !== 'description' && key !== 'createTime') {
const value = currentQuery[key];
if (value !== undefined && value !== null && value !== '') {
searchParams[key] = String(value);
}
}
}); });
console.log('加载列表数据,查询参数:', searchParams);
const res = await fetchKnowledgeBasePageList(searchParams);
const list = res.data?.records || [];
console.log('获取到的列表数据,总数:', list.length);
//
const currentIdValue = currentId.value || (route.query.id as string);
const currentIndex = list.findIndex((item: any) => item.id == currentIdValue);
console.log('当前记录索引:', currentIndex, '当前ID:', currentIdValue);
if (currentIndex !== -1) {
// id
prevId.value = currentIndex > 0 ? list[currentIndex - 1].id : null;
nextId.value = currentIndex < list.length - 1 ? list[currentIndex + 1].id : null;
console.log('上一页ID:', prevId.value, '下一页ID:', nextId.value);
} else {
//
prevId.value = null;
nextId.value = null;
console.warn('未找到当前记录在列表中');
}
} catch (error) {
console.error('获取列表数据失败:', error);
prevId.value = null;
nextId.value = null;
} }
};
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,
};
});
//
const loadDetailData = async (targetId: string) => {
if (!targetId) {
return;
} }
current.value = res?.state;
currentId.value = targetId;
loading.value = true;
try {
const res = await findOneById({ id: targetId });
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,
};
});
} else {
historyList.value = [];
}
current.value = res?.state;
formRef?.setFieldsValue(res);
formRef?.clearValidate();
formRef?.setFieldsValue(res);
formRef?.clearValidate();
//
await loadListData();
} catch (error) {
console.error('加载详情数据失败:', error);
} finally {
loading.value = false;
}
};
//
const handlePrev = () => {
if (prevId.value) {
loadDetailData(prevId.value);
}
};
//
const handleNext = () => {
if (nextId.value) {
loadDetailData(nextId.value);
}
};
onMounted(async () => {
const currentIdValue = route.query.id as string;
if (!currentIdValue) {
console.warn('未找到 id 参数');
return;
}
await loadDetailData(currentIdValue);
}); });
//
watch(
() => route.query.id,
async (newId) => {
if (newId && newId !== currentId.value) {
await loadDetailData(newId as string);
}
},
);
</script> </script>
<style lang="less" scoped></style>
<style lang="less" scoped>
.query-params-card {
background: linear-gradient(135deg, #f8fafc 0%, #ffffff 100%);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 1px solid #e2e8f0;
transition: all 0.3s ease;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.query-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 600;
color: #1e293b;
.title-icon {
color: #3b82f6;
font-size: 16px;
}
}
.query-tags {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 8px;
.query-tag {
padding: 6px 16px;
border-radius: 6px;
font-size: 14px;
border: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.tag-label {
font-weight: 500;
margin-right: 4px;
opacity: 0.8;
}
.tag-value {
font-weight: 600;
}
}
}
:deep(.ant-card-head) {
border-bottom: 1px solid #e2e8f0;
padding: 12px 16px;
min-height: auto;
}
:deep(.ant-card-body) {
padding: 16px;
}
}
.nav-button {
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition: all 0.3s;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
&.next-button {
left: 10px;
}
}
</style>

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

@ -351,11 +351,37 @@
}; };
const handleView = (record: TableListItem) => { const handleView = (record: TableListItem) => {
//
const queryFormRef = dynamicTableInstance?.getQueryFormRef();
const queryParams: Record<string, any> = {
id: record.id,
};
if (queryFormRef) {
const formValues = queryFormRef.getFieldsValue();
//
Object.keys(formValues).forEach((key) => {
const value = formValues[key];
// nullundefined
if (
value !== undefined &&
value !== null &&
value !== '' &&
!(Array.isArray(value) && value.length === 0)
) {
//
if (Array.isArray(value)) {
queryParams[key] = value.join(',');
} else {
queryParams[key] = value;
}
}
});
}
router.push({ router.push({
path: '/question/knowledgeDetail', path: '/question/knowledgeDetail',
query: {
id: record.id,
},
query: queryParams,
}); });
}; };

51
src/views/system/user/index.vue

@ -54,6 +54,7 @@
deleteUserById, deleteUserById,
deleteBatchUserById, deleteBatchUserById,
updateAuditState, updateAuditState,
resetUserPassword,
} from '@/api/user'; } from '@/api/user';
import { computed, reactive, ref, toRaw } from 'vue'; import { computed, reactive, ref, toRaw } from 'vue';
import { Modal, message, Alert } from 'ant-design-vue'; import { Modal, message, Alert } from 'ant-design-vue';
@ -99,6 +100,12 @@
vShow: record.auditState === 0, vShow: record.auditState === 0,
onClick: () => handleAudit(record), onClick: () => handleAudit(record),
}, },
{
icon: 'resetPwd',
color: '#f59e0b',
label: '重置密码',
onClick: () => handleResetPassword(record),
},
{ {
icon: 'delete', icon: 'delete',
color: '#ec6f6f', color: '#ec6f6f',
@ -152,6 +159,50 @@
dynamicTableInstance?.reload(); dynamicTableInstance?.reload();
}; };
const handleResetPassword = async (record: TableListItem) => {
const { id, username } = record;
if (!id) return;
Modal.confirm({
title: '重置密码',
content: `确定要重置用户 "${username}" 的密码吗?`,
icon: <ExclamationCircleOutlined />,
centered: true,
onOk: async () => {
try {
const res = await resetUserPassword({ id });
const newPassword = res;
//
try {
await navigator.clipboard.writeText(newPassword);
Modal.success({
title: '重置密码成功',
content: `新密码为:${newPassword}(已复制到剪切板)`,
centered: true,
});
} catch (clipboardError) {
// API使
const textArea = document.createElement('textarea');
textArea.value = newPassword;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
Modal.success({
title: '重置密码成功',
content: `新密码为:${newPassword}(已复制到剪切板)`,
centered: true,
});
}
} catch (error) {
message.error('重置密码失败');
}
},
});
};
const handleEdit = (record: TableListItem) => { const handleEdit = (record: TableListItem) => {
openUserModal(record); openUserModal(record);
}; };

2
vite.config.ts

@ -126,7 +126,7 @@ export default defineConfig({
// }, // },
'/server': { '/server': {
target: 'http://43.137.2.78:8085', target: 'http://43.137.2.78:8085',
// target: 'http://192.168.2.64:8089',
// target: 'http://192.168.2.22:8089',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/server/, '/server'), rewrite: (path) => path.replace(/^\/server/, '/server'),
}, },

Loading…
Cancel
Save