Browse Source

feat: 知识库添加是否公开字段,添加个人中心页面

master
AaronWu 2 months ago
parent
commit
d58518c359
  1. 4
      src/api/knowledgeBase/model.d.ts
  2. 14
      src/api/login/index.ts
  3. 5
      src/api/login/model.d.ts
  4. 254
      src/views/client/entrance/index.vue
  5. 69
      src/views/question/knowledge/formSchemas.tsx

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

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

14
src/api/login/index.ts

@ -35,3 +35,17 @@ export function getUserInfo() {
method: 'get', method: 'get',
}); });
} }
/**
* @description
* @param {ResetPasswordParams} data
* @returns
*/
export function resetPwd(data: API.ResetPasswordParams) {
return request({
url: 'restPassword',
method: 'post',
data,
});
}

5
src/api/login/model.d.ts

@ -56,4 +56,9 @@ declare namespace API {
img: string; img: string;
id: string; id: string;
}; };
type ResetPasswordParams = {
password: string;
newPassword: string;
};
} }

254
src/views/client/entrance/index.vue

@ -18,6 +18,11 @@
<Avatar :src="userInfo.headImg" :alt="userInfo.name">{{ userInfo.name }}</Avatar> <Avatar :src="userInfo.headImg" :alt="userInfo.name">{{ userInfo.name }}</Avatar>
<template #overlay> <template #overlay>
<Menu> <Menu>
<Menu.Item v-if="userInfo.id">
<div class="flex items-center" @click="showProfile">
<user-outlined />&nbsp; 个人中心
</div>
</Menu.Item>
<Menu.Item> <Menu.Item>
<div v-if="userInfo.id" class="flex items-center" @click.prevent="doLogout"> <div v-if="userInfo.id" class="flex items-center" @click.prevent="doLogout">
<poweroff-outlined />&nbsp; {{ $t('layout.header.dropdownItemLoginOut') }} <poweroff-outlined />&nbsp; {{ $t('layout.header.dropdownItemLoginOut') }}
@ -29,6 +34,59 @@
</Menu> </Menu>
</template> </template>
</Dropdown> </Dropdown>
<!-- 添加个人中心抽屉 -->
<a-drawer
v-model:visible="profileVisible"
title="个人中心"
placement="right"
width="500"
:closable="true"
class="profile-drawer"
>
<div class="profile-content">
<!-- 头部背景区域 -->
<div class="profile-header">
<div class="avatar-wrapper">
<a-avatar :size="100" :src="userInfo.headImg">
{{ userInfo.name?.charAt(0) }}
</a-avatar>
<h2>{{ userInfo.name }}</h2>
<div class="role-tag">
<crown-outlined v-if="userInfo.isAdmin === 1" />
<user-outlined v-else />
<span>{{ userInfo.isAdmin === 1 ? '系统管理员' : '普通用户' }}</span>
</div>
</div>
</div>
<!-- 信息卡片 -->
<a-card class="info-card" :bordered="false">
<template #title> <profile-outlined /> 基本信息 </template>
<a-descriptions :column="1">
<a-descriptions-item label="用户名">
<user-outlined class="info-icon" /> {{ userInfo.name }}
</a-descriptions-item>
<a-descriptions-item label="手机号">
<mobile-outlined class="info-icon" /> {{ userInfo.phone || '未设置' }}
</a-descriptions-item>
<a-descriptions-item label="邮箱">
<mail-outlined class="info-icon" /> {{ userInfo.email || '未设置' }}
</a-descriptions-item>
<a-descriptions-item label="注册时间">
<calendar-outlined class="info-icon" /> {{ userInfo.createTime }}
</a-descriptions-item>
</a-descriptions>
<div class="action-buttons">
<a-button type="primary" @click="handleResetPassword">
<template #icon><edit-outlined /></template>
修改密码
</a-button>
</div>
</a-card>
</div>
</a-drawer>
</a-space> </a-space>
</div> </div>
</div> </div>
@ -123,6 +181,29 @@
</div> </div>
</div> </div>
</div> </div>
<a-modal
v-model:visible="passwordModalVisible"
title="修改密码"
@ok="handlePasswordSubmit"
@cancel="handlePasswordCancel"
:confirmLoading="confirmLoading"
>
<a-form
ref="passwordFormRef"
:model="passwordForm"
:rules="passwordRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<a-form-item label="原密码" name="password">
<a-input-password v-model:value="passwordForm.password" placeholder="请输入原密码" />
</a-form-item>
<a-form-item label="新密码" name="newPassword">
<a-input-password v-model:value="passwordForm.newPassword" placeholder="请输入新密码" />
</a-form-item>
</a-form>
</a-modal>
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
@ -137,6 +218,10 @@
BookOutlined, BookOutlined,
PoweroffOutlined, PoweroffOutlined,
QuestionCircleOutlined, QuestionCircleOutlined,
CrownOutlined,
ProfileOutlined,
MobileOutlined,
CalendarOutlined,
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { Avatar, Menu, Dropdown, Modal, message } from 'ant-design-vue'; import { Avatar, Menu, Dropdown, Modal, message } from 'ant-design-vue';
@ -146,7 +231,9 @@
import { fetchKnowledgeBaseList, findOneById } from '@/api/knowledgeBase'; import { fetchKnowledgeBaseList, findOneById } from '@/api/knowledgeBase';
import { DictEnum } from '@/enums/dictEnum'; import { DictEnum } from '@/enums/dictEnum';
import { getDictionaryByTypeName } from '@/utils/dict'; import { getDictionaryByTypeName } from '@/utils/dict';
import { EditOutlined } from '@ant-design/icons-vue'; //
import { resetPwd } from '@/api/login';
//
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
@ -174,6 +261,72 @@
]); ]);
const searchResults = ref<API.KnowledgeBaseType[]>([]); const searchResults = ref<API.KnowledgeBaseType[]>([]);
//
const profileVisible = ref(false);
// ref
const passwordModalVisible = ref(false);
const confirmLoading = ref(false);
const passwordFormRef = ref();
const passwordForm = ref({
password: '',
newPassword: '',
});
//
const passwordRules = {
password: [{ required: true, message: '请输入原密码', trigger: 'blur' }],
newPassword: [{ required: true, message: '请输入新密码', trigger: 'blur' }],
};
// handleResetPassword
const handleResetPassword = () => {
passwordModalVisible.value = true;
};
//
const handlePasswordSubmit = async () => {
try {
await passwordFormRef.value.validate();
confirmLoading.value = true;
// TODO: API
const res = await resetPwd(passwordForm.value);
message.success('密码修改成功');
passwordModalVisible.value = false;
passwordForm.value = {
password: '',
newPassword: '',
};
profileVisible.value = false;
await userStore.logout();
keepAliveStore.clear();
//
localStorage.clear();
await nextTick();
userInfo.value = {
name: '访客',
};
} catch (error) {
console.error('表单验证失败:', error);
} finally {
confirmLoading.value = false;
}
};
//
const handlePasswordCancel = () => {
passwordModalVisible.value = false;
passwordFormRef.value?.resetFields();
};
const showProfile = () => {
profileVisible.value = true;
};
let searchTimer: NodeJS.Timeout | null = null; let searchTimer: NodeJS.Timeout | null = null;
// //
@ -288,6 +441,105 @@
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.profile-drawer {
:deep(.ant-drawer-header) {
border-bottom: none;
padding: 16px 24px;
.ant-drawer-title {
font-size: 18px;
font-weight: 600;
}
}
.profile-content {
.profile-header {
margin: -24px -24px 24px;
padding: 40px 24px;
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
// border-bottom-left-radius: 30px;
.avatar-wrapper {
text-align: center;
:deep(.ant-avatar) {
border: 4px solid rgba(255, 255, 255, 0.2);
margin-bottom: 16px;
}
h2 {
color: white;
margin: 8px 0;
font-size: 24px;
}
.role-tag {
display: inline-flex;
align-items: center;
background: rgba(255, 255, 255, 0.1);
padding: 4px 12px;
border-radius: 20px;
color: white;
font-size: 14px;
.anticon {
margin-right: 6px;
}
}
}
}
.info-card {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
border-radius: 12px;
:deep(.ant-card-head) {
border-bottom: 1px solid #f0f0f0;
padding: 0 24px;
.ant-card-head-title {
font-size: 16px;
font-weight: 500;
.anticon {
margin-right: 8px;
color: #1890ff;
}
}
}
.ant-descriptions-item {
padding: 12px 0;
.ant-descriptions-item-label {
color: #666;
width: 80px;
}
.info-icon {
margin-right: 8px;
color: #1890ff;
}
}
.action-buttons {
text-align: center;
margin-top: 24px;
.ant-btn {
min-width: 120px;
border-radius: 20px;
height: 36px;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15);
}
}
}
}
}
}
.entrance-container { .entrance-container {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);

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

@ -61,6 +61,75 @@ export const getEditFormSchema: (
span: 12, span: 12,
}, },
}, },
{
field: 'isPublic',
component: 'RadioGroup',
label: '是否公开',
defaultValue: 0,
colProps: {
span: 24,
},
componentProps: {
options: [
{
label: '是',
value: 1,
},
{
label: '否',
value: 0,
},
],
},
rules: [{ required: true, type: 'number' }],
},
{
label: '附件',
field: 'files',
component: 'Upload',
componentProps: {
disabled: isDetail,
customRequest: async (data) => {
console.log('data: ', data);
const formData = new FormData();
formData.append('file', data.file);
const res = await commonUpload(formData);
console.log('res: ', res);
data?.onSuccess && data?.onSuccess(res, data.file as any);
},
beforeUpload: (file) => {
console.log('file: ', file);
// 限制允许上传的文件类型
const allowedTypes = ['xlsx', 'xls', 'doc', 'docx', 'pdf', 'jpg', 'jpeg', 'png'];
// 检查文件类型
const fileType = getFileExtension(file.name) || 'unknown';
if (!allowedTypes.includes(fileType)) {
// 文件类型不在允许列表中,拒绝上传
// 可以在这里展示错误信息
message.warning('文件类型不正确');
return false;
}
// 其他验证逻辑...
// 允许上传
return true;
},
},
componentSlots: {
default: () => (
<Button>
<UploadOutlined />
</Button>
),
},
colProps: {
span: 24,
},
rules: [{ required: false }],
},
{ {
label: '解决方案', label: '解决方案',
field: 'solution', field: 'solution',

Loading…
Cancel
Save