feat:完善字典管理模块
This commit is contained in:
12
components.d.ts
vendored
12
components.d.ts
vendored
@@ -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']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,3 @@ export const login = (data: { username: string; password: string }) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取用户信息
|
|
||||||
export const getUserInfo = () => {
|
|
||||||
return request.get('/api/user/info');
|
|
||||||
};
|
|
||||||
91
src/api/stage/dict/index.ts
Normal file
91
src/api/stage/dict/index.ts
Normal 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`)
|
||||||
|
}
|
||||||
BIN
src/assets/images/login/1.png
Normal file
BIN
src/assets/images/login/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/images/login/2.png
Normal file
BIN
src/assets/images/login/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/images/login/3.png
Normal file
BIN
src/assets/images/login/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/images/login/4.png
Normal file
BIN
src/assets/images/login/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
1
src/auto-imports.d.ts
vendored
1
src/auto-imports.d.ts
vendored
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
206
src/components/proTable/index.vue
Normal file
206
src/components/proTable/index.vue
Normal 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
29
src/dict/dictManage.ts
Normal 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
5
src/dict/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import DictManage from './dictManage';
|
||||||
|
|
||||||
|
const Dict = { DictManage }
|
||||||
|
export { DictManage }
|
||||||
|
export default Dict;
|
||||||
42
src/main.ts
42
src/main.ts
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
427
src/pages/stage/dict/dictFieldConfig.vue
Normal file
427
src/pages/stage/dict/dictFieldConfig.vue
Normal 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>
|
||||||
149
src/pages/stage/dict/dictFieldLevelManage.vue
Normal file
149
src/pages/stage/dict/dictFieldLevelManage.vue
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,8 +147,11 @@ const addDynamicRoutes = async () => {
|
|||||||
allRoutes = [
|
allRoutes = [
|
||||||
{
|
{
|
||||||
code: "stage",
|
code: "stage",
|
||||||
name: "后台管理",
|
name: "管理中心",
|
||||||
icon: "",
|
icon: "",
|
||||||
|
meta:{
|
||||||
|
title:'管理中心'
|
||||||
|
},
|
||||||
children: backendResponse,
|
children: backendResponse,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
13
src/utils/utils.ts
Normal 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');
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user