lzq 2025-12-04 17:31:03 +08:00
parent ebaebc926a
commit d32b16ac11
23 changed files with 1598 additions and 55 deletions

View File

@ -76,7 +76,7 @@ router.beforeEach((to, from) => {
} else { } else {
return { return {
replace: true, replace: true,
name: 'role', path: to.fullPath,
} }
} }
} }
@ -109,6 +109,7 @@ export function reloadRouter() {
routNames.push('menus') routNames.push('menus')
routNames.push('user') routNames.push('user')
routNames.push('role') routNames.push('role')
routNames.push('dict')
if (Colls.isEmpty(routNames)) { if (Colls.isEmpty(routNames)) {
return false return false

View File

@ -0,0 +1,30 @@
import '@/components/a-icon/iconfont.css'
import {
type IconName,
icons,
} from '@/components/a-icon/iconfont.ts'
import {
computed,
defineComponent,
} from 'vue'
export default defineComponent(
(props, {attrs}) => {
const prefixText = icons.css_prefix_text
const fontFamily = icons.font_family
const icon = computed(() => {
return props.name == null ? [] : [ fontFamily, prefixText + props.name ]
})
return () => (<><i class={icon.value} {...attrs}></i></>)
},
{
props: {
name: {
type: String as PropType<IconName>,
required: false,
validator: (_: string) => true,
},
},
name: 'AIcon',
})

View File

@ -0,0 +1,107 @@
@font-face {
font-family: "iconfont"; /* Project id 4985351 */
src: url('@/components/a-icon/iconfont.woff2?t=1764810386158') format('woff2'),
url('@/components/a-icon/iconfont.woff?t=1764810386158') format('woff'),
url('@/components/a-icon/iconfont.ttf?t=1764810386158') format('truetype');
}
.iconfont {
font-family: "iconfont", serif !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-bianji:before {
content: "\e604";
}
.icon-koukuanliebiao:before {
content: "\e63b";
}
.icon-yuechongzhi:before {
content: "\e8f5";
}
.icon-shanchudingdan-mian:before {
content: "\e6d7";
}
.icon-quxiaodingdan:before {
content: "\e64a";
}
.icon-lishiguiji-lan:before {
content: "\e672";
}
.icon-guobangdanju:before {
content: "\e66c";
}
.icon-dianzixiaopiao:before {
content: "\e64d";
}
.icon-xiangqing:before {
content: "\e611";
}
.icon-guanliandanju:before {
content: "\e61d";
}
.icon-track:before {
content: "\e603";
}
.icon-guanliandanxuan:before {
content: "\e68f";
}
.icon-dingdanxiangqing:before {
content: "\e6bf";
}
.icon-tongzhi:before {
content: "\e86a";
}
.icon-yujingguanli:before {
content: "\e61b";
}
.icon-shujuguanli:before {
content: "\e70c";
}
.icon-jiaoseguanli:before {
content: "\e62f";
}
.icon-yingyongyonghuguanli:before {
content: "\e6aa";
}
.icon-shangpinguanli:before {
content: "\fcf3";
}
.icon-xitongguanli:before {
content: "\e85c";
}
.icon-pinleiguanli:before {
content: "\e63d";
}
.icon-qiyezhuce:before {
content: "\e6a1";
}
.icon-shenheguanli:before {
content: "\e639";
}

View File

@ -0,0 +1,181 @@
export const icons = {
'id': '4985351',
'name': '再昇云',
'font_family': 'iconfont',
'css_prefix_text': 'icon-',
'description': '',
'glyphs': [
{
'icon_id': '8582929',
'name': '编辑/修改',
'font_class': 'bianji',
'unicode': 'e604',
'unicode_decimal': 58884,
},
{
'icon_id': '41408341',
'name': '扣款',
'font_class': 'koukuanliebiao',
'unicode': 'e63b',
'unicode_decimal': 58939,
},
{
'icon_id': '33376724',
'name': '余额充值',
'font_class': 'yuechongzhi',
'unicode': 'e8f5',
'unicode_decimal': 59637,
},
{
'icon_id': '6949389',
'name': '删除订单;报表;清单',
'font_class': 'shanchudingdan-mian',
'unicode': 'e6d7',
'unicode_decimal': 59095,
},
{
'icon_id': '16695459',
'name': '取消订单',
'font_class': 'quxiaodingdan',
'unicode': 'e64a',
'unicode_decimal': 58954,
},
{
'icon_id': '14443392',
'name': '历史轨迹-蓝',
'font_class': 'lishiguiji-lan',
'unicode': 'e672',
'unicode_decimal': 58994,
},
{
'icon_id': '44180887',
'name': '过磅单据',
'font_class': 'guobangdanju',
'unicode': 'e66c',
'unicode_decimal': 58988,
},
{
'icon_id': '26397534',
'name': '电子小票',
'font_class': 'dianzixiaopiao',
'unicode': 'e64d',
'unicode_decimal': 58957,
},
{
'icon_id': '12814001',
'name': '详情',
'font_class': 'xiangqing',
'unicode': 'e611',
'unicode_decimal': 58897,
},
{
'icon_id': '9777840',
'name': '关联单据',
'font_class': 'guanliandanju',
'unicode': 'e61d',
'unicode_decimal': 58909,
},
{
'icon_id': '28095045',
'name': '准运证',
'font_class': 'track',
'unicode': 'e603',
'unicode_decimal': 58883,
},
{
'icon_id': '18446165',
'name': '关联单选',
'font_class': 'guanliandanxuan',
'unicode': 'e68f',
'unicode_decimal': 59023,
},
{
'icon_id': '8725687',
'name': '订单详情',
'font_class': 'dingdanxiangqing',
'unicode': 'e6bf',
'unicode_decimal': 59071,
},
{
'icon_id': '23500943',
'name': '通知',
'font_class': 'tongzhi',
'unicode': 'e86a',
'unicode_decimal': 59498,
},
{
'icon_id': '27250248',
'name': '预警管理',
'font_class': 'yujingguanli',
'unicode': 'e61b',
'unicode_decimal': 58907,
},
{
'icon_id': '25301786',
'name': '字典管理',
'font_class': 'shujuguanli',
'unicode': 'e70c',
'unicode_decimal': 59148,
},
{
'icon_id': '3590945',
'name': '角色管理',
'font_class': 'jiaoseguanli',
'unicode': 'e62f',
'unicode_decimal': 58927,
},
{
'icon_id': '20853364',
'name': '用户管理',
'font_class': 'yingyongyonghuguanli',
'unicode': 'e6aa',
'unicode_decimal': 59050,
},
{
'icon_id': '25007161',
'name': '品类管理',
'font_class': 'shangpinguanli',
'unicode': 'fcf3',
'unicode_decimal': 64755,
},
{
'icon_id': '9206620',
'name': '系统管理',
'font_class': 'xitongguanli',
'unicode': 'e85c',
'unicode_decimal': 59484,
},
{
'icon_id': '20136570',
'name': '菜单管理',
'font_class': 'pinleiguanli',
'unicode': 'e63d',
'unicode_decimal': 58941,
},
{
'icon_id': '15689628',
'name': '公司审核',
'font_class': 'qiyezhuce',
'unicode': 'e6a1',
'unicode_decimal': 59041,
},
{
'icon_id': '5468041',
'name': '审核管理',
'font_class': 'shenheguanli',
'unicode': 'e639',
'unicode_decimal': 58937,
},
],
} as const
export type IconName = (typeof icons.glyphs)[number]['font_class']
export interface IconGlyphs {
icon_id: string
name: string
font_class: IconName
unicode: string
unicode_decimal: number
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -14,8 +14,9 @@
width 100%; width 100%;
overflow auto overflow auto
padding 16px padding 16px
box-sizing border-box box-sizing border-box
box-shadow: rgba(0, 0, 0, 0.12) 0px 0px 12px 0px; box-shadow: rgba(30, 35, 43, 0.16) 0px 2px 10px 0px;
background-color: white; background-color: white;
display flex display flex
flex-direction column flex-direction column

View File

@ -17,9 +17,11 @@ declare module 'vue' {
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElEmpty: typeof import('element-plus/es')['ElEmpty'] ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
@ -34,6 +36,7 @@ declare module 'vue' {
ElMain: typeof import('element-plus/es')['ElMain'] ElMain: typeof import('element-plus/es')['ElMain']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable'] ElTable: typeof import('element-plus/es')['ElTable']
@ -61,9 +64,11 @@ declare global {
const ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] const ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
const ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] const ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
const ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] const ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
const ElCollapse: typeof import('element-plus/es')['ElCollapse']
const ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] const ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
const ElContainer: typeof import('element-plus/es')['ElContainer'] const ElContainer: typeof import('element-plus/es')['ElContainer']
const ElDialog: typeof import('element-plus/es')['ElDialog'] const ElDialog: typeof import('element-plus/es')['ElDialog']
const ElDivider: typeof import('element-plus/es')['ElDivider']
const ElDropdown: typeof import('element-plus/es')['ElDropdown'] const ElDropdown: typeof import('element-plus/es')['ElDropdown']
const ElEmpty: typeof import('element-plus/es')['ElEmpty'] const ElEmpty: typeof import('element-plus/es')['ElEmpty']
const ElForm: typeof import('element-plus/es')['ElForm'] const ElForm: typeof import('element-plus/es')['ElForm']
@ -78,6 +83,7 @@ declare global {
const ElMain: typeof import('element-plus/es')['ElMain'] const ElMain: typeof import('element-plus/es')['ElMain']
const ElOption: typeof import('element-plus/es')['ElOption'] const ElOption: typeof import('element-plus/es')['ElOption']
const ElPagination: typeof import('element-plus/es')['ElPagination'] const ElPagination: typeof import('element-plus/es')['ElPagination']
const ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
const ElSelect: typeof import('element-plus/es')['ElSelect'] const ElSelect: typeof import('element-plus/es')['ElSelect']
const ElSwitch: typeof import('element-plus/es')['ElSwitch'] const ElSwitch: typeof import('element-plus/es')['ElSwitch']
const ElTable: typeof import('element-plus/es')['ElTable'] const ElTable: typeof import('element-plus/es')['ElTable']

View File

@ -8,6 +8,9 @@ import {
type MenuItemRegistered, type MenuItemRegistered,
} from 'element-plus' } from 'element-plus'
import { elIcons } from '@/common/element/element.ts' import { elIcons } from '@/common/element/element.ts'
import AIcon from '@/components/a-icon/AIcon.tsx'
import type { IconName } from '@/components/a-icon/iconfont.ts'
import styles from '@/pages/a-frame/aaside.module.styl'
export interface Menu extends G.TreeNode { export interface Menu extends G.TreeNode {
// Id // Id
@ -35,7 +38,7 @@ export interface Menu extends G.TreeNode {
} }
export default defineComponent( export default defineComponent(
({menus}, {emit}) => { (props, {emit}) => {
const onMenuClick = (it: MenuItemRegistered) => emit('menuClick', it.index) const onMenuClick = (it: MenuItemRegistered) => emit('menuClick', it.index)
const renderMenu = (it: Menu) => { const renderMenu = (it: Menu) => {
let renderChildNode: (() => VNode[] | undefined) | undefined = undefined let renderChildNode: (() => VNode[] | undefined) | undefined = undefined
@ -47,7 +50,10 @@ export default defineComponent(
case 'Catalog': { case 'Catalog': {
currentNode = (<ElSubMenu index={it.id}> currentNode = (<ElSubMenu index={it.id}>
{{ {{
title: () => <span>{it.title}</span>, title: () => (<>
<AIcon class={styles.aIcon} name={it.icon as IconName}/>
<span>{it.title}</span>
</>),
default: renderChildNode, default: renderChildNode,
}} }}
</ElSubMenu>) </ElSubMenu>)
@ -64,7 +70,8 @@ export default defineComponent(
case 'Page': { case 'Page': {
currentNode = (<ElMenuItem index={it.id} onClick={onMenuClick}> currentNode = (<ElMenuItem index={it.id} onClick={onMenuClick}>
{{ {{
default: () => <span>{it.title}</span>, title: () => <span>{it.title}</span>,
default: () => <AIcon class={styles.aIcon} name={it.icon as IconName}/>,
}} }}
</ElMenuItem>) </ElMenuItem>)
break break
@ -77,12 +84,12 @@ export default defineComponent(
const isCollapse = ref(false) const isCollapse = ref(false)
return () => (<> return () => (<>
<ElMenu collapse={isCollapse.value} style={{height: '100%', overflow: 'auto', '--el-menu-base-level-padding': '10px'}} class={'menus'}> <ElMenu collapse={isCollapse.value} style={{height: '100%', overflow: 'auto', '--el-menu-base-level-padding': '10px'}} class={[ styles.aMenus, 'menus' ]}>
{{ {{
default: () => menus.map(renderMenu), default: () => props.menus.map(renderMenu),
}} }}
</ElMenu> </ElMenu>
<ElButton style={{position: 'absolute', right: 0, bottom: 0, width: '32px', height: '32px'}} onClick={() => { <ElButton style={{position: 'absolute', right: '6px', bottom: '6px', width: '32px', height: '32px'}} onClick={() => {
isCollapse.value = !isCollapse.value isCollapse.value = !isCollapse.value
}}> }}>
<ElIcon style={{cursor: 'pointer'}}> <ElIcon style={{cursor: 'pointer'}}>

View File

@ -70,13 +70,15 @@ onUnmounted(() => {
height 100% height 100%
width 100%; width 100%;
overflow hidden overflow hidden
box-shadow: inset rgba(0, 0, 0, 0.12) 0px 0px 12px 0px;
background-color #F7F9FC
& > header { & > header {
display flex display flex
justify-content space-between justify-content space-between
border-bottom 1px solid #E5E7EB; border-bottom 1px solid #E5E7EB;
height 60px height 60px
background-color white
& > div:first-child { & > div:first-child {
height: 100%; height: 100%;
display: flex; display: flex;
@ -109,10 +111,10 @@ onUnmounted(() => {
& > main { & > main {
height 100% height 100%
width calc(100% - 300px) width calc(100% - 300px)
padding 8px padding 5px
overflow auto overflow auto
background-color #F7F9FC background-color #F7F9FC
box-shadow: inset rgba(0, 0, 0, 0.12) 0px 0px 12px 0px; //box-shadow: inset rgba(0, 0, 0, 0.12) 0px 0px 12px 0px;
} }
} }
} }

View File

@ -0,0 +1,11 @@
.a-menus {
}
.a-icon {
vertical-align: middle;
margin-right: 5px;
width: var(--el-menu-icon-width);
text-align: center;
font-size: 18px;
flex-shrink: 0;
}

View File

@ -0,0 +1,203 @@
<template>
<div>
<div class="tool-bar">
<ElForm inline @submit.prevent="paging">
<ElFormItem>
<ElInput
v-model="searchForm.dictName"
clearable
placeholder="请输入要搜索的文字"
@clear="reset"/>
</ElFormItem>
<ElFormItem>
<ElButton :icon="elIcons.Search" :loading="searching" native-type="submit" type="primary">搜索</ElButton>
<ElButton :icon="elIcons.Plus" type="primary" @click="addHandler"></ElButton>
</ElFormItem>
</ElForm>
</div>
<ElTable v-loading="searching" :data="tableData"
cell-class-name="table-cell"
class="table-list"
empty-text="暂无数据"
header-row-class-name="table-header"
row-key="id">
<ElTableColumn label="字典标识" prop="dictKey"/>
<ElTableColumn label="字典名称" prop="dictName"/>
<ElTableColumn label="备注" prop="memo"/>
<ElTableColumn label="操作" width="180">
<template #default="scope">
<div class="action-btn">
<el-popconfirm
cancel-button-text="否"
cancel-button-type="primary"
confirm-button-text="是"
confirm-button-type="danger"
placement="top"
title="是否删除当前数据?"
width="180"
@confirm="delHandler(scope)">
<template #reference>
<ElButton :loading="deling" text type="danger">删除</ElButton>
</template>
</el-popconfirm>
<ElButton text type="primary" @click="modifyHandler(scope)"></ElButton>
<ElButton text type="primary" @click="selectDictHandle(scope)"></ElButton>
</div>
</template>
</ElTableColumn>
</ElTable>
<ElPagination
:page-size="pagination.size"
:pager-count="pagination.pages"
:total="pagination.total"
class="pagination"
layout="prev, pager, next"
@change="pageChangeHandler"/>
<DictForm ref="dictForm" @edit-succ="paging"/>
</div>
</template>
<script lang="ts" setup>
import DictApi from '@/pages/sys/dict/dict-api.ts'
import { elIcons } from '@/common/element/element.ts'
import DictForm from '@/pages/sys/dict/DictForm.vue'
const tableData = ref<DictTypes.SearchDictResult[]>([])
const searchForm = reactive<DictTypes.SearchDictParam>(
{
orders: 'dict_key,id',
current: 1,
size: 20,
})
const searching = ref(false)
const deling = ref(false)
const dictFormIns = useTemplateRef<InstanceType<typeof DictForm>>('dictForm')
const pagination = reactive<G.Pagination>({
total: 0,
pages: 0,
current: 1,
size: 20,
})
function showDialog(data?: DictTypes.SearchDictResult) {
dictFormIns.value?.open(data)
}
function delHandler({row}: { row: DictTypes.SearchDictResult }) {
deling.value = true
DictApi.del([ row.id! ])
.then(() => {
ElMessage.success('删除成功')
paging()
})
.finally(() => {
deling.value = false
})
}
function modifyHandler({row}: { row: DictTypes.SearchDictResult }) {
showDialog(row)
}
function addHandler() {
showDialog()
}
const emits = defineEmits([ 'searchDict' ])
function selectDictHandle({row}: { row: DictTypes.SearchDictResult }) {
emits('searchDict', row)
}
function pageChangeHandler(currentPage: number, pageSize: number) {
searchForm.current = currentPage
searchForm.size = pageSize
paging()
}
function reset() {
Object.assign(searchForm, {})
paging()
}
function paging() {
searching.value = true
DictApi.paging(searchForm)
.then(res => {
tableData.value = res.data?.records ?? []
Object.assign(pagination, {
total: res.data?.total ?? 0,
pages: res.data?.pages ?? 0,
current: res.data?.current ?? 1,
size: res.data?.size ?? 20,
})
})
.finally(() => {
searching.value = false
})
}
onMounted(() => {
paging()
})
</script>
<style lang="stylus" scoped>
.table-list {
flex 1;
width 100%
:deep(.table-header) {
color #454C59
th {
background-color #EDF1F7
font-weight 500
position relative
& > div {
display flex
gap 5px
align-items center
}
&:not(:first-child) > div::before {
position: absolute;
top: 50%;
left: 1px;
width: 1px;
background-color: #D3D7DE;
transform: translateY(-50%);
content: "";
height 50%
}
}
}
:deep(.table-cell) {
color #2F3540
}
.action-btn {
width 100%
display flex
flex-wrap wrap
& > button {
margin 0
}
}
}
.tool-bar {
display flex
justify-content space-between
margin 0 0 20px 0
}
.pagination {
justify-content: end;
margin: 8px;
}
</style>

View File

@ -50,30 +50,48 @@
<ElInput v-model="menuForm.routePath" :min="0" placeholder="路由地址"/> <ElInput v-model="menuForm.routePath" :min="0" placeholder="路由地址"/>
</ElFormItem> </ElFormItem>
<ElFormItem <ElFormItem
label="编码"> label="编码">
<ElInput v-model="menuForm.sn" <ElInput v-model="menuForm.sn"
:disabled="status === 'view'" :disabled="status === 'view'"
:min="0" placeholder="编码"/> :min="0" placeholder="编码"/>
</ElFormItem> </ElFormItem>
<ElFormItem <ElFormItem label="图标">
label="图标">
<ElDropdown closable header="图标列表" <ElDropdown closable header="图标列表"
placement="bottom" placement="bottom"
style="width: 100%" trigger="click"> style="width: 100%" trigger="click">
<ElInput v-model="menuForm.icon" <ElInput v-model="menuForm.iconName"
placeholder="选择图标"
:disabled="status === 'view'" :disabled="status === 'view'"
placeholder="选择图标"/> readonly>
<template #suffix>
<AIcon :name="menuForm.icon!"/>
</template>
</ElInput>
<template #dropdown> <template #dropdown>
<ElTable <div class="dropdown-table-wrapper">
:data="iconTableDataSource" <ElTable
empty-text="暂无数据" ref="dropdownTable"
style="width: 324px;" :data="iconTableDataSource"
> empty-text="暂无数据"
<ElTableColumn label="图标" prop="name"/> highlight-current-row
<ElTableColumn label="名称" prop="name"/> style="width: 324px;"
</ElTable> @current-change="currentChangeHandler"
>
<ElTableColumn label="#" type="index"/>
<ElTableColumn label="图标" prop="font_class">
<template #default="scope">
<AIcon :name="scope?.row?.font_class"/>
</template>
</ElTableColumn>
<ElTableColumn label="名称" prop="name"/>
</ElTable>
<ElPagination
:page-size="pagination.size"
:pager-count="pagination.pages"
:total="pagination.total"
layout="prev, pager, next"
@change="pageChangeHandler"/>
</div>
</template> </template>
</ElDropdown> </ElDropdown>
</ElFormItem> </ElFormItem>
@ -86,7 +104,6 @@
style="width: 100%" style="width: 100%"
/> />
</ElFormItem> </ElFormItem>
</ElForm> </ElForm>
<template #footer> <template #footer>
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton> <ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
@ -104,39 +121,52 @@ import {
import MenuApi from '@/pages/sys/menus/menu-api.ts' import MenuApi from '@/pages/sys/menus/menu-api.ts'
import Colls from '@/common/utils/colls.ts' import Colls from '@/common/utils/colls.ts'
import Strings from '@/common/utils/strings.ts' import Strings from '@/common/utils/strings.ts'
import { ElMessage } from 'element-plus' import {
ElMessage,
type TableInstance,
} from 'element-plus'
import {
type IconGlyphs,
icons,
} from '@/components/a-icon/iconfont.ts'
import AIcon from '@/components/a-icon/AIcon.tsx'
interface IconData { const pagination = reactive<G.Pagination>({
name: string total: icons.glyphs.length,
pages: Math.ceil(icons.glyphs.length / 5),
current: 1,
size: 5,
})
function pageChangeHandler(currentPage: number, pageSize: number) {
pagination.current = currentPage
pagination.size = pageSize
iconTableDataSource.value = icons.glyphs.filter((_, i) => {
return i >= (currentPage - 1) * pageSize && i < currentPage * pageSize
})
} }
const selectedRowKeys = ref<string[]>([]) const dropdownTableIns = useTemplateRef<TableInstance>('dropdownTable')
const iconTableDataSource = ref<IconData[]>([].filter((_, i) => { const menuForm = reactive<MenuTypes.MenuForm>({
icon: 'dianzixiaopiao',
})
const iconTableDataSource = ref<IconGlyphs[]>(icons.glyphs.filter((_, i) => {
return i >= 0 && i < 5 return i >= 0 && i < 5
})) }))
function currentChangeHandler(val?: IconGlyphs) {
console.log(val)
if (val == null) return
menuForm.icon = val.font_class
menuForm.iconName = val.name
}
const showDialog = ref(false) const showDialog = ref(false)
const submiting = ref(false) const submiting = ref(false)
const status = ref<'add' | 'view' | 'modify'>('add') const status = ref<'add' | 'view' | 'modify'>('add')
const pagination = reactive({
pageIndex: 1,
pageSize: 5,
total: 20,
size: 'sm',
showTotal: true,
onChange(pageIndex: number, pageSize: number) {
pagination.pageIndex = pageIndex
pagination.pageSize = pageSize
iconTableDataSource.value = [].filter((_, i) => {
return i >= (pageIndex - 1) * pageSize && i < pageIndex * pageSize
})
},
})
const menuForm = reactive<MenuTypes.MenuForm>({})
const menuCategoryData = computed(() => { const menuCategoryData = computed(() => {
const data: { key: string, label: string }[] = [] const data: { key: string, label: string }[] = []
for (const key in MenuCategoryDict) { for (const key in MenuCategoryDict) {
@ -179,10 +209,8 @@ defineExpose({
} }
Object.assign(menuForm, data) Object.assign(menuForm, data)
showDialog.value = true showDialog.value = true
if (data.icon != null) { dropdownTableIns.value?.setCurrentRow(icons.glyphs.find(it => it.font_class === data.icon))
selectedRowKeys.value = [ data.icon ] },
}
}
}) })
onMounted(() => { onMounted(() => {
@ -199,4 +227,13 @@ onMounted(() => {
.menu-form { .menu-form {
padding 20px padding 20px
} }
.dropdown-table-wrapper {
padding 10px
display flex
flex-direction column
align-items end
justify-content space-between
gap 10px
}
</style> </style>

View File

@ -26,9 +26,15 @@
header-row-class-name="table-header" header-row-class-name="table-header"
lazy lazy
row-key="id"> row-key="id">
<ElTableColumn label="图标" prop="icon" width="60"/> <!-- <ElTableColumn type="expand" width="60"/> -->
<ElTableColumn label="图标" prop="icon" width="60">
<template #default="scope">
<AIcon :name="scope.row.icon"/>
</template>
</ElTableColumn>
<ElTableColumn label="类型" prop="menuCategoryTxt" width="140"/> <ElTableColumn label="类型" prop="menuCategoryTxt" width="140"/>
<ElTableColumn label="菜单名称" prop="title"/> <ElTableColumn label="菜单名称" prop="title"/>
<ElTableColumn label="编码" prop="sn" width="80"/>
<!-- <ElTableColumn label="路径" prop="breadcrumb"/> --> <!-- <ElTableColumn label="路径" prop="breadcrumb"/> -->
<ElTableColumn label="路由名称" prop="routeName"> <ElTableColumn label="路由名称" prop="routeName">
<template #default="scope"> <template #default="scope">
@ -47,7 +53,19 @@
<!-- <ElTableColumn label="排序" prop="sort" width="60"/> --> <!-- <ElTableColumn label="排序" prop="sort" width="60"/> -->
<ElTableColumn label="操作" width="180"> <ElTableColumn label="操作" width="180">
<template #default="scope"> <template #default="scope">
<ElButton text type="danger" @click="delHandler(scope)"></ElButton> <el-popconfirm
cancel-button-text="否"
cancel-button-type="primary"
confirm-button-text="是"
confirm-button-type="danger"
placement="top"
title="是否删除当前数据?"
width="180"
@confirm="delHandler(scope)">
<template #reference>
<ElButton :loading="deling" text type="danger">删除</ElButton>
</template>
</el-popconfirm>
<ElButton text type="primary" @click="modifyHandler(scope)"></ElButton> <ElButton text type="primary" @click="modifyHandler(scope)"></ElButton>
</template> </template>
</ElTableColumn> </ElTableColumn>
@ -65,19 +83,28 @@ import { onMounted } from 'vue'
import { elIcons } from '@/common/element/element.ts' import { elIcons } from '@/common/element/element.ts'
import MenuForm from '@/pages/sys/menus/MenuForm.vue' import MenuForm from '@/pages/sys/menus/MenuForm.vue'
import Strings from '@/common/utils/strings.ts' import Strings from '@/common/utils/strings.ts'
import AIcon from '@/components/a-icon/AIcon.tsx'
const tableData = ref<MenuTypes.SysMenu[]>([]) const tableData = ref<MenuTypes.SysMenu[]>([])
const searchForm = reactive<MenuTypes.SearchForm>({}) const searchForm = reactive<MenuTypes.SearchForm>({})
const searching = ref(false) const searching = ref(false)
const showSearchForm = ref(true) const showSearchForm = ref(true)
const menuFormIns = useTemplateRef<InstanceType<typeof MenuForm>>('menuForm') const menuFormIns = useTemplateRef<InstanceType<typeof MenuForm>>('menuForm')
const deling = ref(false)
function showDialog(data?: MenuTypes.MenuForm) { function showDialog(data?: MenuTypes.MenuForm) {
menuFormIns.value?.open(data) menuFormIns.value?.open(data)
} }
function delHandler({row}: { row: MenuTypes.MenuForm, }) { function delHandler({row}: { row: MenuTypes.MenuForm, }) {
deling.value = true
MenuApi.del([ row.id! ]) MenuApi.del([ row.id! ])
.then(() => {
ElMessage.success('删除成功')
listAll()
})
.finally(() => deling.value = false)
} }
function modifyHandler({row}: { row: MenuTypes.MenuForm, }) { function modifyHandler({row}: { row: MenuTypes.MenuForm, }) {

View File

@ -1,4 +1,5 @@
import { MenuCategory } from '@/common/app/contants' import { MenuCategory } from '@/common/app/contants'
import type { IconName } from '@/components/a-icon/iconfont.ts'
export {} export {}
@ -45,7 +46,8 @@ declare global {
// 菜单名称 // 菜单名称
title?: string title?: string
// 图标 // 图标
icon?: string icon?: IconName
iconName?: string
// 排序 // 排序
sort?: number sort?: number
// 路由名称 // 路由名称

View File

@ -0,0 +1,153 @@
<script lang="ts" setup>
import Page from '@/components/page/Page.vue'
import SnConfigApi from '@/pages/sys/sn-config/sn-config-api.ts'
import { descConfig } from '@/pages/sys/sn-config/sn-config-util.ts'
import SnConfigForm from '@/pages/sys/sn-config/SnConfigForm.vue'
import Strings from '@/common/utils/strings.ts'
import { elIcons } from '@/common/element/element.ts'
const snConfigForm = ref<InstanceType<typeof SnConfigForm> | null>(null)
function addHandler() {
snConfigForm.value?.open()
}
function reset() {
Object.assign(searchForm, {})
searchHandler()
}
const searchForm = reactive<SnConfigTypes.SearchSnConfigParam>({
snname: undefined,
sncode: undefined,
})
const pagination = reactive<G.Pagination>({
total: 0,
pages: 0,
current: 1,
size: 20,
})
const searching = ref(false)
function pageChangeHandler(currentPage: number, pageSize: number) {
searchForm.current = currentPage
searchForm.size = pageSize
searchHandler()
}
const datasource = ref<SnConfigTypes.SnConfigDetail[]>([])
function searchHandler() {
searching.value = true
SnConfigApi.paging(searchForm, {...pagination})
.then((res) => {
pagination.current = res.data.current
pagination.size = res.data.size
pagination.total = res.data.total
datasource.value = res.data.records.map((it) => ({
key: it.id,
...it,
configDesc: descConfig(it.config).join('\n'),
}))
})
.finally(() => {
searching.value = false
})
}
function modify(record: SnConfigTypes.SnConfigDetail) {
snConfigForm.value?.open(record)
}
function del(record: SnConfigTypes.SnConfigDetail) {
console.log(record)
SnConfigApi.del(record.id)
.then(() => {
ElMessage.success('删除成功')
searchHandler()
})
}
onMounted(() => {
searchHandler()
})
</script>
<template>
<Page>
<ElForm class="search-form" inline @submit.prevent="searchHandler">
<ElFormItem :gutter="[0, 10]" label="规则名称">
<ElInput clearable control="snname" placeholder="请输入规则名称"/>
</ElFormItem>
<ElFormItem :gutter="[0, 10]" label="规则代码">
<ElInput clearable control="sncode" placeholder="请输入规则代码"/>
</ElFormItem>
<ElFormItem>
<ElButton :icon="elIcons.Search" :loading="searching" native-type="submit" type="primary">搜索</ElButton>
<ElButton :icon="elIcons.Refresh" :loading="searching" @click="reset"></ElButton>
</ElFormItem>
</ElForm>
<div class="tool-bar">
<ElButton :icon="elIcons.Plus" type="primary" @click="addHandler"></ElButton>
</div>
<ElTable
v-loading="searching" :data="datasource"
cell-class-name="table-cell"
class="table-list"
empty-text="暂无数据"
header-row-class-name="table-header"
row-key="id">
<ElTableColumn label="规则名称" prop="snname" width="140"/>
<ElTableColumn label="规则代码" prop="sncode" width="140"/>
<ElTableColumn label="示例" prop="example" width="140"/>
<ElTableColumn label="配置信息" prop="configDesc">
<template #default="scope">
<span>
{{ Strings.isBlank(scope.row.configDesc) ? '' : scope.row.configDesc.split('\n') }}
</span>
</template>
</ElTableColumn>
<ElTableColumn label="备注" prop="memo" width="140"/>
<ElTableColumn label="备注" prop="memo" width="140"/>
<ElTableColumn label="" prop=""/>
<ElTableColumn label="操作" width="180">
<template #default="scope">
<el-popconfirm
cancel-button-text="否"
cancel-button-type="primary"
confirm-button-text="是"
confirm-button-type="danger"
placement="top"
title="是否删除当前数据?"
width="180"
@confirm="del(scope)">
<template #reference>
<ElButton text type="danger">删除</ElButton>
</template>
</el-popconfirm>
<ElButton text type="primary" @click="modify(scope)"></ElButton>
</template>
</ElTableColumn>
</ElTable>
<ElPagination
:page-size="pagination.size"
:pager-count="pagination.pages"
:total="pagination.total"
class="pagination"
layout="prev, pager, next"
@change="pageChangeHandler"/>
<SnConfigForm ref="snConfigForm" @close="searchHandler"/>
</Page>
</template>
<style lang="stylus" scoped>
.pagination {
justify-content: end;
margin: 8px;
}
</style>

View File

@ -0,0 +1,447 @@
<script lang="ts" setup>
import Utils from '@/common/utils'
import SnConfigApi from '@/pages/sys/sn-config/sn-config-api.ts'
import Strings from '@/common/utils/strings.ts'
import SnConfigUtil from '@/pages/sys/sn-config/sn-config-util.ts'
import {
PadMode,
RandomMode,
Rollback,
SectionName,
TimeUnit,
} from '@/pages/sys/sn-config/contant.ts'
import Colls from '@/common/utils/colls.ts'
defineOptions({name: 'SnConfigForm'})
const emits = defineEmits([ 'close' ])
const visible = ref(false)
const modify = ref(false)
const snConfig = reactive<SnConfigTypes.SnConfigDetail>({
id: '',
snname: '',
sncode: '',
example: '',
config: [],
memo: '',
})
function clearConfig() {
snConfig.config = []
}
function onCloseHandler() {
visible.value = false
Object.assign(snConfig, {
id: '',
snname: '',
sncode: '',
example: '',
config: [],
memo: '',
})
}
function checkConfig() {
const {id, snname, sncode, config} = snConfig
if (modify.value) {
if (Strings.isBlank(id)) {
ElMessage.error('未指定要修改的规则')
return false
}
} else {
if (Strings.isBlank(snname)) {
ElMessage.error('请输入规则名称')
return false
}
if (Strings.isBlank(sncode)) {
ElMessage.error('请输入规则编码')
return false
}
}
if (Colls.isEmpty(config)) {
ElMessage.error('请配置规则')
return false
}
return true
}
function processConfig(configs: SnConfigTypes.Section[]): SnConfigTypes.Section[] {
return configs.map(it => {
switch (it.sectionName) {
case 'ShiJian': {
const {sectionName, timestamp, pattern, unit} = it as SnConfigTypes.TimeSectionConfig
return {
sectionName,
timestamp,
pattern: timestamp ? undefined : pattern,
unit: !timestamp ? undefined : unit,
}
}
case 'ZiZeng': {
const {sectionName, code, step, initialVal, padMode, padVal, padLen, rollback, allowOverflow} = it as SnConfigTypes.IncSectionConfig
return {
sectionName, code, step, initialVal, padMode,
padVal: padMode === 'Wu' ? '' : padVal,
padLen: padMode === 'Wu' ? 0 : padLen,
rollback, allowOverflow,
}
}
case 'SuiJi': {
const {sectionName, randomMode, workerId, datacenterId, nanoIdSize} = it as SnConfigTypes.RandomSectionConfig
return {
sectionName, randomMode,
workerId: randomMode !== 'Snowflake' ? undefined : workerId,
datacenterId: randomMode !== 'Snowflake' ? undefined : datacenterId,
nanoIdSize: randomMode !== 'NanoId' ? undefined : nanoIdSize,
}
}
case 'GuDing': {
const {sectionName, code, value} = it as SnConfigTypes.FixedSectionConfig
return {
sectionName, code, value,
}
}
default:
throw new Error(`不支持的配置项: ${it.sectionName}`)
}
})
}
const pici = ref<string>('')
const showPici = computed(() => {
let show = false
for (let c of snConfig.config) {
if (c.sectionName === 'ZiZeng') {
if ((c as SnConfigTypes.IncSectionConfig).rollback === 'Pi') {
show = true
break
}
}
}
return show
})
function testSn() {
SnConfigApi.next(snConfig.sncode, Strings.isBlank(pici.value) ? undefined : pici.value)
.then(res => {
ElMessage.success(`下一个编码: ${res.data}`)
})
}
function reset() {
SnConfigApi.reset(snConfig.sncode)
.then(() => {
ElMessage.success('重置成功')
})
}
function onSaveHandler() {
if (!checkConfig()) {
return
}
let {id, snname, sncode, config, memo} = Utils.clone(snConfig)
config = processConfig(config)
if (modify.value) {
SnConfigApi.modify({id, snname, sncode, config, memo})
.then(() => {
ElMessage.success('保存成功')
emits('close')
// onCloseHandler()
})
} else {
SnConfigApi.add({
snname, sncode, config, memo,
}).then(() => {
ElMessage.success('保存成功')
emits('close')
// onCloseHandler()
})
}
}
function addSection(sectionName: SnConfigTypes.SectionType) {
const section = SnConfigUtil.createSection(sectionName)
section.sectionDesc = SnConfigUtil.descSection(section)
snConfig.config.push(section)
}
function delSection(index: number) {
snConfig.config.splice(index, 1)
}
function moveUp(index: number) {
if (index > 0) {
const section = snConfig.config[index]
snConfig.config[index] = snConfig.config[index - 1]
snConfig.config[index - 1] = section
}
}
function moveDown(index: number) {
if (index < snConfig.config.length - 1) {
const section = snConfig.config[index]
snConfig.config[index] = snConfig.config[index + 1]
snConfig.config[index + 1] = section
}
}
const expandedKeys = computed(() => snConfig.config.map((_, i) => i))
const arrowDropdownVisible = ref(false)
defineExpose({
open(data?: SnConfigTypes.SnConfigDetail) {
if (data == null) {
modify.value = false
} else {
modify.value = true
const source = Utils.clone(data)
Object.assign(snConfig, source)
}
visible.value = true
},
})
</script>
<template>
<ElDialog v-model="visible" :title="Strings.isBlank(snConfig.id) ? '新增编码配置' : '修改编码配置'" width="950px"
@close="onCloseHandler">
<div class="config-panel">
<div class="config-title">
<div>
<ElInput v-model="snConfig.snname" clearable placeholder="规则名称"/>
</div>
<div>
<ElInput v-model="snConfig.sncode" clearable placeholder="规则编码"/>
</div>
<div>
<ElInput v-model="snConfig.memo" clearable max-count="128" placeholder="备注" resize="none" showCount type="textarea"/>
</div>
</div>
<div class="config-detail">
<ElCollapse v-if="!Colls.isEmpty(snConfig.config)" :expandedKeys="expandedKeys">
<ElCollapse v-for="(section, i) in snConfig.config" :key="i">
<template #header=" ">
<ElHeader :title="(i + 1 )+'、'+SnConfigUtil.descSection(section)">
<template #suffix>
<ElButtonGroup gap="20px" mode="text" @click.stop>
<ElPopconfirm :title="'是否删除配置?'" placement="top" @ok.stop="delSection(i)">
<ElButton danger icon="delete"/>
</ElPopconfirm>
<ElTooltip v-if="i > 0" placement="top" title="上移">
<ElButton icon="up" @click.stop="moveUp(i)"/>
</ElTooltip>
<ElTooltip v-if="i < snConfig.config.length - 1" placement="top" title="下移">
<ElButton icon="down" @click.stop="moveDown(i)"/>
</ElTooltip>
</ElButtonGroup>
</template>
</ElHeader>
</template>
<template v-if="section.sectionName === 'GuDing'">
<div class="config-item">
<div>
<div>固定值</div>
<ElInput v-model="(section as SnConfigTypes.FixedSectionConfig).value" clearable placeholder="请输入"/>
</div>
</div>
</template>
<template v-if="section.sectionName === 'ShiJian'">
<div class="config-item">
<div>
<div>使用时间戳</div>
<ElSwitch v-model="(section as SnConfigTypes.TimeSectionConfig).timestamp" :labels="['是', '否']"/>
<div class="config-item-padding"></div>
</div>
<div v-if="(section as SnConfigTypes.TimeSectionConfig).timestamp">
<div>时间单位</div>
<ElSelect v-model="(section as SnConfigTypes.TimeSectionConfig).unit" clearable placeholder="请选择">
<ElOption v-for="(val, key) in TimeUnit" :key="'TimeUnit' + key" :label="val" :value="key"/>
</ElSelect>
</div>
<div v-else>
<div>时间格式</div>
<ElInput v-model="(section as SnConfigTypes.TimeSectionConfig).pattern" clearable placeholder="请输入"/>
</div>
</div>
</template>
<template v-if="section.sectionName === 'ZiZeng'">
<div class="config-item">
<div>
<div>步长</div>
<ElInputNumber v-model="(section as SnConfigTypes.IncSectionConfig).step" clearable placeholder="请输入"/>
</div>
<div>
<div>初始值</div>
<ElInputNumber v-model="(section as SnConfigTypes.IncSectionConfig).initialVal" clearable placeholder="请输入"/>
</div>
<div>
<div>填充模式</div>
<ElSelect v-model="(section as SnConfigTypes.IncSectionConfig).padMode" placeholder="请选择">
<ElOption v-for="(val, key) in PadMode" :key="'PadMode' + key" :label="val" :value="key"/>
</ElSelect>
</div>
<div v-if="(section as SnConfigTypes.IncSectionConfig).padMode !== 'Wu'">
<div>填充字符</div>
<ElInput v-model="(section as SnConfigTypes.IncSectionConfig).padVal" clearable maxlength="1" minlength="1" placeholder="请输入"/>
</div>
<div v-if="(section as SnConfigTypes.IncSectionConfig).padMode !== 'Wu'">
<div>填充长度</div>
<ElInputNumber v-model="(section as SnConfigTypes.IncSectionConfig).padLen" :max="20" :min="1" clearable placeholder="请输入"/>
</div>
<div>
<div>回卷模式</div>
<ElSelect v-model="(section as SnConfigTypes.IncSectionConfig).rollback" clearable placeholder="请选择">
<ElOption v-for="(val, key) in Rollback" :key="'rollback-' + key" :label="val" :value="key"/>
</ElSelect>
</div>
<div>
<div>允许溢出</div>
<ElSwitch v-model="(section as SnConfigTypes.IncSectionConfig).allowOverflow" :labels="['是', '否']"/>
<div class="config-item-padding"></div>
</div>
</div>
</template>
<template v-if="section.sectionName === 'SuiJi'">
<div class="config-item">
<div>
<div>随机模式</div>
<ElSelect v-model="(section as SnConfigTypes.RandomSectionConfig).randomMode" clearable placeholder="请选择">
<ElOption v-for="(val, key) in RandomMode" :key="'RandomMode' + key" :label="val" :value="key"/>
</ElSelect>
</div>
<div v-if="(section as SnConfigTypes.RandomSectionConfig).randomMode === 'Snowflake'">
<div>工作节点</div>
<ElInputNumber v-model="(section as SnConfigTypes.RandomSectionConfig).workerId" clearable placeholder="请输入"/>
</div>
<div v-if="(section as SnConfigTypes.RandomSectionConfig).randomMode === 'Snowflake'">
<div>数据中心</div>
<ElInputNumber v-model="(section as SnConfigTypes.RandomSectionConfig).datacenterId" clearable placeholder="请输入"/>
</div>
<div v-if="(section as SnConfigTypes.RandomSectionConfig).randomMode === 'NanoId'">
<div>长度</div>
<ElInputNumber v-model="(section as SnConfigTypes.RandomSectionConfig).nanoIdSize" clearable placeholder="请输入"/>
</div>
</div>
</template>
</ElCollapse>
</ElCollapse>
<ElEmpty v-else/>
</div>
</div>
<template #footer>
<ElButton mode="default" @click="onCloseHandler"></ElButton>
<ElPopconfirm v-if="snConfig.sncode === 'CS' && showPici" placement="top" title="填写批次" @ok="testSn">
<ElButton danger mode="default">测试</ElButton>
<template #content>
<ElFormItem compact label="批次" required>
<ElInput v-model="pici"/>
</ElFormItem>
</template>
</ElPopconfirm>
<ElButton v-if="snConfig.sncode === 'CS' && !showPici" danger mode="default" @click="testSn"></ElButton>
<ElButton v-if="snConfig.sncode === 'CS'" danger mode="default" @click="reset"></ElButton>
<ElPopconfirm :title="'是否清空配置?'" placement="top" @ok.stop="clearConfig">
<ElButton danger mode="default">清空配置</ElButton>
</ElPopconfirm>
<ElButton mode="primary" @click="onSaveHandler"></ElButton>
<ElDropdown v-modelvisible="arrowDropdownVisible">
<ElButton mode="primary">
<div class="flex-center">
添加配置
<ElIcon :rotate="arrowDropdownVisible ? -180 : 0" name="down" size="16px" style="margin-left: 4px"></ElIcon>
</div>
</ElButton>
<template #dropdown>
<div class="dropdown-panel">
<ElButton v-for="(val, key) in SectionName" :key="'SectionName' + key" class="dropdown-item" mode="text" @click="addSection(key)">{{ val }}</ElButton>
</div>
</template>
</ElDropdown>
</template>
</ElDialog>
</template>
<style lang="stylus" scoped>
.config-panel {
width 100%
height 500px
display flex
justify-content space-between
.config-title {
width: 200px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 1rem;
& > div:nth-child(3) {
flex 1
:deep(.ix-textarea) {
height 100%
textarea {
height 100%
}
}
}
}
.config-detail {
flex 1
height 100%
padding .5rem
display: flex
justify-content: center;
align-items: center;
.config-item {
display: flex
flex-direction: column;
gap: 1rem;
& > div {
display flex
align-items center
justify-content space-between
& > div:first-child {
width 80px
flex-shrink 0
}
.config-item-padding {
flex: 1
}
}
}
:deep(.ix-collapse) {
width 100%
height 100%
overflow auto
}
}
}
.dropdown-panel {
display flex
flex-direction column
margin .5rem
.dropdown-item {
height 2rem
&:not(:last-child) {
border-bottom 1px solid #E5E5E5
}
}
}
</style>

View File

@ -0,0 +1,32 @@
export const SectionName = {
GuDing: '固定值',
ShiJian: '时间值',
ZiZeng: '增量值',
SuiJi: '随机值',
}
export const TimeUnit = {
1: '毫秒',
1000: '秒',
60000: '分',
3600000: '时',
86400000: '天',
}
export const PadMode = {
Wu: '不填充',
Zuo: '左填充',
You: '右填充',
}
export const RandomMode = {
Snowflake: '雪花码',
NanoId: '唯一识别码NanoId',
UUID: '通用唯一识别码UUID',
}
export const Rollback = {
Wu: '不回卷',
Pi: '按批',
Ri: '按天',
Zhou: '按周',
Yue: '按月',
Nian: '按年',
}

View File

@ -0,0 +1,3 @@
export default {
component: () => import('@/pages/sys/sn-config/SnConfig.vue'),
} as RouterTypes.RouteConfig

View File

@ -0,0 +1,28 @@
import {
get,
post,
} from '@/common/utils/http-util.ts'
export default {
paging(data: SnConfigTypes.SearchSnConfigParam, {size, current, orders}: G.PageParam) {
return get<G.PageResult<SnConfigTypes.SnConfigDetail>>('/sys_sn_config/paging', {
size, current, orders,
...data,
})
},
modify(data: SnConfigTypes.ModifySnConfigParam) {
return post('/sys_sn_config/modify', data)
},
add(data: SnConfigTypes.AddSnConfigParam) {
return post('/sys_sn_config/add', data)
},
reset(sncode: string) {
return get('/sys_sn_config/reset', {sncode})
},
next(sncode: string, pici?: string) {
return get<string>('/sys_sn_config/next', {sncode, pici})
},
del(...id: string[]) {
return post('/sys_sn_config/del', id)
},
}

View File

@ -0,0 +1,107 @@
import {
PadMode,
RandomMode,
Rollback,
SectionName,
TimeUnit,
} from '@/pages/sys/sn-config/contant.ts'
import Colls from '@/common/utils/colls.ts'
import { nanoid } from 'nanoid'
export function descGuDingSection(section: SnConfigTypes.FixedSectionConfig) {
return `${SectionName.GuDing}${section.value}`
}
export function descShiJianSection(section: SnConfigTypes.TimeSectionConfig) {
return `${SectionName.ShiJian}${section.timestamp ? (section.unit != null ? TimeUnit[section.unit!] + '(时间戳)' : '(时间戳)') : section.pattern}`
}
export function descZhiZengSection(section: SnConfigTypes.IncSectionConfig) {
if (section.padMode === 'Wu') {
return `${SectionName.ZiZeng}:初始值为:${section.initialVal},步长为:${section.step}${PadMode[section.padMode]}字符,${Rollback[section.rollback] ?? Rollback.Wu}${section.allowOverflow ? '允许溢出' : '不允许溢出'}`
} else {
return `${SectionName.ZiZeng}:初始值为:${section.initialVal},步长为:${section.step}${PadMode[section.padMode]}${section.padLen} 个字符:${section.padVal}${Rollback[section.rollback] ?? Rollback.Wu}${section.allowOverflow ? '允许溢出' : '不允许溢出'}`
}
}
export function descSuiJiSection(section: SnConfigTypes.RandomSectionConfig) {
switch (section.randomMode) {
case 'Snowflake':
return `${SectionName.SuiJi}${RandomMode[section.randomMode]}(工作节点:${section.workerId ?? ''},数据中心:${section.datacenterId ?? ''}`
case 'NanoId':
return `${SectionName.SuiJi}${RandomMode[section.randomMode]}(长度:${section.nanoIdSize ?? ''}`
case 'UUID':
return `${SectionName.SuiJi}${RandomMode[section.randomMode]}`
}
}
export function descConfig(sections: SnConfigTypes.Section[]) {
if (Colls.isEmpty(sections)) {
return []
}
return sections.map(it => descSection(it))
}
export function descSection(section: SnConfigTypes.Section) {
switch (section.sectionName) {
case 'GuDing':
return descGuDingSection(section as SnConfigTypes.FixedSectionConfig)
case 'ShiJian':
return descShiJianSection(section as SnConfigTypes.TimeSectionConfig)
case 'ZiZeng':
return descZhiZengSection(section as SnConfigTypes.IncSectionConfig)
case 'SuiJi':
return descSuiJiSection(section as SnConfigTypes.RandomSectionConfig)
default:
throw new Error(`不支持的配置项: ${section.sectionName}`)
}
}
export function createSection(sectionName: SnConfigTypes.SectionType): SnConfigTypes.Section {
switch (sectionName) {
case 'GuDing':
return {
sectionName,
value: '',
}
case 'ShiJian':
return {
sectionName,
timestamp: false,
unit: null,
pattern: 'yyyyMMdd',
}
case 'ZiZeng':
return {
sectionName,
code: nanoid(),
padMode: 'Wu',
padVal: '',
padLen: null,
rollback: 'Wu',
allowOverflow: true,
initialVal: 1,
step: 1,
}
case 'SuiJi':
return {
sectionName,
randomMode: 'UUID',
workerId: null,
datacenterId: null,
nanoIdSize: null,
}
default:
throw new Error(`不支持的配置项: ${sectionName}`)
}
}
export default {
descGuDingSection,
descShiJianSection,
descZhiZengSection,
descSuiJiSection,
descConfig,
descSection,
createSection,
}

View File

@ -0,0 +1,158 @@
export {}
declare global {
namespace SnConfigTypes {
type SectionType = 'GuDing' | 'ShiJian' | 'ZiZeng' | 'SuiJi'
interface Section extends Record<string, any> {
sectionName: SectionType
sectionDesc?: string
}
interface AddSnConfigParam {
/**
*
*/
snname: string
/**
*
*/
sncode: string
/**
*
*/
memo: string
/**
*
*/
config: Section[]
}
interface ModifySnConfigParam {
id: string
/**
*
*/
snname: string
/**
*
*/
sncode: string
/**
*
*/
memo: string
/**
*
*/
config: Section[]
}
interface SnConfigDetail {
id: string
/**
*
*/
snname: string
/**
*
*/
sncode: string
/**
*
*/
example: string
/**
*
*/
memo: string
/**
*
*/
config: Section[]
configDesc?: string
}
interface FixedSectionConfig extends Section {
sectionName: 'GuDing'
value: string
}
interface TimeSectionConfig extends Section {
sectionName: 'ShiJian'
timestamp: boolean
pattern?: string
/**
* 1 --> 1000-->60000 --> 3600000 --> 86400000 -->
*/
unit?: 1 | 1000 | 60000 | 3600000 | 86400000
}
interface IncSectionConfig extends Section {
sectionName: 'ZiZeng'
/**
*
*/
code: string
/**
*
*/
step: number
/**
*
*/
initialVal: number
/**
*
*/
padMode: 'Wu' | 'Zuo' | 'You'
/**
*
*/
padVal: string
/**
*
*/
padLen?: number
/**
*
*/
rollback: 'Wu' | 'Ri' | 'Zhou' | 'Yue' | 'Nian' | 'Pi'
/**
*
*/
allowOverflow: boolean
}
interface RandomSectionConfig extends Section {
sectionName: 'SuiJi'
/**
*
*/
randomMode: 'Snowflake' | 'NanoId' | 'UUID'
/**
* -ID
*/
workerId?: number
/**
* -ID
*/
datacenterId?: number
/**
* NanoId-
*/
nanoIdSize?: number
}
interface SearchSnConfigParam extends G.PageParam {
/**
*
*/
snname?: string
/**
*
*/
sncode?: string
}
}
}