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']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
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']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
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']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
@@ -52,12 +58,18 @@ declare module 'vue' {
|
||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
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']
|
||||
PageForm: typeof import('./src/components/pageForm/index.vue')['default']
|
||||
ProTable: typeof import('./src/components/proTable/index.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
StageBreadcrumbs: typeof import('./src/components/stageBreadcrumbs/index.vue')['default']
|
||||
StandardMenu: typeof import('./src/components/standardMenu/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 {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue').EffectScope
|
||||
const ElMessage: typeof import('element-plus/es').ElMessage
|
||||
const ElMessageBox: typeof import('element-plus/es').ElMessageBox
|
||||
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
|
||||
const computed: typeof import('vue').computed
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
<template>
|
||||
<div class="mj-filter-group">
|
||||
<div class="mj-icon-container">
|
||||
<div :class="className ? className : 'mj-icon-container'">
|
||||
<el-popover
|
||||
ref="filterPopover"
|
||||
trigger="click"
|
||||
popper-class="filter-popper"
|
||||
placement="bottom-end"
|
||||
:teleported="true"
|
||||
width="auto"
|
||||
@hide="$emit('on-hide')"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="mj-icon-warp">
|
||||
<div class="mj-icon-item" title="筛选">
|
||||
<el-icon><Filter /></el-icon>
|
||||
</div>
|
||||
<slot name="filterLabel"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="filter-container">
|
||||
<div class="filter-container" @click.stop>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,7 +34,19 @@ import { Filter, Download } from "@element-plus/icons-vue";
|
||||
const filterPopover = ref(null);
|
||||
|
||||
// 定义事件:重置、应用、下载
|
||||
defineEmits(["download"]);
|
||||
defineEmits(["download",'on-hide']);
|
||||
|
||||
|
||||
defineProps({
|
||||
download:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
className:{
|
||||
type:String,
|
||||
default:''
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
defineExpose({
|
||||
@@ -48,7 +64,14 @@ defineExpose({
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
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 {
|
||||
display: flex;
|
||||
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>
|
||||
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);
|
||||
|
||||
// 导入全局的i18n文件
|
||||
const loadLocalMessages = async (lang: string) => {
|
||||
const loadLocalMessages = async () => {
|
||||
const messages: Record<string, any> = {};
|
||||
const locales = import.meta.glob("./locales/*.ts");
|
||||
for (const path in locales) {
|
||||
const locales = import.meta.glob("./locales/*.ts", { eager: true });
|
||||
|
||||
// 遍历所有匹配的文件
|
||||
Object.keys(locales).forEach((path) => {
|
||||
const lang = path.match(/\.\/locales\/(.+)\.ts$/)?.[1];
|
||||
if (lang) {
|
||||
const module = await locales[path]();
|
||||
messages[lang] = module.default;
|
||||
}
|
||||
if (lang && locales[path]) {
|
||||
messages[lang] = locales[path].default;
|
||||
}
|
||||
});
|
||||
|
||||
return messages;
|
||||
};
|
||||
@@ -46,19 +47,30 @@ const getLocalLang = () => {
|
||||
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(),
|
||||
messages: await loadLocalMessages(),
|
||||
messages: messages,
|
||||
legacy: false,
|
||||
});
|
||||
});
|
||||
|
||||
const elementLocale = getLocalLang() === "zh" ? zhCn : en;
|
||||
|
||||
app
|
||||
app
|
||||
.use(i18n)
|
||||
.use(router)
|
||||
.use(pinia)
|
||||
.use(Directives)
|
||||
.use(ElementPlus, { locale: elementLocale, size: "medium" })
|
||||
.use(ElementPlus, { locale: elementLocale, size: "default" })
|
||||
.mount("#app");
|
||||
};
|
||||
// 执行初始化
|
||||
initApp().catch(console.error);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="mj-aside-title" v-show="!isCollapse">
|
||||
{{ topTitle }}
|
||||
</div>
|
||||
<mjMenus
|
||||
<standardMenu
|
||||
class="mj-aside_menu"
|
||||
:isCollapse="isCollapse"
|
||||
:menuList="sideMenuList"
|
||||
@@ -28,7 +28,7 @@
|
||||
<el-container>
|
||||
<el-header class="mj-header-content">
|
||||
<!-- 左侧的菜单展示 -->
|
||||
<mjMenus
|
||||
<standardMenu
|
||||
:menuList="topLevelMenuList"
|
||||
mode="horizontal"
|
||||
:active-menu="selectedTopMenu"
|
||||
@@ -46,7 +46,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import mjMenus from "@/components/standardMenu/index.vue";
|
||||
import standardMenu from "@/components/standardMenu/index.vue";
|
||||
import { useUserStore } from "@/store";
|
||||
import { DArrowLeft, DArrowRight } from "@element-plus/icons-vue";
|
||||
import rightMenuGroup from './rightMenuGroup.vue';
|
||||
@@ -101,6 +101,11 @@ const topLevelMenuList = computed(() => {
|
||||
}).filter(itv=>itv.name !== 'stage');
|
||||
});
|
||||
|
||||
// 获取是管理后台中心的数据内容
|
||||
const backTitle = computed(()=>{
|
||||
return menuList.value.find(itv=>itv.name === 'stage')?.meta?.title || '-';
|
||||
})
|
||||
|
||||
// 后台管理点击获取列表
|
||||
const onStageManage = () =>{
|
||||
selectedTopMenu.value = '/stage';
|
||||
@@ -109,7 +114,7 @@ const onStageManage = () =>{
|
||||
const topTitle = computed(() => {
|
||||
return (
|
||||
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="action-group">
|
||||
<div class="action-item">
|
||||
<el-tooltip content="管理中心">
|
||||
<el-icon :size="16" @click="onStageManage"><Monitor /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<el-badge is-dot class="notice-badge">
|
||||
|
||||
@@ -17,10 +17,9 @@
|
||||
<div class="footer-users">
|
||||
<div class="avatar-group">
|
||||
<img
|
||||
v-for="i in 4"
|
||||
:key="i"
|
||||
:src="`https://i.pravatar.cc/100?img=${i + 10}`"
|
||||
alt="user"
|
||||
v-for="(img,imgIndex) in picList"
|
||||
:key="imgIndex"
|
||||
:src="img.src"
|
||||
/>
|
||||
</div>
|
||||
<span class="user-count">超过 5,000+ 行业领先企业正在使用</span>
|
||||
@@ -43,7 +42,7 @@
|
||||
<el-form-item label="账号/邮箱" class="account-item">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
placeholder="admin@figmamake.com"
|
||||
placeholder="请输入账号/邮箱"
|
||||
:prefix-icon="Message"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -58,7 +57,7 @@
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="••••••••"
|
||||
placeholder="请输入密码"
|
||||
:prefix-icon="Lock"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -95,6 +94,18 @@ import TokenManager from '@/utils/storage';
|
||||
|
||||
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 route = useRoute();
|
||||
const userStore = useUserStore();
|
||||
@@ -104,7 +115,8 @@ const loading = ref(false);
|
||||
|
||||
const loginForm = reactive({
|
||||
username: "user",
|
||||
password: "password"
|
||||
password: "password",
|
||||
remember:false
|
||||
});
|
||||
|
||||
const loginRules: FormRules = {
|
||||
@@ -144,8 +156,9 @@ const handleLogin = async () => {
|
||||
|
||||
// 保存用户信息
|
||||
userStore.setToken(refresh_token);
|
||||
// TODO:获取用户信息
|
||||
// 获取用户信息
|
||||
userStore.setUserInfo(userInfo);
|
||||
// TODO:记住密码功能
|
||||
|
||||
ElMessage.success("登录成功");
|
||||
|
||||
@@ -269,7 +282,7 @@ $bg-light: #f8faff;
|
||||
// --- 右侧登录区 ---
|
||||
.right-section {
|
||||
flex: 1;
|
||||
background: white;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -289,7 +302,6 @@ $bg-light: #f8faff;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
// 深度作用选择器统一处理 Element Plus 内部样式
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
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"
|
||||
:title
|
||||
:width="558"
|
||||
:model-value="dialogVisible"
|
||||
:model-value="dictVisible"
|
||||
@close="onCancel"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@@ -19,9 +19,9 @@
|
||||
<el-form-item label="字典名称:" prop="name">
|
||||
<el-input placeholder="请输入字典名称" v-model="form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="type">
|
||||
<el-form-item prop="key">
|
||||
<template #label>
|
||||
<div class="el-form-item-owerner">
|
||||
<div class="el-form-item-owner">
|
||||
<span>字典类型</span>
|
||||
<el-tooltip content="字典类型" placement="top-start">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
@@ -29,76 +29,90 @@
|
||||
<span>:</span>
|
||||
</div>
|
||||
</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 label="管理人员:">
|
||||
<el-input
|
||||
placeholder="请选择管理人员"
|
||||
v-model="form.manager"
|
||||
></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-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"
|
||||
:row="4"
|
||||
: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">确认</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 type { FormInstance, FormRules } from "element-plus";
|
||||
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
||||
import { QuestionFilled } from "@element-plus/icons-vue";
|
||||
import { addDictValue, updateDictValue } from "@/api/stage/dict";
|
||||
defineOptions({ name: "DictManage" });
|
||||
|
||||
const { dialogVisible = false,row={},title="字典管理" } = defineProps<{
|
||||
dialogVisible: boolean;
|
||||
row: any;
|
||||
title?:string
|
||||
const loading = ref(false);
|
||||
const {
|
||||
dictVisible = false,
|
||||
row = {},
|
||||
title = "字典管理",
|
||||
} = defineProps<{
|
||||
dictVisible: boolean;
|
||||
row?: any;
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:dialogVisible", value: boolean): void;
|
||||
(e: "close"): void;
|
||||
(e: "confirm"): void;
|
||||
(e: "update:dictVisible", value: boolean): void;
|
||||
(e: "confirm-success"): void;
|
||||
}>();
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
|
||||
const form = reactive({
|
||||
name: "",
|
||||
type: "",
|
||||
manager: "",
|
||||
status: "",
|
||||
key: "",
|
||||
status: 1,
|
||||
remark: "",
|
||||
});
|
||||
|
||||
// 监听组件中传递的数据-然后进行复制操作
|
||||
watch(()=>row,(newRow)=>{
|
||||
if (newRow && Object.keys(newRow).length > 0) {
|
||||
Object.assign(form, newRow);
|
||||
}
|
||||
},{deep:true})
|
||||
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: "请输入字典名称", trigger: "blur" }],
|
||||
type: [{ required: true, message: "请输入字典类型", trigger: "blur" }],
|
||||
resource: [{ required: true, message: "请选择状态", trigger: "change" }],
|
||||
key: [{ required: true, message: "请输入字典类型", trigger: "blur" }],
|
||||
status: [{ required: true, message: "请选择状态", trigger: "change" }],
|
||||
});
|
||||
|
||||
// 确定
|
||||
const onConfirm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
await formEl.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
emit("update:dialogVisible", false);
|
||||
emit("confirm",form);
|
||||
loading.value = true;
|
||||
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 {
|
||||
console.log("error submit!", fields);
|
||||
}
|
||||
@@ -107,15 +121,19 @@ const onConfirm = async (formEl: FormInstance | undefined) => {
|
||||
|
||||
// 取消
|
||||
const onCancel = () => {
|
||||
ruleFormRef.value && ruleFormRef.value.resetFields()
|
||||
emit("update:dialogVisible", false);
|
||||
ruleFormRef.value && ruleFormRef.value.resetFields();
|
||||
emit("update:dictVisible", false);
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.el-form-item-owerner{
|
||||
.dict-manage {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
.el-form-item-owner {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -2,15 +2,49 @@
|
||||
<div class="mj-dict">
|
||||
<stageBreadcrumbs title="组织管理">
|
||||
<template #content>
|
||||
<el-button :icon="Plus" type="primary" @click="addDict">新增字典</el-button>
|
||||
<el-button :icon="Plus" type="primary" @click="addDict"
|
||||
>新增字典</el-button
|
||||
>
|
||||
</template>
|
||||
<template #action>
|
||||
<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">
|
||||
<el-input
|
||||
placeholder="搜索字典..."
|
||||
class="auto-expand-input"
|
||||
:prefix-icon="Search"
|
||||
v-model="searchVal"
|
||||
@keyup.enter="fetchTableData"
|
||||
></el-input>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,23 +52,200 @@
|
||||
</stageBreadcrumbs>
|
||||
|
||||
<!-- 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>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import dictManage from "./DictManage.vue";
|
||||
import { Plus, Search } from "@element-plus/icons-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" });
|
||||
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 = () => {
|
||||
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>
|
||||
<style lang="scss" scoped>
|
||||
.mj-dict {
|
||||
@@ -47,16 +258,17 @@ const addDict = () => {
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.search-dict-input {
|
||||
width: 160px;
|
||||
transition: width 0.3s ease;
|
||||
|
||||
&:focus-within {
|
||||
width: 224px;
|
||||
}
|
||||
.auto-expand-input {
|
||||
width: 100%;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="mj-permission-management">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {reactive,ref,onMounted} from "vue"
|
||||
|
||||
defineOptions({})
|
||||
defineOptions({ name: "PermissionManagement"})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ import { VITE_APP_BASE_API } from "../../config.js";
|
||||
import TokenManager from "@/utils/storage";
|
||||
import { getMockData, shouldUseMock } from "@/mock"; //mock数据信息
|
||||
const tokenManager = TokenManager.getInstance();
|
||||
const baseUrl = "/api"; //TODO: 本地调试需要修改为/api
|
||||
|
||||
const baseUrl = import.meta.env.MODE === "development" ? "/api" : VITE_APP_BASE_API;
|
||||
// 1. 锁和队列定义在类外部,确保全局唯一
|
||||
let isRefreshing = false;
|
||||
let requestsQueue: Array<(token: string) => void> = [];
|
||||
@@ -70,9 +69,7 @@ class HttpRequest {
|
||||
this.instance.interceptors.response.use(
|
||||
async (response: AxiosResponse) => {
|
||||
const { data: res, config: originalRequest } = response;
|
||||
|
||||
console.log("响应拦截器",res,originalRequest)
|
||||
// TODO:如果是登录接口就不要走全局的拦截 而是直接返回当前的数据信息
|
||||
// 如果是登录接口就不要走全局的拦截 而是直接返回当前的数据信息
|
||||
if (this.isAuthEndpoint(originalRequest.url || "")) {
|
||||
return res;
|
||||
}
|
||||
@@ -144,21 +141,6 @@ class HttpRequest {
|
||||
}
|
||||
|
||||
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>>;
|
||||
}
|
||||
|
||||
|
||||
@@ -147,8 +147,11 @@ const addDynamicRoutes = async () => {
|
||||
allRoutes = [
|
||||
{
|
||||
code: "stage",
|
||||
name: "后台管理",
|
||||
name: "管理中心",
|
||||
icon: "",
|
||||
meta:{
|
||||
title:'管理中心'
|
||||
},
|
||||
children: backendResponse,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,37 +1,137 @@
|
||||
@use './element.scss' as *;
|
||||
@use './stage.scss' as *;
|
||||
|
||||
html,body{
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin:0;
|
||||
padding:0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#app{
|
||||
#app {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
|
||||
:root{
|
||||
:root {
|
||||
--mj-menu-header-height:#{$mj-menu-header-height};
|
||||
--mj-border-color:#{$mj-border-color};
|
||||
--mj-padding-standard:#{$mj-padding-standard};
|
||||
--mj-popper-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
.filter-popper.el-popover.el-popper{
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
.filter-popper.el-popover.el-popper {
|
||||
--el-popover-padding: 0;
|
||||
border-radius: var(--mj-popper-radius);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
// 全局重新element相关样式
|
||||
.mj-input-form{
|
||||
.el-input{
|
||||
--el-border-radius-base:10px;
|
||||
--el-border-color:#E2E8F0;
|
||||
.mj-input-form {
|
||||
.el-input {
|
||||
--el-border-radius-base: 10px;
|
||||
--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{
|
||||
|
||||
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