feat:完善字典管理模块

This commit is contained in:
unknown
2026-01-04 20:05:42 +08:00
parent f69d83d3ea
commit 80eec42473
26 changed files with 1478 additions and 151 deletions

12
components.d.ts vendored
View File

@@ -35,16 +35,22 @@ declare module 'vue' {
ElHeader: typeof import('element-plus/es')['ElHeader'] ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink'] ElLink: typeof import('element-plus/es')['ElLink']
ElMain: typeof import('element-plus/es')['ElMain'] ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover'] ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
@@ -52,12 +58,18 @@ declare module 'vue' {
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree'] ElTree: typeof import('element-plus/es')['ElTree']
GlobaIcon: typeof import('./src/components/globaIcon/index.vue')['default']
GlobalIcon: typeof import('./src/components/GlobalIcon/index.vue')['default']
OverflowTabs: typeof import('./src/components/overflowTabs/index.vue')['default'] OverflowTabs: typeof import('./src/components/overflowTabs/index.vue')['default']
PageForm: typeof import('./src/components/pageForm/index.vue')['default'] PageForm: typeof import('./src/components/pageForm/index.vue')['default']
ProTable: typeof import('./src/components/proTable/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
StageBreadcrumbs: typeof import('./src/components/stageBreadcrumbs/index.vue')['default'] StageBreadcrumbs: typeof import('./src/components/stageBreadcrumbs/index.vue')['default']
StandardMenu: typeof import('./src/components/standardMenu/index.vue')['default'] StandardMenu: typeof import('./src/components/standardMenu/index.vue')['default']
StandMenu: typeof import('./src/components/standMenu/index.vue')['default'] StandMenu: typeof import('./src/components/standMenu/index.vue')['default']
} }
export interface GlobalDirectives {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
} }

View File

@@ -13,8 +13,3 @@ export const login = (data: { username: string; password: string }) => {
} }
}); });
}; };
// 获取用户信息
export const getUserInfo = () => {
return request.get('/api/user/info');
};

View File

@@ -0,0 +1,91 @@
import request from '@/request';
type paramsProps = {
keyword?: string;
key?: string;
status?:string|number;
pageNo:number;
pageSize:number;
}
type addDataProps = {
name: string;
key?: string;
remark: string;
status: number;
[key:string]:any;
}
// 获取字典类型的数据
export const getDictValues = (params: paramsProps) => {
return request.get('/auth/v1/backend/dict/type', params);
};
// 添加字典类型数据
export const addDictValue = (data: addDataProps) => {
return request.post('/auth/v1/backend/dict/type', data);
};
// 删除字典类型
export const deleteDictValue = (id: string) => {
return request.delete(`/auth/v1/backend/dict/type/${id}`);
};
// 修改字典类型
export const updateDictValue = (id: string, data: addDataProps) => {
return request.put(`/auth/v1/backend/dict/type/${id}`, data);
};
// 启用接口
export const enableDict = (id:string)=>{
return request.post(`/auth/v1/backend/dict/type/${id}/enable`);
}
// 禁用接口
export const disableDict = (id:string)=>{
return request.post(`/auth/v1/backend/dict/type/${id}/disable`)
}
/**
* 二级字典类型值模块的增删改查
* */
// 获取字典类型值的数据
export const getDictTypeValue = (id:string,params: paramsProps) => {
return request.get(`/auth/v1/backend/dict/type/${id}`, params);
};
// 保存字典类型值的数据
export const saveDictTypeValue = (id:string,data: addDataProps) => {
return request.post(`/auth/v1/backend/dict/type/${id}`, data);
};
// 删除字典类型值
export const deleteDictTypeValue = (typeId:string,id: string) => {
return request.delete(`/auth/v1/backend/dict/${typeId}/${id}`);
};
// 更新字典类型值
export const updateDictTypeValue = (typeId:string,id: string,data: addDataProps) => {
return request.put(`/auth/v1/backend/dict/${typeId}/${id}`, data);
};
// 获取下级菜单数据
export const getNextDictMenu = (id:string,parentId:string) => {
return request.get(`/auth/v1/backend/dict/type/${id}/data/${parentId}`);
};
// 启用接口
export const enableTypeDict = (id:string)=>{
return request.post(`/auth/v1/backend/dict/type/${typeId}/${id}/enable`);
}
// 禁用接口
export const disableTypeDict = (id:string)=>{
return request.post(`/auth/v1/backend/dict/type/${typeId}/${id}/disable`)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -7,6 +7,7 @@
export {} export {}
declare global { declare global {
const EffectScope: typeof import('vue').EffectScope const EffectScope: typeof import('vue').EffectScope
const ElMessage: typeof import('element-plus/es').ElMessage
const ElMessageBox: typeof import('element-plus/es').ElMessageBox const ElMessageBox: typeof import('element-plus/es').ElMessageBox
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
const computed: typeof import('vue').computed const computed: typeof import('vue').computed

View File

@@ -1,24 +1,28 @@
<template> <template>
<div class="mj-filter-group"> <div class="mj-filter-group">
<div class="mj-icon-container"> <div :class="className ? className : 'mj-icon-container'">
<el-popover <el-popover
ref="filterPopover" ref="filterPopover"
trigger="click" trigger="click"
popper-class="filter-popper" popper-class="filter-popper"
placement="bottom-end" placement="bottom-end"
:teleported="true" :teleported="true"
width="auto"
@hide="$emit('on-hide')"
> >
<template #reference> <template #reference>
<div class="mj-icon-warp">
<div class="mj-icon-item" title="筛选"> <div class="mj-icon-item" title="筛选">
<el-icon><Filter /></el-icon> <el-icon><Filter /></el-icon>
</div> </div>
<slot name="filterLabel"></slot>
</div>
</template> </template>
<div class="filter-container" @click.stop>
<div class="filter-container">
<slot></slot> <slot></slot>
</div> </div>
</el-popover> </el-popover>
<div class="mj-icon-item" title="下载" @click="$emit('download')"> <div class="mj-icon-item" title="下载" @click="$emit('download')" v-if="download">
<el-icon><Download /></el-icon> <el-icon><Download /></el-icon>
</div> </div>
</div> </div>
@@ -30,7 +34,19 @@ import { Filter, Download } from "@element-plus/icons-vue";
const filterPopover = ref(null); const filterPopover = ref(null);
// 定义事件:重置、应用、下载 // 定义事件:重置、应用、下载
defineEmits(["download"]); defineEmits(["download",'on-hide']);
defineProps({
download:{
type:Boolean,
default:true
},
className:{
type:String,
default:''
}
})
defineExpose({ defineExpose({
@@ -48,7 +64,14 @@ defineExpose({
border-radius: 4px; border-radius: 4px;
background-color: #fff; background-color: #fff;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
.mj-icon-warp{
display: flex;
align-items: center;
padding-right: 3px;
font-size: 12px;
color:#45556C;
cursor: pointer;
}
.mj-icon-item { .mj-icon-item {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -67,4 +90,17 @@ defineExpose({
} }
} }
} }
.mj-icon-level-container{
@extend .mj-icon-container;
border-radius: 10px;
box-shadow: none;
border: 1px solid #E2E8F0;
padding: 0 4px;
.mj-icon-item{
width: 30px;
height: 30px;
}
}
</style> </style>

View File

@@ -0,0 +1,206 @@
<template>
<div class="pro-table-container">
<el-table :data="data" v-bind="$attrs" v-loading="innerLoading" class="hover-action-table" header-row-class-name="header-row-name">
<template v-for="(col,index) in columns" :key="col.prop">
<el-table-column
v-if="!col.slot && col.prop !== 'actions'"
v-bind="col"
>
<template #default="scope" v-if="!col.formatter">{{ scope.row[col.prop] || "-" }}</template>
</el-table-column>
<el-table-column v-else-if="col.slot" v-bind="col">
<template #default="scope">
<slot
:name="col.slot"
:row="scope.row"
:index="scope.$index"
></slot>
</template>
</el-table-column>
<el-table-column v-else-if="col.prop === 'actions'" v-bind="col">
<template #default="scope">
<div class="action-group">
<slot name="actions" :row="scope.row"></slot>
</div>
</template>
</el-table-column>
</template>
</el-table>
<div class="pro-table-footer">
<slot name="footer">
<div class="mj-footer-content" v-if="pagination">
<div class="footer-left">
<span class="total-text"> {{ total || data.length }} 个条目</span>
</div>
<div class="footer-right">
<el-pagination
v-bind="paginationConfig"
v-model:current-page="pagination.currentPage"
v-model:page-size="pagination.pageSize"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
</slot>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
const props = defineProps({
columns: { type: Array, required: true },
data: { type: Array, required: true },
total: { type: Number, default: 0 },
pagination: { type: [Object, Boolean], default: false },
requestApi: { type: Function, default: null },
// 是否立即请求数据
immediate: { type: Boolean, default: true },
// 是否在激活时刷新数据
refreshOnActivated: { type: Boolean, default: true }
});
const emit = defineEmits(["current-change", "size-change", "update:data", "update:total"]);
// 内部控制 loading
const innerLoading = ref(false);
// 标记是否是首次挂载
let isFirstMount = true;
// 参数传递逻辑
const params = computed(()=>{
if(!props.pagination){
return {}
}else if(typeof props.pagination === 'object' && props.pagination !== null){
return {
pageNo:props.pagination.currentPage,
pageSize:props.pagination.pageSize
}
}else{
return {
pageNo:1,
pageSize:20
}
}
})
// 请求方法
const refresh = async () => {
if (!props.requestApi) return;
innerLoading.value = true;
try {
const res = await props.requestApi(params.value);
emit("update:data", res?.records || []);
emit("update:total", res?.total || 0);
} catch (error) {
console.error("ProTable Request Error:", error);
} finally {
innerLoading.value = false;
}
};
// 分页变化时自动触发刷新
const handleCurrentChange = (val) => {
emit("current-change", val);
if (props.requestApi) refresh();
};
const handleSizeChange = (val) => {
emit("size-change", val);
if (props.requestApi) refresh();
};
onMounted(() => {
if (props.immediate) {
refresh();
}
setTimeout(() => { isFirstMount = false; }, 0);
});
// 兼容设置了keep-alive
onActivated(() => {
if (!isFirstMount && props.refreshOnActivated) {
refresh();
}
});
// 暴露 refresh 方法给外部使用
defineExpose({ refresh });
// 分页配置
const paginationConfig = computed(() => ({
layout: "prev, pager, next",
background: false,
...props.pagination,
}));
</script>
<style scoped lang="scss">
.pro-table-container {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
border: 1px solid #f0f0f0;
:deep(.header-row-name){
th.el-table__cell{
background-color: #FBFCFD;
}
}
/* 底部容器样式:对应图片中的布局 */
.pro-table-footer {
padding: 3px 24px;
background-color: #fcfdfe;
}
.mj-footer-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.total-text {
font-size: 13px;
color: #909399;
}
/* 悬浮操作列核心逻辑 */
.action-group {
opacity: 0;
transform: translateX(15px);
pointer-events: none;
transition: opacity 0.25s ease-in, transform 0.25s ease-in;
}
:deep(.el-table__row:hover) .action-group {
opacity: 1;
transform: translateX(0);
pointer-events: auto;
transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1),
transform 0.3s cubic-bezier(0.25, 1, 0.5, 1);
}
/* 分页器样式微调,使其更扁平符合图片 */
:deep(.el-pagination button) {
background-color: transparent;
}
:deep(.el-pager li) {
background: transparent;
font-weight: normal;
}
:deep(.el-pager li.is-active) {
color: #409eff;
font-weight: bold;
}
}
</style>

29
src/dict/dictManage.ts Normal file
View File

@@ -0,0 +1,29 @@
// 后台 - 字典管理模块-字典内容信息
// 字典状态映射
const statusDict = {
1: '正常',
0: '禁用'
}
// 字典状态颜色
const statusDictColor = {
1:'#66E5BE',
0:'#ff0000'
}
// 设置字典转换为目标格式
const statusOptions = Object.keys(statusDict).map((key) => {
return {
label: statusDict[key],
value: Number(key)
}
})
export default {
statusDict,
statusDictColor,
statusOptions
}

5
src/dict/index.ts Normal file
View File

@@ -0,0 +1,5 @@
import DictManage from './dictManage';
const Dict = { DictManage }
export { DictManage }
export default Dict;

View File

@@ -19,16 +19,17 @@ const pinia = createPinia();
const app = createApp(App); const app = createApp(App);
// 导入全局的i18n文件 // 导入全局的i18n文件
const loadLocalMessages = async (lang: string) => { const loadLocalMessages = async () => {
const messages: Record<string, any> = {}; const messages: Record<string, any> = {};
const locales = import.meta.glob("./locales/*.ts"); const locales = import.meta.glob("./locales/*.ts", { eager: true });
for (const path in locales) {
// 遍历所有匹配的文件
Object.keys(locales).forEach((path) => {
const lang = path.match(/\.\/locales\/(.+)\.ts$/)?.[1]; const lang = path.match(/\.\/locales\/(.+)\.ts$/)?.[1];
if (lang) { if (lang && locales[path]) {
const module = await locales[path](); messages[lang] = locales[path].default;
messages[lang] = module.default;
}
} }
});
return messages; return messages;
}; };
@@ -46,19 +47,30 @@ const getLocalLang = () => {
return "en"; return "en";
}; };
// 设置全局的i18语言显示
const i18n = createI18n({ // 创建并初始化应用的异步函数
const initApp = async () => {
const pinia = createPinia();
const app = createApp(App);
// 加载语言消息
const messages = await loadLocalMessages();
const elementLocale = getLocalLang() === "zh" ? zhCn : en;
// 设置全局的i18n语言显示
const i18n = createI18n({
locale: getLocalLang(), locale: getLocalLang(),
messages: await loadLocalMessages(), messages: messages,
legacy: false, legacy: false,
}); });
const elementLocale = getLocalLang() === "zh" ? zhCn : en; app
app
.use(i18n) .use(i18n)
.use(router) .use(router)
.use(pinia) .use(pinia)
.use(Directives) .use(Directives)
.use(ElementPlus, { locale: elementLocale, size: "medium" }) .use(ElementPlus, { locale: elementLocale, size: "default" })
.mount("#app"); .mount("#app");
};
// 执行初始化
initApp().catch(console.error);

View File

@@ -11,7 +11,7 @@
<div class="mj-aside-title" v-show="!isCollapse"> <div class="mj-aside-title" v-show="!isCollapse">
{{ topTitle }} {{ topTitle }}
</div> </div>
<mjMenus <standardMenu
class="mj-aside_menu" class="mj-aside_menu"
:isCollapse="isCollapse" :isCollapse="isCollapse"
:menuList="sideMenuList" :menuList="sideMenuList"
@@ -28,7 +28,7 @@
<el-container> <el-container>
<el-header class="mj-header-content"> <el-header class="mj-header-content">
<!-- 左侧的菜单展示 --> <!-- 左侧的菜单展示 -->
<mjMenus <standardMenu
:menuList="topLevelMenuList" :menuList="topLevelMenuList"
mode="horizontal" mode="horizontal"
:active-menu="selectedTopMenu" :active-menu="selectedTopMenu"
@@ -46,7 +46,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import mjMenus from "@/components/standardMenu/index.vue"; import standardMenu from "@/components/standardMenu/index.vue";
import { useUserStore } from "@/store"; import { useUserStore } from "@/store";
import { DArrowLeft, DArrowRight } from "@element-plus/icons-vue"; import { DArrowLeft, DArrowRight } from "@element-plus/icons-vue";
import rightMenuGroup from './rightMenuGroup.vue'; import rightMenuGroup from './rightMenuGroup.vue';
@@ -101,6 +101,11 @@ const topLevelMenuList = computed(() => {
}).filter(itv=>itv.name !== 'stage'); }).filter(itv=>itv.name !== 'stage');
}); });
// 获取是管理后台中心的数据内容
const backTitle = computed(()=>{
return menuList.value.find(itv=>itv.name === 'stage')?.meta?.title || '-';
})
// 后台管理点击获取列表 // 后台管理点击获取列表
const onStageManage = () =>{ const onStageManage = () =>{
selectedTopMenu.value = '/stage'; selectedTopMenu.value = '/stage';
@@ -109,7 +114,7 @@ const onStageManage = () =>{
const topTitle = computed(() => { const topTitle = computed(() => {
return ( return (
topLevelMenuList.value.find((path) => path.path === selectedTopMenu.value) topLevelMenuList.value.find((path) => path.path === selectedTopMenu.value)
?.meta?.title || "-" ?.meta?.title || backTitle.value
); );
}); });

View File

@@ -3,7 +3,9 @@
<div class="user-header-container"> <div class="user-header-container">
<div class="action-group"> <div class="action-group">
<div class="action-item"> <div class="action-item">
<el-tooltip content="管理中心">
<el-icon :size="16" @click="onStageManage"><Monitor /></el-icon> <el-icon :size="16" @click="onStageManage"><Monitor /></el-icon>
</el-tooltip>
</div> </div>
<div class="action-item"> <div class="action-item">
<el-badge is-dot class="notice-badge"> <el-badge is-dot class="notice-badge">

View File

@@ -17,10 +17,9 @@
<div class="footer-users"> <div class="footer-users">
<div class="avatar-group"> <div class="avatar-group">
<img <img
v-for="i in 4" v-for="(img,imgIndex) in picList"
:key="i" :key="imgIndex"
:src="`https://i.pravatar.cc/100?img=${i + 10}`" :src="img.src"
alt="user"
/> />
</div> </div>
<span class="user-count">超过 5,000+ 行业领先企业正在使用</span> <span class="user-count">超过 5,000+ 行业领先企业正在使用</span>
@@ -43,7 +42,7 @@
<el-form-item label="账号/邮箱" class="account-item"> <el-form-item label="账号/邮箱" class="account-item">
<el-input <el-input
v-model="loginForm.username" v-model="loginForm.username"
placeholder="admin@figmamake.com" placeholder="请输入账号/邮箱"
:prefix-icon="Message" :prefix-icon="Message"
/> />
</el-form-item> </el-form-item>
@@ -58,7 +57,7 @@
v-model="loginForm.password" v-model="loginForm.password"
type="password" type="password"
show-password show-password
placeholder="••••••••" placeholder="请输入密码"
:prefix-icon="Lock" :prefix-icon="Lock"
/> />
</el-form-item> </el-form-item>
@@ -95,6 +94,18 @@ import TokenManager from '@/utils/storage';
defineOptions({ name: "Login" }); defineOptions({ name: "Login" });
const imagePaths = [
'../../assets/images/login/1.png',
'../../assets/images/login/2.png',
'../../assets/images/login/3.png',
'../../assets/images/login/4.png',
]
const picList = imagePaths.map((path, index) => ({
src: new URL(path, import.meta.url).href,
id: index
}));
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const userStore = useUserStore(); const userStore = useUserStore();
@@ -104,7 +115,8 @@ const loading = ref(false);
const loginForm = reactive({ const loginForm = reactive({
username: "user", username: "user",
password: "password" password: "password",
remember:false
}); });
const loginRules: FormRules = { const loginRules: FormRules = {
@@ -144,8 +156,9 @@ const handleLogin = async () => {
// 保存用户信息 // 保存用户信息
userStore.setToken(refresh_token); userStore.setToken(refresh_token);
// TODO:获取用户信息 // 获取用户信息
userStore.setUserInfo(userInfo); userStore.setUserInfo(userInfo);
// TODO:记住密码功能
ElMessage.success("登录成功"); ElMessage.success("登录成功");
@@ -269,7 +282,7 @@ $bg-light: #f8faff;
// --- 右侧登录区 --- // --- 右侧登录区 ---
.right-section { .right-section {
flex: 1; flex: 1;
background: white; background: #fff;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -289,7 +302,6 @@ $bg-light: #f8faff;
} }
.login-form { .login-form {
// 深度作用选择器统一处理 Element Plus 内部样式
:deep(.el-form-item__label) { :deep(.el-form-item__label) {
font-weight: 500; font-weight: 500;
color: #666; color: #666;

View File

@@ -0,0 +1,427 @@
<template>
<el-drawer
v-model="visible"
title="字段配置"
size="70%"
:close-on-click-modal="false"
:close-on-press-escape="false"
destroy-on-close
class="standard-ui-back-drawer"
>
<div class="mj-drawer-content">
<!-- 搜索查询 -->
<div class="mj-drawer-top-container">
<div class="top-toolbar">
<div class="left-actions">
<div class="search-dict-input">
<el-input
v-model="searchQuery"
placeholder="搜索字段名称..."
class="custom-search-input auto-expand-input"
:prefix-icon="Search"
@keyup.enter="onConfirmSuccess"
/>
</div>
<!-- 状态筛选的内容 -->
<CommonFilter
@on-hide="onCloseFilter"
:download="false"
className="mj-icon-level-container"
>
<template #filterLabel>
<span>状态筛选</span>
</template>
<div class="mj-filter-content">
<div class="filter-header">
<span class="title">条件筛选</span>
<el-link
type="primary"
underline="never"
class="reset-btn"
@click="onReset"
>重置</el-link
>
</div>
<div class="filter-body">
<div class="filter-item">
<label>状态</label>
<el-select
v-model="filterForm.status"
placeholder="全部状态"
class="custom-select"
:teleported="false"
>
<el-option
:label="item.label"
:value="item.value"
v-for="(item, index) in DictManage.statusOptions"
:key="index"
/>
</el-select>
</div>
</div>
<el-button
type="primary"
class="apply-btn"
@click="onConfirmSuccess"
>应用筛选</el-button
>
</div>
</CommonFilter>
</div>
<div class="right-actions">
<el-button
type="primary"
class="add-field-btn"
:icon="Plus"
@click="addFields"
>
新增字段
</el-button>
</div>
</div>
</div>
<!-- Table列表 -->
<CommonTable
ref="tableRef"
v-model:data="list"
:columns="columns"
pagination
height="calc(100vh - 186px)"
:immediate="false"
row-key="id"
lazy
:load="load"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:request-api="fetchData"
>
<!-- 状态插槽 -->
<template #status="{ row }">
<div
class="mj-status-dot"
:style="{
'--data-status-color': DictManage.statusDictColor[row.status],
}"
@click="handleDictStatus(row)"
>
{{ DictManage.statusDict[row.status] }}
</div>
</template>
<template #actions="{ row }">
<el-button link type="primary" @click="handleAddNext(row)"
>添加二级字段</el-button
>
<el-button link type="primary" @click="handleEdit(row)"
>编辑</el-button
>
<el-button link type="danger" @click="handleDelete(row)"
>删除</el-button
>
</template>
</CommonTable>
<!-- 新增字段 -->
<dictFieldLevelManage
:title="dictTitle"
v-model:dialogVisible="addVisible"
:row="selectItem"
:parentId="parentId"
@confirm-success="onConfirmSuccess"
/>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import CommonTable from "@/components/proTable/index.vue";
import dayjs from "dayjs";
import { Search, Filter, Plus } from "@element-plus/icons-vue";
import dictFieldLevelManage from "./dictFieldLevelManage.vue";
import { DictManage } from "@/dict";
import {
getDictTypeValue,
deleteDictTypeValue,
getNextDictMenu,
enableTypeDict,
disableTypeDict,
} from "@/api/stage/dict";
defineOptions({ name: "DictFieldConfig" });
interface User {
id: number;
date: string;
name: string;
address: string;
hasChildren?: boolean;
children?: User[];
}
const dictTitle = ref("");
const addVisible = ref(false);
const selectItem = reactive({});
const searchQuery = ref("");
const filterForm = reactive({
status: "",
});
const tableRef = ref(null);
const visible = ref<boolean>(false);
const parentId = ref<string>("");
const total = ref(0);
const list = ref([]);
const columns = [
{
prop: "id",
label: "字典编码",
},
{
prop: "label",
label: "字典名称",
align: "center",
},
{
prop: "value",
label: "字典值",
align: "center",
},
{
prop: "status",
label: "状态",
align: "center",
slot: "status",
},
{
prop: "sort",
label: "排序",
align: "center",
},
{
prop: "updateTime",
label: "更新时间",
align: "center",
showOverflowTooltip: true,
formatter: (val) => {
return val.createTime
? dayjs(val.createTime).format("YYYY-MM-DD HH:mm")
: "-";
},
},
{ prop: "actions", label: "操作", align: "right", width: "300" },
];
// 请求数据信息
const fetchData = async (params) => {
try {
const response = await getDictTypeValue(parentId.value, {
...params,
keyword: searchQuery.value,
...filterForm,
});
if (response.records) {
response.records = response.records.map((item) => ({
...item,
children: item.children || [],
hasChildren: true, // 设置为true加载子集数据信息
}));
}
return response;
} catch (error) {
console.log("getTableData Error", error);
}
};
// 启用-禁用事件
const handleDictStatus = async (row) => {
try {
row.status === 1
? await disableTypeDict(parentId.value, row.id)
: await enableTypeDict(parentId.value, row.id);
ElMessage.success("操作成功");
onConfirmSuccess();
} catch (error) {
console.log("error", error);
}
};
// FIXME:tree懒加载数据
const load = async (
row: User,
treeNode: unknown,
resolve: (data: User[]) => void
) => {
try {
const resp = await getNextDictMenu(row.id,parentId.value);
console.log("获取当前返回的二级数据信息==>:",resp);
resolve([]);
} catch (error) {
resolve([]);
console.log("fetch tree error", error);
}
};
// 新增二级菜单数据
const addFields = () => {
dictTitle.value = "新增字段";
addVisible.value = true;
};
// 确定刷新数据
const onConfirmSuccess = () => {
tableRef.value && tableRef.value.refresh();
};
// 关闭popover 重置数据信息
const onCloseFilter = () =>{
filterForm.status = "";
}
// 筛选重置
const onReset = () => {
onCloseFilter();
onConfirmSuccess();
};
// 添加二级字段
const handleAddNext = async (item) => {
addVisible.value = true;
dictTitle.value = "添加二级字段";
};
// 编辑当前字段
const handleEdit = (item) => {
addVisible.value = true;
dictTitle.value = "编辑字段";
Object.assign(selectItem, item);
};
// 删除字典类型数据
const handleDelete = async (item) => {
ElMessageBox.confirm("确定要删除吗?", "提示", { type: "warning" })
.then(async () => {
try {
await deleteDictTypeValue(parentId.value, item.id);
tableRef.value && tableRef.value.refresh();
} catch (error) {
console.log("fetch error", error);
}
})
.catch(() => {
console.log("cancel");
});
};
defineExpose({
open: async (item) => {
parentId.value = item.id;
visible.value = true;
await nextTick();
if (tableRef.value) {
await tableRef.value.refresh();
}
},
close() {
visible.value = false;
},
});
</script>
<style lang="scss" scoped>
.mj-drawer-content {
.pro-table-container {
border-radius: 2px;
:deep(.pro-table-footer) {
padding-top: 12px;
padding-bottom: 12px;
}
}
.mj-drawer-top-container {
.top-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
padding: 16px 20px;
}
.left-actions {
display: flex;
gap: 12px;
}
/* 搜索框自定义 */
.custom-search-input {
--el-input-height: 30px;
&:deep(.el-input__wrapper) {
background-color: #f5f7fa;
border-radius: 10px;
box-shadow: none;
border: 1px solid #e2e8f0;
}
}
/* 新增字段按钮样式 */
.add-field-btn {
background: linear-gradient(to right, #2b65f6, #1e4edb);
border: none;
border-radius: 10px;
padding: 10px 20px;
font-weight: bold;
box-shadow: 0 4px 12px rgba(43, 101, 246, 0.3);
}
/* 筛选卡片内部样式 */
.filter-card {
padding: 10px 5px;
}
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18px;
}
.filter-header .title {
font-weight: 600;
font-size: 15px;
color: #333;
}
.reset-link {
font-size: 13px;
color: #2b65f6;
}
.filter-group {
margin-bottom: 20px;
}
.filter-group label {
display: block;
font-size: 13px;
color: #94a3b8;
margin-bottom: 8px;
font-weight: 500;
}
/* 统一输入框底色 */
.full-width-select :deep(.el-input__wrapper),
.full-width-date {
width: 100% !important;
background-color: #f5f7fa !important;
box-shadow: none !important;
border: 1px solid #e4e7ed !important;
height: 40px;
}
/* 应用筛选按钮 */
.apply-btn {
width: 100%;
height: 42px;
background-color: #2b65f6;
border-radius: 6px;
font-weight: bold;
margin-top: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,149 @@
<template>
<div class="dict-manage">
<el-dialog
class="standard-ui-dialog"
:title
:width="558"
:model-value="dialogVisible"
@close="onCancel"
destroy-on-close
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-form
ref="ruleFormRef"
:model="form"
:rules="rules"
label-width="120px"
>
<el-form-item label="字段名称:" prop="label">
<el-input placeholder="请输入字典名称" v-model="form.label"></el-input>
</el-form-item>
<el-form-item label="字典值:" prop="value">
<el-input placeholder="请输入字典值" v-model="form.value" :disabled="form.id ? true : false"></el-input>
</el-form-item>
<el-form-item prop="sort">
<template #label>
<div class="el-form-item-owner">
<span>排序</span>
<el-tooltip content="排序" placement="top-start">
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
<span></span>
</div>
</template>
<!-- 换成排序的输入框 -->
<el-input-number placeholder="请输入排序" v-model="form.sort" :min="0" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="状态:" prop="status">
<el-radio-group v-model="form.status">
<el-radio :value="1">启用</el-radio>
<el-radio :value="0">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注:">
<el-input
type="textarea"
:autosize="{ minRows: 4, maxRows: 5 }"
placeholder="请输入备注"
maxlength="500"
show-word-limit
v-model="form.remark"
></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="onCancel">取消</el-button>
<el-button @click="onConfirm(ruleFormRef)" type="primary" :loading="loading">确认</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted } from "vue";
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
import { QuestionFilled } from "@element-plus/icons-vue";
import { saveDictTypeValue, updateDictTypeValue } from "@/api/stage/dict";
defineOptions({ name: "DictFieldLevelManage" });
const loading = ref(false);
const {
dialogVisible = false,
row = {},
parentId,
title = "字典管理",
} = defineProps<{
dialogVisible: boolean;
row?: any;
parentId?: string|number;
title?: string;
}>();
const emit = defineEmits<{
(e: "update:dialogVisible", value: boolean): void;
(e: "confirm-success"): void;
}>();
const ruleFormRef = ref<FormInstance>();
const form = reactive({
label: "",
value: "",
sort: 0,
status:1,
remark:''
});
// 监听组件中传递的数据-然后进行复制操作
watch(()=>row,(newRow)=>{
if (newRow && Object.keys(newRow).length > 0) {
Object.assign(form, newRow);
}
},{deep:true})
const rules = reactive({
label: [{ required: true, message: "请输入字段名称", trigger: "blur" }],
value: [{ required: true, message: "请输入字典值", trigger: "blur" }],
sort: [{ required: true, message: "请输入排序", trigger: "blur" }],
});
// 确定
const onConfirm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate(async (valid, fields) => {
if (valid) {
loading.value = true;
try {
const response = row.id ? await updateDictTypeValue(parentId,row.id,form) : await saveDictTypeValue(parentId,form);
ElMessage.success(row.id ? '修改成功' : '新增成功');
onCancel();
emit('confirm-success');
} catch (error) {
console.log('error',error);
} finally{
loading.value = false;
}
} else {
console.log("error submit!", fields);
}
});
};
// 取消
const onCancel = () => {
ruleFormRef.value && ruleFormRef.value.resetFields();
emit("update:dialogVisible", false);
};
</script>
<style lang="scss" scoped>
.dict-manage {
:deep(.el-form-item) {
margin-bottom: 26px;
}
.el-form-item-owner {
display: inline-flex;
align-items: center;
gap: 3px;
}
}
</style>

View File

@@ -4,7 +4,7 @@
class="standard-ui-dialog" class="standard-ui-dialog"
:title :title
:width="558" :width="558"
:model-value="dialogVisible" :model-value="dictVisible"
@close="onCancel" @close="onCancel"
destroy-on-close destroy-on-close
:close-on-click-modal="false" :close-on-click-modal="false"
@@ -19,9 +19,9 @@
<el-form-item label="字典名称:" prop="name"> <el-form-item label="字典名称:" prop="name">
<el-input placeholder="请输入字典名称" v-model="form.name"></el-input> <el-input placeholder="请输入字典名称" v-model="form.name"></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="type"> <el-form-item prop="key">
<template #label> <template #label>
<div class="el-form-item-owerner"> <div class="el-form-item-owner">
<span>字典类型</span> <span>字典类型</span>
<el-tooltip content="字典类型" placement="top-start"> <el-tooltip content="字典类型" placement="top-start">
<el-icon><QuestionFilled /></el-icon> <el-icon><QuestionFilled /></el-icon>
@@ -29,76 +29,90 @@
<span></span> <span></span>
</div> </div>
</template> </template>
<el-input placeholder="请输入字典类型" v-model="form.type"></el-input> <el-input placeholder="请输入字典类型" v-model="form.key" :disabled="form.id"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="管理人员:"> <el-form-item label="状态:" prop="status">
<el-input <el-radio-group v-model="form.status">
placeholder="请选择管理人员" <el-radio :value="1">启用</el-radio>
v-model="form.manager" <el-radio :value="0">停用</el-radio>
></el-input>
</el-form-item>
<el-form-item label="状态:" prop="resource">
<el-radio-group v-model="form.resource">
<el-radio :value="1">显示</el-radio>
<el-radio :value="0">隐藏</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="备注:"> <el-form-item label="备注:">
<el-input <el-input
type="textarea" type="textarea"
:row="4" :autosize="{ minRows: 4, maxRows: 5 }"
placeholder="请输入备注" placeholder="请输入备注"
maxlength="500"
show-word-limit
v-model="form.remark" v-model="form.remark"
></el-input> ></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="onCancel">取消</el-button> <el-button @click="onCancel">取消</el-button>
<el-button @click="onConfirm(ruleFormRef)" type="primary">确认</el-button> <el-button @click="onConfirm(ruleFormRef)" type="primary" :loading="loading">确认</el-button>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, onMounted } from "vue"; import { reactive, ref, onMounted } from "vue";
import type { FormInstance, FormRules } from "element-plus"; import { ElMessage, type FormInstance, type FormRules } from "element-plus";
import { QuestionFilled } from "@element-plus/icons-vue"; import { QuestionFilled } from "@element-plus/icons-vue";
import { addDictValue, updateDictValue } from "@/api/stage/dict";
defineOptions({ name: "DictManage" }); defineOptions({ name: "DictManage" });
const loading = ref(false);
const { dialogVisible = false,row={},title="字典管理" } = defineProps<{ const {
dialogVisible: boolean; dictVisible = false,
row: any; row = {},
title?:string title = "字典管理",
} = defineProps<{
dictVisible: boolean;
row?: any;
title?: string;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: "update:dialogVisible", value: boolean): void; (e: "update:dictVisible", value: boolean): void;
(e: "close"): void; (e: "confirm-success"): void;
(e: "confirm"): void;
}>(); }>();
const ruleFormRef = ref<FormInstance>(); const ruleFormRef = ref<FormInstance>();
const form = reactive({ const form = reactive({
name: "", name: "",
type: "", key: "",
manager: "", status: 1,
status: "",
remark: "", remark: "",
}); });
// 监听组件中传递的数据-然后进行复制操作
watch(()=>row,(newRow)=>{
if (newRow && Object.keys(newRow).length > 0) {
Object.assign(form, newRow);
}
},{deep:true})
const rules = reactive({ const rules = reactive({
name: [{ required: true, message: "请输入字典名称", trigger: "blur" }], name: [{ required: true, message: "请输入字典名称", trigger: "blur" }],
type: [{ required: true, message: "请输入字典类型", trigger: "blur" }], key: [{ required: true, message: "请输入字典类型", trigger: "blur" }],
resource: [{ required: true, message: "请选择状态", trigger: "change" }], status: [{ required: true, message: "请选择状态", trigger: "change" }],
}); });
// 确定 // 确定
const onConfirm = async (formEl: FormInstance | undefined) => { const onConfirm = async (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
await formEl.validate((valid, fields) => { await formEl.validate(async (valid, fields) => {
if (valid) { if (valid) {
emit("update:dialogVisible", false); loading.value = true;
emit("confirm",form); try {
const response = row.id ? await updateDictValue(row.id,form) : await addDictValue(form);
ElMessage.success(row.id ? '修改成功' : '新增成功');
onCancel();
emit('confirm-success');
} catch (error) {
console.log('error',error);
} finally{
loading.value = false;
}
} else { } else {
console.log("error submit!", fields); console.log("error submit!", fields);
} }
@@ -107,15 +121,19 @@ const onConfirm = async (formEl: FormInstance | undefined) => {
// 取消 // 取消
const onCancel = () => { const onCancel = () => {
ruleFormRef.value && ruleFormRef.value.resetFields() ruleFormRef.value && ruleFormRef.value.resetFields();
emit("update:dialogVisible", false); emit("update:dictVisible", false);
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-form-item-owerner{ .dict-manage {
:deep(.el-form-item) {
margin-bottom: 26px;
}
.el-form-item-owner {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 3px; gap: 3px;
}
} }
</style> </style>

View File

@@ -2,15 +2,49 @@
<div class="mj-dict"> <div class="mj-dict">
<stageBreadcrumbs title="组织管理"> <stageBreadcrumbs title="组织管理">
<template #content> <template #content>
<el-button :icon="Plus" type="primary" @click="addDict">新增字典</el-button> <el-button :icon="Plus" type="primary" @click="addDict"
>新增字典</el-button
>
</template> </template>
<template #action> <template #action>
<div class="mj-dict-actions"> <div class="mj-dict-actions">
<CommonFilter ref="filterPopover"></CommonFilter> <CommonFilter @on-hide="onPopoverHide">
<div class="mj-filter-content">
<div class="filter-header">
<span class="title">条件筛选</span>
<el-link type="primary" underline="never" class="reset-btn" @click="onReset"
>重置</el-link
>
</div>
<div class="filter-body">
<div class="filter-item">
<label>状态</label>
<el-select
v-model="filterForm.status"
placeholder="全部状态"
class="custom-select"
:teleported="false"
>
<el-option
:label="item.label"
:value="item.value"
v-for="(item, index) in DictManage.statusOptions"
:key="index"
/>
</el-select>
</div>
</div>
<el-button type="primary" class="apply-btn" @click="fetchTableData">应用筛选</el-button>
</div>
</CommonFilter>
<div class="search-dict-input"> <div class="search-dict-input">
<el-input <el-input
placeholder="搜索字典..." placeholder="搜索字典..."
class="auto-expand-input" class="auto-expand-input"
:prefix-icon="Search"
v-model="searchVal"
@keyup.enter="fetchTableData"
></el-input> ></el-input>
</div> </div>
</div> </div>
@@ -18,23 +52,200 @@
</stageBreadcrumbs> </stageBreadcrumbs>
<!-- Table内容 --> <!-- Table内容 -->
<CommonTable
ref="dictTableRef"
:columns="columns"
v-model:data="dataValue"
v-model:total="total"
pagination
:request-api="getTableData"
>
<!-- 编号内容显示 -->
<template #number="{ row, index }">
<span>#{{ formatIndex(index) }}</span>
</template>
<template #name="{ row }">
<span style="font-weight: 600">{{ row.name }}</span>
</template>
<!-- 编码标识 -->
<template #code="{ row }">
<el-tag size="small" type="info">{{ row.key }}</el-tag>
</template>
<!-- 状态插槽 -->
<template #status="{ row }">
<div
class="mj-status-dot"
:style="{
'--data-status-color': DictManage.statusDictColor[row.status],
}"
@click="handleDictStatus(row)"
>
{{ DictManage.statusDict[row.status] }}
</div>
</template>
<template #actions="{ row }">
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
<el-button link type="primary" @click="handlefieldsConfig(row)"
>字段配置</el-button
>
<el-button link type="danger" @click="handleDelete(row)"
>删除</el-button
>
</template>
</CommonTable>
<!-- 新增-编辑字典弹窗 --> <!-- 新增-编辑字典弹窗 -->
<dict-manage v-model:dialogVisible="visible" /> <dict-manage
v-model:dictVisible="dictVisible"
:row="selectItem"
@confirm-success="onConfirmSuccess"
/>
<!-- 字段配置弹窗 -->
<dictFieldConfig ref="fieldsConfigRef" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Plus } from "@element-plus/icons-vue"; import { Plus, Search } from "@element-plus/icons-vue";
import dictManage from "./DictManage.vue"; import CommonTable from "@/components/proTable/index.vue";
import dictFieldConfig from "./dictFieldConfig.vue";
import dictManage from "./dictManage.vue";
import dayjs from "dayjs";
import { getDictValues, deleteDictValue,disableDict,enableDict } from "@/api/stage/dict";
import { DictManage } from "@/dict";
import { formatIndex } from "@/utils/utils";
import { ElMessage } from "element-plus";
defineOptions({ name: "Dictionary" }); defineOptions({ name: "Dictionary" });
const visible = ref(false); const fieldsConfigRef = ref(null);
const dictTableRef = ref(null);
const dictVisible = ref<boolean>(false);
const searchVal = ref<string>("");
const total = ref<number>(0);
const filterForm = reactive({
status: "",
});
const selectItem = reactive({});
// 列表columns数据
const columns = [
{ prop: "id", label: "编号", width: "80", align: "center", slot: "number" },
{ prop: "name", label: "字典名称", align: "center", slot: "name" },
{
prop: "key",
label: "编码标识",
align: "center",
slot: "code",
},
{
prop: "status",
label: "状态",
align: "center",
slot: "status",
},
{
prop: "remark",
label: "备注说明",
align: "center",
showOverflowTooltip: true,
},
{
prop: "createTime",
label: "创建时间",
align: "center",
showOverflowTooltip: true,
formatter: (val) => {
return val.createTime
? dayjs(val.createTime).format("YYYY-MM-DD HH:mm")
: "-";
},
},
{
prop:'updateByName',
label:'最后修改人',
align:'center'
},
{ prop: "actions", label: "操作", align: "right", width: "200" },
];
// 返回的data数据信息
const dataValue = ref([]);
// popover关闭事件
const onPopoverHide = () =>{
filterForm.status = '';
}
// 筛选重置
const onReset = () =>{
filterForm.status = '';
fetchTableData();
}
// 获取当前的table数据信息
const getTableData = async (params) => {
try {
const response = await getDictValues({
...params,
keyword: searchVal.value,
...filterForm
});
return response;
} catch (error) {
console.log("getTableData Error", error);
}
};
// 搜索查询条件信息
const fetchTableData = () => {
dictTableRef.value && dictTableRef.value.refresh();
};
// 新增字典信息
const addDict = () => { const addDict = () => {
visible.value = true; dictVisible.value = true;
};
// 编辑字典信息
const handleEdit = (item) => {
addDict();
Object.assign(selectItem, item);
}; };
// 启用-禁用事件
const handleDictStatus = async (row)=>{
try {
row.status === 1 ? await disableDict(row.id) : await enableDict(row.id);
ElMessage.success('操作成功');
onConfirmSuccess();
} catch (error) {
console.log('error',error);
}
}
// 刷新Table数据信息
const onConfirmSuccess = () => {
dictTableRef.value && dictTableRef.value.refresh();
};
// TODO:字段配置
const handlefieldsConfig = (ite) => {
fieldsConfigRef.value.open(ite);
};
// 删除字典类型数据
const handleDelete = async (item) => {
ElMessageBox.confirm("确定要删除吗?", "提示", { type: "warning" })
.then(async () => {
try {
await deleteDictValue(item.id);
dictTableRef.value && dictTableRef.value.refresh();
} catch (error) {
console.log("fetch error", error);
}
})
.catch(() => {
console.log("cancel");
});
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.mj-dict { .mj-dict {
@@ -47,16 +258,17 @@ const addDict = () => {
gap: 14px; gap: 14px;
} }
.search-dict-input { .mj-status-dot {
width: 160px; cursor: pointer;
transition: width 0.3s ease; &::before {
content: "";
&:focus-within { display: inline-block;
width: 224px; width: 6px;
} height: 6px;
.auto-expand-input { border-radius: 50%;
width: 100%; background-color: var(--data-status-color);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); margin-right: 8px;
vertical-align: middle;
} }
} }
} }

View File

@@ -1,12 +1,11 @@
<template> <template>
<div class=""> <div class="mj-permission-management">
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {reactive,ref,onMounted} from "vue"
defineOptions({}) defineOptions({ name: "PermissionManagement"})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -5,8 +5,7 @@ import { VITE_APP_BASE_API } from "../../config.js";
import TokenManager from "@/utils/storage"; import TokenManager from "@/utils/storage";
import { getMockData, shouldUseMock } from "@/mock"; //mock数据信息 import { getMockData, shouldUseMock } from "@/mock"; //mock数据信息
const tokenManager = TokenManager.getInstance(); const tokenManager = TokenManager.getInstance();
const baseUrl = "/api"; //TODO: 本地调试需要修改为/api const baseUrl = import.meta.env.MODE === "development" ? "/api" : VITE_APP_BASE_API;
// 1. 锁和队列定义在类外部,确保全局唯一 // 1. 锁和队列定义在类外部,确保全局唯一
let isRefreshing = false; let isRefreshing = false;
let requestsQueue: Array<(token: string) => void> = []; let requestsQueue: Array<(token: string) => void> = [];
@@ -70,9 +69,7 @@ class HttpRequest {
this.instance.interceptors.response.use( this.instance.interceptors.response.use(
async (response: AxiosResponse) => { async (response: AxiosResponse) => {
const { data: res, config: originalRequest } = response; const { data: res, config: originalRequest } = response;
// 如果是登录接口就不要走全局的拦截 而是直接返回当前的数据信息
console.log("响应拦截器",res,originalRequest)
// TODO:如果是登录接口就不要走全局的拦截 而是直接返回当前的数据信息
if (this.isAuthEndpoint(originalRequest.url || "")) { if (this.isAuthEndpoint(originalRequest.url || "")) {
return res; return res;
} }
@@ -144,21 +141,6 @@ class HttpRequest {
} }
public request<T = any>(config: AxiosRequestConfig): Promise<ApiResponse<T>> { public request<T = any>(config: AxiosRequestConfig): Promise<ApiResponse<T>> {
// TODO:检查是否应该使用 Mock 数据
// const requestUrl = config.url || '';
// if (shouldUseMock(requestUrl)) {
// const mockData = getMockData(requestUrl, config.params, config.data);
// if (mockData) {
// console.log(`[Mock] 使用 Mock 数据: ${requestUrl}`, mockData);
// // 模拟网络延迟
// return new Promise((resolve) => {
// setTimeout(() => {
// resolve(mockData as ApiResponse<T>);
// }, 100);
// });
// }
// }
return this.instance(config) as unknown as Promise<ApiResponse<T>>; return this.instance(config) as unknown as Promise<ApiResponse<T>>;
} }

View File

@@ -147,8 +147,11 @@ const addDynamicRoutes = async () => {
allRoutes = [ allRoutes = [
{ {
code: "stage", code: "stage",
name: "后台管理", name: "管理中心",
icon: "", icon: "",
meta:{
title:'管理中心'
},
children: backendResponse, children: backendResponse,
}, },
]; ];

View File

@@ -1,37 +1,137 @@
@use './element.scss' as *; @use './element.scss' as *;
@use './stage.scss' as *; @use './stage.scss' as *;
html,body{ html,
body {
height: 100%; height: 100%;
margin:0; margin: 0;
padding:0; padding: 0;
} }
#app{ #app {
height: inherit; height: inherit;
} }
:root{ :root {
--mj-menu-header-height:#{$mj-menu-header-height}; --mj-menu-header-height:#{$mj-menu-header-height};
--mj-border-color:#{$mj-border-color}; --mj-border-color:#{$mj-border-color};
--mj-padding-standard:#{$mj-padding-standard}; --mj-padding-standard:#{$mj-padding-standard};
--mj-popper-radius: 8px;
} }
.filter-popper.el-popover.el-popper{ .filter-popper.el-popover.el-popper {
padding: 20px; --el-popover-padding: 0;
border-radius: 8px; border-radius: var(--mj-popper-radius);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
} }
// 全局重新element相关样式 // 全局重新element相关样式
.mj-input-form{ .mj-input-form {
.el-input{ .el-input {
--el-border-radius-base:10px; --el-border-radius-base: 10px;
--el-border-color:#E2E8F0; --el-border-color: #E2E8F0;
} }
} }
// 搜索框动画
.search-dict-input {
--default-width: 160px;
--max-width: 224px;
width: var(--default-width);
transition: width 0.3s ease;
&:focus-within {
width: var(--max-width);
}
.auto-expand-input {
--el-input-border-radius: 4px;
width: 100%;
}
}
// 字典状态全局样式
.mj-status-dot {
cursor: pointer;
&::before {
content: "";
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var(--data-status-color);
margin-right: 8px;
vertical-align: middle;
}
}
// 筛选框全局样式内容
.mj-filter-content {
width: 380px;
background: #fff;
border-radius: 8px;
padding: 20px;
box-sizing: border-box;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
border: 1px solid #ebeef5;
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.filter-header .title {
font-weight: bold;
font-size: 16px;
color: #303133;
}
.reset-btn {
font-size: 14px;
color: #2b65f6;
}
.filter-item {
margin-bottom: 20px;
label{
display: block;
font-size: 14px;
color: #909399;
margin-bottom: 8px;
font-weight: 500;
}
}
.custom-select {
width: 100%;
background-color: #f5f7fa;
box-shadow: none;
border: 1px solid #e4e7ed;
:deep(.el-input__wrapper) {
background-color: transparent;
box-shadow: none;
}
}
.apply-btn {
width: 100%;
height: 44px;
background-color: #2b65f6;
border-color: #2b65f6;
font-size: 16px;
font-weight: bold;
border-radius: 6px;
margin-top: 10px;
&:hover {
background-color: #407eff;
border-color: #407eff;
}
}
}

View File

@@ -20,6 +20,24 @@
} }
} }
.standard-ui-back-drawer{
@extend .standard-ui-drawer;
.el-drawer__header{
background-color: #FBFCFD;
&::after{
width: 100%;
left: 0;
}
}
.el-drawer__body{
padding: 0;
}
.el-drawer__footer{
background-color: #FBFCFD;
border-top: 1px solid #E5E7EB;
}
}
// 标注弹窗样式 // 标注弹窗样式
.standard-ui-dialog{ .standard-ui-dialog{

13
src/utils/utils.ts Normal file
View File

@@ -0,0 +1,13 @@
//vite引入图片
export function getImageUrl(url: string) {
return new URL(url, import.meta.url).href;
}
// 设置index前缀 如001 010 100种 默认兼容3位数
export const formatIndex = (index,padNum=3) => {
return String(index + 1).padStart(padNum, '0');
};