lzq 2026-02-28 15:54:51 +08:00
parent c9d02287e4
commit 0e18fd3c33
35 changed files with 1350 additions and 1270 deletions

View File

@ -1,13 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import Utils from '@/common/utils' import Utils from '@/common/utils'
import { elIcons } from '@/common/element/element.ts' import { elIcons } from '@/common/element/element.ts'
import Colls from '@/common/utils/colls.ts'
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
modelValue?: string | string[] | null, modelValue?: string | string[] | null,
multiple?: boolean, multiple?: boolean,
placeholder?: string, placeholder?: string,
displayField: string, displayField: string,
columns: { columns?: {
label: string label: string
prop: string prop: string
}[] }[]
@ -63,7 +64,10 @@ function onOpen() {
paging() paging()
} }
onMounted(onOpen)
function onClose() { function onClose() {
searchForm.$reset()
} }
function onClear() { function onClear() {
@ -131,11 +135,13 @@ const displayData = computed(() => {
if (props.multiple) { if (props.multiple) {
return `已选 ${selectRows.length}` return `已选 ${selectRows.length}`
} }
return tableDataList.value let data = tableDataList.value
.filter(it => selectRows.includes(it.id)) .filter(it => selectRows.includes(it.id))
.map(it => ((it as Record<string, any>) [props.displayField] as any) ?? '') .map(it => ((it as Record<string, any>) [props.displayField] as any) ?? '')
.join(' ') if (Colls.isEmpty(data)) {
data = selectRows as string[]
}
return data.join('')
}) })
</script> </script>
@ -145,6 +151,9 @@ const displayData = computed(() => {
placement="bottom" placement="bottom"
trigger="click" trigger="click"
@visible-change="visibleChangeHandler"> @visible-change="visibleChangeHandler">
<template #default>
<div>
<slot :displayData="displayData" name="displayArea">
<ElInput :model-value="displayData" :placeholder="placeholder" readonly> <ElInput :model-value="displayData" :placeholder="placeholder" readonly>
<template #suffix> <template #suffix>
<ElIcon class="clear-btn" @click.stop="onClear"> <ElIcon class="clear-btn" @click.stop="onClear">
@ -152,6 +161,9 @@ const displayData = computed(() => {
</ElIcon> </ElIcon>
</template> </template>
</ElInput> </ElInput>
</slot>
</div>
</template>
<template #dropdown> <template #dropdown>
<div class="drop-table-content"> <div class="drop-table-content">
<ElForm inline @submit.prevent="paging"> <ElForm inline @submit.prevent="paging">
@ -187,11 +199,13 @@ const displayData = computed(() => {
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="#" type="index"/> <ElTableColumn label="#" type="index"/>
<slot name="columns">
<ElTableColumn <ElTableColumn
v-for="(item, i) in columns" v-for="(item, i) in columns"
:key="'a-drop-table-'+i" :key="'a-drop-table-'+i"
:label="item.label" :label="item.label"
:prop="item.prop"/> :prop="item.prop"/>
</slot>
</ElTable> </ElTable>
<ElDivider class="drop-table-content-divider"/> <ElDivider class="drop-table-content-divider"/>
<ElPagination <ElPagination

View File

@ -0,0 +1,57 @@
<script lang="ts" setup>
import ADropTable from '@/components/a-drop-table/ADropTable.vue'
import {
type IconName,
icons,
} from '@/components/a-icon/iconfont.ts'
import AIcon from '@/components/a-icon/AIcon.vue'
import Strings from '@/common/utils/strings.ts'
const model = defineModel<string | undefined | null>()
const dropTableLoader = (param: { keywords: string } & G.PageParam) => {
const dataList = icons.glyphs
.filter(it => it.name.toLowerCase().includes(param.keywords.toLowerCase()) || it.font_class.toLowerCase().includes(param.keywords.toLowerCase()))
.sort((a, b) => a.unicode_decimal - b.unicode_decimal)
return Promise.resolve({
current: param.current!,
size: param.size,
pages: Math.ceil(dataList.length / param.size!),
total: dataList.length,
records: dataList
.filter((_, i) => {
return i >= (param.current! - 1) * param.size! && i < param.current! * param.size!
})
.map(it => ({
id: it.font_class,
...it,
})),
} as G.PageResult)
}
</script>
<template>
<ADropTable v-model="model"
:loader="dropTableLoader"
display-field="font_class">
<template #displayArea="{displayData}">
<div v-if=" Strings.isBlank(displayData)" class="display-area"></div>
<AIcon v-else :name="displayData as IconName" class="display-area"/>
</template>
<template #columns>
<ElTableColumn label="图标" prop="font_class">
<template #default="{row}">
<AIcon :name="row.font_class as IconName"/>
<span style="margin-left: 10px;display: inline-block">{{ row.font_class }}</span>
</template>
</ElTableColumn>
<ElTableColumn label="名称" prop="name"/>
</template>
</ADropTable>
</template>
<style lang="stylus" scoped>
.display-area {
cursor pointer
}
</style>

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* 项目名称 再昇云 */ font-family: "iconfont"; /* 项目名称 再昇云 */
src: url('@/components/a-icon/iconfont.woff2?t=1771989909326') format('woff2'), src: url('@/components/a-icon/iconfont.woff2?t=1772264126793') format('woff2'),
url('@/components/a-icon/iconfont.woff?t=1771989909326') format('woff'), url('@/components/a-icon/iconfont.woff?t=1772264126793') format('woff'),
url('@/components/a-icon/iconfont.ttf?t=1771989909326') format('truetype'); url('@/components/a-icon/iconfont.ttf?t=1772264126793') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,38 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-yulan:before {
content: "\e668";
}
.icon-putongzhihang:before {
content: "\e663";
}
.icon-zhihangjilu:before {
content: "\e892";
}
.icon-xitongguanli1:before {
content: "\e6ae";
}
.icon-API:before {
content: "\e740";
}
.icon-bianma:before {
content: "\e772";
}
.icon-caidan:before {
content: "\e662";
}
.icon-gongnengquanxian:before {
content: "\e661";
}
.icon-zhongzhimima:before { .icon-zhongzhimima:before {
content: "\e660"; content: "\e660";
} }

View File

@ -5,6 +5,62 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "7450656",
"name": "预览",
"font_class": "yulan",
"unicode": "e668",
"unicode_decimal": 58984
},
{
"icon_id": "11493961",
"name": "普通执行",
"font_class": "putongzhihang",
"unicode": "e663",
"unicode_decimal": 58979
},
{
"icon_id": "44782561",
"name": "执行记录",
"font_class": "zhihangjilu",
"unicode": "e892",
"unicode_decimal": 59538
},
{
"icon_id": "12884221",
"name": "系统管理",
"font_class": "xitongguanli1",
"unicode": "e6ae",
"unicode_decimal": 59054
},
{
"icon_id": "3351180",
"name": "API",
"font_class": "API",
"unicode": "e740",
"unicode_decimal": 59200
},
{
"icon_id": "19105727",
"name": "编码",
"font_class": "bianma",
"unicode": "e772",
"unicode_decimal": 59250
},
{
"icon_id": "7155218",
"name": "菜单",
"font_class": "caidan",
"unicode": "e662",
"unicode_decimal": 58978
},
{
"icon_id": "12820163",
"name": "功能权限",
"font_class": "gongnengquanxian",
"unicode": "e661",
"unicode_decimal": 58977
},
{ {
"icon_id": "524416", "icon_id": "524416",
"name": "重置密码", "name": "重置密码",

View File

@ -5,6 +5,62 @@ export const icons = {
'css_prefix_text': 'icon-', 'css_prefix_text': 'icon-',
'description': '', 'description': '',
'glyphs': [ 'glyphs': [
{
'icon_id': '7450656',
'name': '预览',
'font_class': 'yulan',
'unicode': 'e668',
'unicode_decimal': 58984,
},
{
'icon_id': '11493961',
'name': '普通执行',
'font_class': 'putongzhihang',
'unicode': 'e663',
'unicode_decimal': 58979,
},
{
'icon_id': '44782561',
'name': '执行记录',
'font_class': 'zhihangjilu',
'unicode': 'e892',
'unicode_decimal': 59538,
},
{
'icon_id': '12884221',
'name': '系统管理',
'font_class': 'xitongguanli1',
'unicode': 'e6ae',
'unicode_decimal': 59054,
},
{
'icon_id': '3351180',
'name': 'API',
'font_class': 'API',
'unicode': 'e740',
'unicode_decimal': 59200,
},
{
'icon_id': '19105727',
'name': '编码',
'font_class': 'bianma',
'unicode': 'e772',
'unicode_decimal': 59250,
},
{
'icon_id': '7155218',
'name': '菜单',
'font_class': 'caidan',
'unicode': 'e662',
'unicode_decimal': 58978,
},
{
'icon_id': '12820163',
'name': '功能权限',
'font_class': 'gongnengquanxian',
'unicode': 'e661',
'unicode_decimal': 58977,
},
{ {
'icon_id': '524416', 'icon_id': '524416',
'name': '重置密码', 'name': '重置密码',

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -102,6 +102,7 @@ interface FormPropsType<P, T extends DefaultRow> {
* @param param * @param param
*/ */
paging: (param: P) => Promise<R<G.PageResult<T>>> paging: (param: P) => Promise<R<G.PageResult<T>>>
list?: (param: P) => Promise<R<T[]>>
/** /**
* *
* @param param * @param param
@ -329,9 +330,19 @@ const component = defineComponent(
loading.value = false loading.value = false
}) })
return return
} else if (props.searchForm.list != null) {
props.searchForm.list(formData.$clone() as P)
.then(res => {
const records = res.data ?? ([] as T[])
tableData.$reset(records)
})
.finally(() => {
loading.value = false
})
return
} }
} }
expose({doSearch}) expose({doSearch, tableData})
const showHighFormHandle = () => { const showHighFormHandle = () => {
showHighForm.value = !showHighForm.value showHighForm.value = !showHighForm.value
formData.$reset() formData.$reset()
@ -634,7 +645,7 @@ const component = defineComponent(
} }
</ElTable>, [ [ ElLoading.directive, loading.value ] ]) </ElTable>, [ [ ElLoading.directive, loading.value ] ])
} }
<ElPagination {props.searchForm.paging != null ? <ElPagination
current-page={(formData as G.PageParam).current} current-page={(formData as G.PageParam).current}
page-size={(formData as G.PageParam).size} page-size={(formData as G.PageParam).size}
onUpdate:current-page={(val) => (formData as G.PageParam).current = val} onUpdate:current-page={(val) => (formData as G.PageParam).current = val}
@ -645,7 +656,7 @@ const component = defineComponent(
total={totalCount.value} total={totalCount.value}
background={true} background={true}
layout="total, prev, pager, next, sizes, jumper" layout="total, prev, pager, next, sizes, jumper"
onChange={doSearch}/> onChange={doSearch}/> : <></>}
{ {
slots?.default?.() slots?.default?.()
} }
@ -658,8 +669,9 @@ const component = defineComponent(
}, },
) )
export interface ATablePageInstance extends InstanceType<typeof component> { export interface ATablePageInstance<T extends DefaultRow = DefaultRow> extends InstanceType<typeof component> {
doSearch: () => void doSearch: () => void
tableData: T[]
} }
export default component export default component

View File

@ -33,6 +33,7 @@ declare module 'vue' {
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader'] ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElIconArrowRight: typeof import('@element-plus/icons-vue')['ArrowRight']
ElIconCircleClose: typeof import('@element-plus/icons-vue')['CircleClose'] ElIconCircleClose: typeof import('@element-plus/icons-vue')['CircleClose']
ElIconPicture: typeof import('@element-plus/icons-vue')['Picture'] ElIconPicture: typeof import('@element-plus/icons-vue')['Picture']
ElIconPlus: typeof import('@element-plus/icons-vue')['Plus'] ElIconPlus: typeof import('@element-plus/icons-vue')['Plus']
@ -91,6 +92,7 @@ declare global {
const ElFormItem: typeof import('element-plus/es')['ElFormItem'] const ElFormItem: typeof import('element-plus/es')['ElFormItem']
const ElHeader: typeof import('element-plus/es')['ElHeader'] const ElHeader: typeof import('element-plus/es')['ElHeader']
const ElIcon: typeof import('element-plus/es')['ElIcon'] const ElIcon: typeof import('element-plus/es')['ElIcon']
const ElIconArrowRight: typeof import('@element-plus/icons-vue')['ArrowRight']
const ElIconCircleClose: typeof import('@element-plus/icons-vue')['CircleClose'] const ElIconCircleClose: typeof import('@element-plus/icons-vue')['CircleClose']
const ElIconPicture: typeof import('@element-plus/icons-vue')['Picture'] const ElIconPicture: typeof import('@element-plus/icons-vue')['Picture']
const ElIconPlus: typeof import('@element-plus/icons-vue')['Plus'] const ElIconPlus: typeof import('@element-plus/icons-vue')['Plus']

View File

@ -19,7 +19,7 @@
<ElInput v-model="formData.categoryName" placeholder="分类名称"/> <ElInput v-model="formData.categoryName" placeholder="分类名称"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="排序" prop="sort"> <ElFormItem label="排序" prop="sort">
<ElInputNumber v-model="formData.sort" :min="0" placeholder="排序"/> <ElInputNumber v-model="formData.sort" :min="0" controls-position="right" placeholder="排序"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="备注" prop="memo"> <ElFormItem label="备注" prop="memo">
<ElInput v-model="formData.memo" placeholder="备注"/> <ElInput v-model="formData.memo" placeholder="备注"/>

View File

@ -115,7 +115,6 @@ const formPanelIns = useTemplateRef<AFormPanelInstance>('formPanel')
const uploaderIns = useTemplateRef<InstanceType<typeof Uploader>>('uploader') const uploaderIns = useTemplateRef<InstanceType<typeof Uploader>>('uploader')
const status = ref<'add' | 'modify'>('add') const status = ref<'add' | 'modify'>('add')
const formPanelProps = buildFormPanelProps<GoodsTypes.GoodsForm>({ const formPanelProps = buildFormPanelProps<GoodsTypes.GoodsForm>({
// title: status.value === 'add' ? '' : '',
detailsLoader(id?: string) { detailsLoader(id?: string) {
if (Strings.isBlank(id)) { if (Strings.isBlank(id)) {
status.value = 'add' status.value = 'add'

View File

@ -1,13 +1,11 @@
<template> <template>
<FormPage <ATablePage
ref="formPage" ref="tablePage"
:action-column="actionColumn" v-bind="tablePageProps">
:left-tools="leftTools" <template #simpleFormItem="formData">
:paging="paging"> <ElFormItem>
<template #searchFormItem="{ searchForm }">
<ElFormItem label="端点地址">
<ElInput <ElInput
v-model="searchForm.endpointPath" v-model="formData.endpointPath"
placeholder="端点地址"/> placeholder="端点地址"/>
</ElFormItem> </ElFormItem>
</template> </template>
@ -18,28 +16,30 @@
<ElTableColumn label="访问模式" prop="accessModelTxt"/> <ElTableColumn label="访问模式" prop="accessModelTxt"/>
<ElTableColumn label="备注" prop="memo"/> <ElTableColumn label="备注" prop="memo"/>
</template> </template>
<EndpointForm ref="endpointForm" @edit-succ="research"/> <EndpointForm ref="endpointForm" :research="research"/>
</FormPage> </ATablePage>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import EndpointApi from '@/pages/sys/endpoint/endpoint-api.ts' import EndpointApi from '@/pages/sys/endpoint/endpoint-api.ts'
import EndpointForm from '@/pages/sys/endpoint/EndpointForm.vue' import EndpointForm from '@/pages/sys/endpoint/EndpointForm.vue'
import FormPage from '@/components/page/FormPage.vue' import ATablePage, {
import type { type ATablePageInstance,
ActionColumnType, buildTablePageProps,
ToolType, } from '@/components/a-page/a-table-page/ATablePage.tsx'
} from '@/components/page/a-page-type.ts'
import type { ComponentExposed } from 'vue-component-type-helpers'
const formPageIns = useTemplateRef<ComponentExposed<typeof FormPage>>('formPage') const tablePageIns = useTemplateRef<ATablePageInstance>('tablePage')
const endpointFormIns = useTemplateRef<InstanceType<typeof EndpointForm>>('endpointForm') const endpointFormIns = useTemplateRef<InstanceType<typeof EndpointForm>>('endpointForm')
function research() { const tablePageProps = buildTablePageProps<EndpointTypes.SearchEndpointParam, EndpointTypes.SearchEndpointResult>({
formPageIns.value?.doSearch() pageLayout: {
} enableHighForm: false,
},
const leftTools: ToolType[] = [ searchForm: {
paging: EndpointApi.paging,
},
toolBar: {
leftTools: [
{ {
icon: 'Plus', icon: 'Plus',
label: '新建', label: '新建',
@ -47,8 +47,10 @@ const leftTools: ToolType[] = [
endpointFormIns.value?.open() endpointFormIns.value?.open()
}, },
}, },
] ],
const actionColumn = reactive<ActionColumnType<EndpointTypes.SearchEndpointResult>>({ },
table: {
actionColumn: {
tableActions: [ tableActions: [
{ {
tooltip: '编辑', tooltip: '编辑',
@ -75,71 +77,14 @@ const actionColumn = reactive<ActionColumnType<EndpointTypes.SearchEndpointResul
}, },
}, },
], ],
},
},
}) })
function research() {
function paging(param: EndpointTypes.SearchEndpointParam) { tablePageIns.value?.doSearch()
return EndpointApi.paging(param)
} }
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.table-list {
flex 1;
margin 0 0 20px 0
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> </style>

View File

@ -1,20 +1,12 @@
<template> <template>
<ElDialog <AFormPanel
v-model="showDialog" ref="formPanel"
:close-on-click-modal="false" v-bind="formPanelProps">
destroy-on-close <template #default="formData">
width="fit-content" <div class="form-items">
@close="dialogCloseHandler">
<ElForm :model="formData"
ref="endpointForm"
:rules="rules"
class="form-panel"
>
<ElFormItem label="请求方式" prop="requestMethod"> <ElFormItem label="请求方式" prop="requestMethod">
<ElSelect <ElSelect
v-model="formData.requestMethod" v-model="formData.requestMethod"
placeholder="请求方式"> placeholder="请求方式">
<ElOption label="GET 请求" value="GET"/> <ElOption label="GET 请求" value="GET"/>
<ElOption label="POST 请求" value="POST"/> <ElOption label="POST 请求" value="POST"/>
@ -25,19 +17,16 @@
<ElFormItem label="路由前缀" prop="routingPath"> <ElFormItem label="路由前缀" prop="routingPath">
<ElInput <ElInput
v-model="formData.routingPath" v-model="formData.routingPath"
placeholder="以 / 开头 或 为空"/> placeholder="以 / 开头 或 为空"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="端点地址" prop="endpointPath"> <ElFormItem label="端点地址" prop="endpointPath">
<ElInput <ElInput
v-model="formData.endpointPath" v-model="formData.endpointPath"
placeholder="以 / 开头"/> placeholder="以 / 开头"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="访问模式" prop="accessModel"> <ElFormItem label="访问模式" prop="accessModel">
<ElSelect <ElSelect
v-model="formData.accessModel" v-model="formData.accessModel"
placeholder="访问模式"> placeholder="访问模式">
<ElOption label="允许匿名访问" value="Anonymous"/> <ElOption label="允许匿名访问" value="Anonymous"/>
<ElOption label="允许已登录用户访问" value="Logined"/> <ElOption label="允许已登录用户访问" value="Logined"/>
@ -48,37 +37,53 @@
<ElFormItem label="备注" prop="memo"> <ElFormItem label="备注" prop="memo">
<ElInput <ElInput
v-model="formData.memo" v-model="formData.memo"
placeholder="备注"/> placeholder="备注"/>
</ElFormItem> </ElFormItem>
</ElForm> </div>
<template #footer>
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</template> </template>
</ElDialog> </AFormPanel>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import EndpointApi from '@/pages/sys/endpoint/endpoint-api.ts' import EndpointApi from '@/pages/sys/endpoint/endpoint-api.ts'
import Strings from '@/common/utils/strings.ts' import Strings from '@/common/utils/strings.ts'
import FormUtil from '@/common/utils/formUtil.ts'
import {
ElMessage,
type FormInstance,
type FormRules,
} from 'element-plus'
import type { InternalRuleItem } from 'async-validator/dist-types/interface' import type { InternalRuleItem } from 'async-validator/dist-types/interface'
import AFormPanel, {
type AFormPanelInstance,
buildFormPanelProps,
} from '@/components/a-form-panel/AFormPanel.tsx'
const emits = defineEmits([ 'editSucc' ]) const props = withDefaults(defineProps<{
const showDialog = ref(false) research?: () => void
const submiting = ref(false) }>(), {
const status = ref<'add' | 'view' | 'modify'>('add') research: () => {
},
const endpointFormIns = useTemplateRef<FormInstance>('endpointForm') })
const status = ref<'add' | 'modify'>('add')
const formData = ref<EndpointTypes.SearchEndpointResult>({}) const formPanelIns = useTemplateRef<AFormPanelInstance>('formPanel')
const rules = reactive<FormRules<EndpointTypes.SearchEndpointResult>>({ const formPanelProps = buildFormPanelProps<GoodsTypes.GoodsForm>({
detailsLoader(id?: string) {
if (Strings.isBlank(id)) {
status.value = 'add'
return Promise.resolve()
} else {
status.value = 'modify'
return EndpointApi.detail(id!)
.then((res) => {
return res.data
})
}
},
doSubmit(data) {
if (status.value === 'add') {
return EndpointApi.add(data)
.then(props.research)
} else {
return EndpointApi.modify(data)
.then(props.research)
}
},
rules: {
requestMethod: [ {required: true, message: '请选择请求方式', trigger: 'blur'} ], requestMethod: [ {required: true, message: '请选择请求方式', trigger: 'blur'} ],
routingPath: [ { routingPath: [ {
validator(_: InternalRuleItem, value: string, callback: (error?: string | Error) => void) { validator(_: InternalRuleItem, value: string, callback: (error?: string | Error) => void) {
@ -107,57 +112,28 @@ const rules = reactive<FormRules<EndpointTypes.SearchEndpointResult>>({
}, trigger: 'blur', }, trigger: 'blur',
} ], } ],
accessModel: [ {required: true, message: '请选择访问模式', trigger: 'blur'} ], accessModel: [ {required: true, message: '请选择访问模式', trigger: 'blur'} ],
},
}) })
watchEffect(() => {
function dialogCloseHandler() { formPanelProps.title = status.value === 'add' ? '新建产品' : '修改产品'
formData.value = {}
}
function submitHandler() {
if (status.value === 'view') return
submiting.value = true
if (formData.value.id != null) {
FormUtil.submit(endpointFormIns, () => EndpointApi.modify(formData.value))
.then(() => {
ElMessage.success('修改成功')
emits('editSucc')
showDialog.value = false
}) })
.finally(() => {
submiting.value = false
})
} else {
FormUtil.submit(endpointFormIns, () => EndpointApi.add(formData.value))
.then(() => {
ElMessage.success('添加成功')
emits('editSucc')
showDialog.value = false
})
.finally(() => {
submiting.value = false
})
}
}
defineExpose({ defineExpose({
open(data: EndpointTypes.SearchEndpointResult = {}) { open(data?: EndpointTypes.SearchEndpointResult) {
showDialog.value = true formPanelIns.value?.open(data?.id)
if (!Strings.isBlank(data.id)) {
status.value = 'modify'
EndpointApi.detail(data.id!)
.then(res => {
formData.value = res.data
})
} else {
status.value = 'add'
formData.value = data
}
}, },
}) })
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.form-panel { .form-items {
padding 20px grid-template-columns: 1fr 1fr;
:deep(.el-form-item) {
&:last-child {
grid-column: span 2;
width: 100%;
}
}
} }
</style> </style>

View File

@ -1,9 +1,7 @@
<template> <template>
<ElDialog v-model="showDialog" <ADialog v-model:show="showDialog"
:close-on-click-modal="false" :closed="dialogCloseHandler"
destroy-on-close title="预览"
width="fit-content"
@close="dialogCloseHandler"
> >
<div class="content"> <div class="content">
<ElForm :model="formData" <ElForm :model="formData"
@ -79,7 +77,7 @@
<ElButton :loading="previewing" type="primary" @click="previewHandler"></ElButton> <ElButton :loading="previewing" type="primary" @click="previewHandler"></ElButton>
<ElButton :loading="downloading" type="primary" @click="downloadHandler"></ElButton> <ElButton :loading="downloading" type="primary" @click="downloadHandler"></ElButton>
</template> </template>
</ElDialog> </ADialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { import {
@ -90,6 +88,7 @@ import {
import TplApi from '@/pages/sys/gen/tpl/tpl-api.ts' import TplApi from '@/pages/sys/gen/tpl/tpl-api.ts'
import Strings from '@/common/utils/strings.ts' import Strings from '@/common/utils/strings.ts'
import FormUtil from '@/common/utils/formUtil.ts' import FormUtil from '@/common/utils/formUtil.ts'
import ADialog from '@/components/a-dialog/ADialog.vue'
const activeName = ref('') const activeName = ref('')
const showDialog = ref(false) const showDialog = ref(false)
@ -256,7 +255,7 @@ defineExpose({
} }
& > div:nth-child(3) { & > div:nth-child(3) {
width: calc(800px - 272px); width: calc(800px - 288px);
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
} }

View File

@ -1,11 +1,11 @@
<template> <template>
<FormPage <ATablePage
:action-column="actionColumn" ref="tablePage"
:paging="paging"> v-bind="tablePageProps">
<template #searchFormItem="{searchForm}"> <template #simpleFormItem="formData">
<ElFormItem label="表名称"> <ElFormItem>
<ElInput <ElInput
v-model="searchForm.tableName" v-model="formData.tableName"
placeholder="表名称"/> placeholder="表名称"/>
</ElFormItem> </ElFormItem>
</template> </template>
@ -15,30 +15,35 @@
<ElTableColumn label="备注" prop="tableComment"/> <ElTableColumn label="备注" prop="tableComment"/>
</template> </template>
<CodePreview ref="codePreview"/> <CodePreview ref="codePreview"/>
</FormPage> </ATablePage>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import CodePreview from '@/pages/sys/gen/db-table/CodePreview.vue' import CodePreview from '@/pages/sys/gen/db-table/CodePreview.vue'
import DbTableApi from '@/pages/sys/gen/db-table/db-table-api.ts' import DbTableApi from '@/pages/sys/gen/db-table/db-table-api.ts'
import FormPage from '@/components/page/FormPage.vue' import ATablePage, { buildTablePageProps } from '@/components/a-page/a-table-page/ATablePage.tsx'
import type { ActionColumnType } from '@/components/page/a-page-type.ts'
const codePreviewIns = useTemplateRef<InstanceType<typeof CodePreview>>('codePreview') const codePreviewIns = useTemplateRef<InstanceType<typeof CodePreview>>('codePreview')
const actionColumn = reactive<ActionColumnType<DbTableTypes.TableInfo>>({ const tablePageProps = buildTablePageProps<DbTableTypes.SearchTplParam, DbTableTypes.TableInfo>({
pageLayout: {
enableHighForm: false,
},
searchForm: {
paging: DbTableApi.tablePaing,
},
table: {
actionColumn: {
tableActions: [ tableActions: [
{ {
tooltip: '预览', tooltip: '预览',
icon: 'Edit', icon: 'yulan',
action({row}: { row: DbTableTypes.TableInfo }) { action({row}: { row: DbTableTypes.TableInfo }) {
codePreviewIns.value?.open(row) codePreviewIns.value?.open(row)
}, },
}, },
], ],
},
},
}) })
function paging(param: DbTableTypes.SearchTplParam) {
return DbTableApi.tablePaing(param)
}
</script> </script>

View File

@ -1,19 +1,16 @@
<template> <template>
<FormPage <ATablePage
ref="formPage" ref="tablePage"
:action-column="actionColumn" v-bind="tablePageProps">
:default-search-form="defaultSearchForm" <template #simpleFormItem="formData">
:left-tools="leftTools" <ElFormItem>
:paging="paging">
<template #searchFormItem="{searchForm}">
<ElFormItem label="模板名称">
<ElInput <ElInput
v-model="searchForm.tplName" v-model="formData.tplName"
placeholder="模板名称"/> placeholder="模板名称"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="前端/后端"> <ElFormItem>
<ElSelect <ElSelect
v-model="searchForm.lang" v-model="formData.lang"
placeholder="前端/后端" placeholder="前端/后端"
> >
<ElOption <ElOption
@ -40,27 +37,38 @@
</template> </template>
</ElTableColumn> </ElTableColumn>
</template> </template>
<TplForm ref="tplForm" @edit-succ="research"/> <TplForm ref="tplForm" :research="research"/>
</FormPage> </ATablePage>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import TplApi from '@/pages/sys/gen/tpl/tpl-api.ts' import TplApi from '@/pages/sys/gen/tpl/tpl-api.ts'
import TplForm from '@/pages/sys/gen/tpl/TplForm.vue' import TplForm from '@/pages/sys/gen/tpl/TplForm.vue'
import FormPage from '@/components/page/FormPage.vue' import ATablePage, {
import type { type ATablePageInstance,
ActionColumnType, buildTablePageProps,
ToolType, } from '@/components/a-page/a-table-page/ATablePage.tsx'
} from '@/components/page/a-page-type.ts'
import type { ComponentExposed } from 'vue-component-type-helpers'
const defaultSearchForm: TplTypes.SearchTplParam = { const tablePageIns = useTemplateRef<ATablePageInstance>('tablePage')
const tplFormIns = useTemplateRef<InstanceType<typeof TplForm>>('tplForm')
const tablePageProps = buildTablePageProps<TplTypes.SearchTplParam, TplTypes.SearchTplResult>({
pageLayout: {
enableHighForm: false,
},
searchForm: {
defaultData: {
orders: 'lang,tpl_name', orders: 'lang,tpl_name',
tpl: {}, tpl: {},
} },
const tplFormIns = useTemplateRef<InstanceType<typeof TplForm>>('tplForm') highForm: {
contentWidth: 320,
const formPageIns = useTemplateRef<ComponentExposed<typeof FormPage>>('formPage') },
const leftTools: ToolType[] = [ simpleForm: {
colCount: 2,
},
paging: TplApi.paging,
},
toolBar: {
leftTools: [
{ {
icon: 'Plus', icon: 'Plus',
label: '新建', label: '新建',
@ -68,8 +76,10 @@ const leftTools: ToolType[] = [
tplFormIns.value?.open() tplFormIns.value?.open()
}, },
}, },
] ],
const actionColumn = reactive<ActionColumnType<TplTypes.SearchTplResult>>({ },
table: {
actionColumn: {
tableActions: [ tableActions: [
{ {
tooltip: '编辑', tooltip: '编辑',
@ -96,13 +106,12 @@ const actionColumn = reactive<ActionColumnType<TplTypes.SearchTplResult>>({
}, },
}, },
], ],
},
},
}) })
function research() { function research() {
formPageIns.value?.doSearch() tablePageIns.value?.doSearch()
} }
function paging(param: TplTypes.SearchTplParam) {
return TplApi.paging(param)
}
</script> </script>

View File

@ -1,7 +1,6 @@
<template> <template>
<ElDialog v-model="showDialog" <ADialog v-model:show="showDialog"
:close-on-click-modal="false" :title="status==='add'?'新增模板':'修改模板'"
destroy-on-close
width="25vw" width="25vw"
@close="dialogCloseHandler"> @close="dialogCloseHandler">
<ElForm :model="tplFormData" <ElForm :model="tplFormData"
@ -10,13 +9,11 @@
<ElFormItem label="模板名称"> <ElFormItem label="模板名称">
<ElInput <ElInput
v-model="tplFormData.tplName" v-model="tplFormData.tplName"
placeholder="模板名称"/> placeholder="模板名称"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="前端/后端"> <ElFormItem label="前端/后端">
<ElSelect <ElSelect
v-model="tplFormData.lang" v-model="tplFormData.lang"
placeholder="前端/后端" placeholder="前端/后端"
> >
<ElOption <ElOption
@ -31,7 +28,6 @@
<ElInput <ElInput
v-model="tplFormData.tpl.content" v-model="tplFormData.tpl.content"
:spellcheck="false" :spellcheck="false"
placeholder="模板内容" placeholder="模板内容"
resize="none" resize="none"
type="textarea"/> type="textarea"/>
@ -41,7 +37,6 @@
<ElInput <ElInput
:spellcheck="false" :spellcheck="false"
v-model="tplFormData.tpl.dir" v-model="tplFormData.tpl.dir"
placeholder="文件路径" placeholder="文件路径"
resize="none" resize="none"
type="textarea"/> type="textarea"/>
@ -51,7 +46,6 @@
<ElInput <ElInput
:spellcheck="false" :spellcheck="false"
v-model="tplFormData.tpl.filename" v-model="tplFormData.tpl.filename"
placeholder="文件名称" placeholder="文件名称"
resize="none" resize="none"
type="textarea"/> type="textarea"/>
@ -61,12 +55,13 @@
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton> <ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton> <ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</template> </template>
</ElDialog> </ADialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import TplApi from '@/pages/sys/gen/tpl/tpl-api.ts' import TplApi from '@/pages/sys/gen/tpl/tpl-api.ts'
import Strings from '@/common/utils/strings.ts' import Strings from '@/common/utils/strings.ts'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import ADialog from '@/components/a-dialog/ADialog.vue'
const emits = defineEmits([ 'editSucc' ]) const emits = defineEmits([ 'editSucc' ])
const showDialog = ref(false) const showDialog = ref(false)

View File

@ -1,80 +1,48 @@
<template> <template>
<ElDialog v-model="showDialog" <AFormPanel
:close-on-click-modal="false" ref="formPanel"
destroy-on-close width="25vw" @close="dialogCloseHandler"> v-bind="formPanelProps">
<ElForm :model="menuForm" class="menu-form"> <template #default="formData">
<div class="form-items">
<ElFormItem label="上级"> <ElFormItem label="上级">
<ElTreeSelect v-model="menuForm.pid" :data="menuTreeDataSource" :default-expanded-keys="['0']" :render-after-expand="false" check-strictly placeholder="选择上级菜单"/> <ElTreeSelect v-model="formData.pid" v-loading="loadingMenus" :data="menuTreeDataSource" :default-expanded-keys="['0']" :render-after-expand="false" check-strictly placeholder="选择上级菜单"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="客户端" prop="clients"> <ElFormItem label="客户端" prop="clients">
<ElCheckboxGroup v-model="menuForm.clients"> <ElCheckboxGroup v-model="formData.clients">
<ElCheckbox v-for="client in ClientUtil.clients" :key="client.val" :label="client.txt" :value="client.val"/> <ElCheckbox v-for="client in ClientUtil.clients" :key="client.val" :label="client.txt" :value="client.val"/>
</ElCheckboxGroup> </ElCheckboxGroup>
</ElFormItem> </ElFormItem>
<ElFormItem label="类型"> <ElFormItem label="类型">
<ElSelect v-model="menuForm.menuCategory" :data="menuCategoryData" placeholder="选择类型"> <ElSelect v-model="formData.menuCategory" :data="menuCategoryData" placeholder="选择类型">
<ElOption v-for="menuCategory in menuCategoryData" :key="menuCategory.key" :label="menuCategory.label" :value="menuCategory.key"/> <ElOption v-for="menuCategory in menuCategoryData" :key="menuCategory.key" :label="menuCategory.label" :value="menuCategory.key"/>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
<ElFormItem label="名称"> <ElFormItem label="名称">
<ElInput v-model="menuForm.title" placeholder="名称"/> <ElInput v-model="formData.title" placeholder="名称"/>
</ElFormItem> </ElFormItem>
<ElFormItem v-if="menuForm.menuCategory === MenuCategory.Page || menuForm.menuCategory === MenuCategory.SubPage" label="路由名称"> <ElFormItem v-if="formData.menuCategory === MenuCategory.Page || formData.menuCategory === MenuCategory.SubPage" label="路由名称">
<ElInput v-model="menuForm.routeName" :min="0" placeholder="路由名称"/> <ElInput v-model="formData.routeName" :min="0" placeholder="路由名称"/>
</ElFormItem> </ElFormItem>
<ElFormItem v-if="menuForm.menuCategory === MenuCategory.Page || menuForm.menuCategory === MenuCategory.SubPage" label="路由地址"> <ElFormItem v-if="formData.menuCategory === MenuCategory.Page || formData.menuCategory === MenuCategory.SubPage" label="路由地址">
<ElInput v-model="menuForm.routePath" :min="0" placeholder="路由地址"/> <ElInput v-model="formData.routePath" :min="0" placeholder="路由地址"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="编码"> <ElFormItem label="编码">
<ElInput v-model="menuForm.sn" :min="0" placeholder="编码"/> <ElInput v-model="formData.sn" :min="0" placeholder="编码"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="图标"> <ElFormItem label="图标">
<ElDropdown closable header="图标列表" placement="bottom" style="width: 100%" trigger="click"> <AIconDropTable v-model="formData.icon"/>
<ElInput v-model="menuForm.iconName" placeholder="选择图标" readonly>
<template #suffix>
<AIcon :name="menuForm.icon!"/>
</template>
</ElInput>
<template #dropdown>
<div class="dropdown-table-wrapper">
<ElTable
ref="dropdownTable"
:data="iconTableDataSource"
cell-class-name="table-cell"
class="table-list"
empty-text="暂无数据"
header-row-class-name="table-header"
row-key="id"
width="324"
@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" :total="pagination.total" layout="total, prev, pager, next" @change="pageChangeHandler"/>
</div>
</template>
</ElDropdown>
</ElFormItem> </ElFormItem>
<ElFormItem label="排序"> <ElFormItem label="排序">
<ElInputNumber v-model="menuForm.sort" :min="0" placeholder="排序" style="width: 100%"/> <ElInputNumber v-model="formData.sort" :min="0" controls-position="right" placeholder="排序"/>
</ElFormItem> </ElFormItem>
</ElForm> </div>
<template #footer>
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</template> </template>
</ElDialog> </AFormPanel>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'
import { import {
MenuCategory, MenuCategory,
MenuCategoryDict, MenuCategoryDict,
@ -82,62 +50,23 @@ 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,
type TableInstance,
} from 'element-plus'
import {
type IconGlyphs,
icons,
} from '@/components/a-icon/iconfont.ts'
import AIcon from '@/components/a-icon/AIcon.vue'
import ClientUtil from '@/common/utils/client-util.ts' import ClientUtil from '@/common/utils/client-util.ts'
import Utils from '@/common/utils' import AFormPanel, {
type AFormPanelInstance,
buildFormPanelProps,
} from '@/components/a-form-panel/AFormPanel.tsx'
import AIconDropTable from '@/components/a-icon/AIconDropTable.vue'
import { nanoid } from 'nanoid'
const emits = defineEmits([ 'editSucc' ]) const props = withDefaults(defineProps<{
research?: () => void
const pagination = reactive<G.Pagination>({ }>(), {
total: icons.glyphs.length, research: () => {
pages: Math.ceil(icons.glyphs.length / 5), },
current: 1,
size: 5,
}) })
const status = ref<'add' | 'modify'>('add')
function dialogCloseHandler() { const loadingMenus = ref<boolean>(false)
menuForm.value = { const formPanelIns = useTemplateRef<AFormPanelInstance>('formPanel')
icon: 'dianzixiaopiao',
}
}
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 dropdownTableIns = useTemplateRef<TableInstance>('dropdownTable')
let menuForm = ref<MenuTypes.MenuForm>({
icon: 'dianzixiaopiao',
})
const iconTableDataSource = ref<IconGlyphs[]>(
icons.glyphs.filter((_, i) => {
return i >= 0 && i < 5
}),
)
function currentChangeHandler(val?: IconGlyphs) {
if (val == null) return
menuForm.value.icon = val.font_class
menuForm.value.iconName = val.name
}
const showDialog = ref(false)
const submiting = ref(false)
const status = ref<'add' | 'view' | 'modify'>('add')
const menuCategoryData = computed(() => { const menuCategoryData = computed(() => {
const data: { key: string; label: string }[] = [] const data: { key: string; label: string }[] = []
@ -151,66 +80,61 @@ const menuCategoryData = computed(() => {
}) })
const menuTreeDataSource = ref<MenuTypes.SysMenu[]>() const menuTreeDataSource = ref<MenuTypes.SysMenu[]>()
const formPanelProps = buildFormPanelProps<MenuTypes.MenuForm>({
detailsLoader(id?: string) {
function submitHandler() { if (Strings.isBlank(id)) {
if (status.value === 'view') return status.value = 'add'
submiting.value = true return Promise.resolve({
menuForm.value.clientCode = ClientUtil.getClientCode(menuForm.value.clients!)
if (menuForm.value.id != null) {
MenuApi.modify(menuForm.value)
.then(() => {
ElMessage.success('修改成功')
showDialog.value = false
emits(('editSucc'))
})
.finally(() => {
submiting.value = false
})
} else {
MenuApi.add(menuForm.value)
.then(() => {
ElMessage.success('添加成功')
showDialog.value = false
emits(('editSucc'))
})
.finally(() => {
submiting.value = false
})
}
}
defineExpose({
open(data: MenuTypes.MenuForm = {
icon: 'dianzixiaopiao', icon: 'dianzixiaopiao',
}) { pid: '0',
if (!Strings.isBlank(data.id)) { clients: [ 1 ],
menuCategory: MenuCategory.Catalog,
sn: nanoid(10),
} as MenuTypes.MenuForm)
} else {
status.value = 'modify' status.value = 'modify'
data = Utils.clone(data) return MenuApi
data.clients = ClientUtil.getClients(data.clientCode!).map(it => it.val) .detail(id!)
menuForm.value = data .then((res) => {
return {
...res.data,
clients: ClientUtil.getClients(res.data.clientCode!).map(it => it.val),
} as MenuTypes.MenuForm
})
}
},
doSubmit(data) {
if (status.value === 'add') {
return MenuApi.add(data)
.then(props.research)
} else { } else {
menuForm.value = { return MenuApi.modify(data)
icon: 'dianzixiaopiao', .then(props.research)
} }
}
showDialog.value = true
dropdownTableIns.value?.setCurrentRow(icons.glyphs.find((it) => it.font_class === data.icon))
}, },
}) })
watchEffect(() => {
onMounted(() => { formPanelProps.title = status.value === 'add' ? '新建菜单' : '修改菜单'
MenuApi.list().then(({data}) => { })
defineExpose({
open(data?: MenuTypes.MenuForm) {
loadingMenus.value = true
MenuApi.list()
.then(({data}) => {
const nodes = Colls.toTree(data.map((it) => ({value: it.id, label: it.title, ...it}))) const nodes = Colls.toTree(data.map((it) => ({value: it.id, label: it.title, ...it})))
menuTreeDataSource.value = [ {value: '0', label: '顶级菜单', id: '0', pid: '0', children: nodes} ] menuTreeDataSource.value = [ {value: '0', label: '顶级菜单', id: '0', pid: '0', children: nodes} ]
}) })
.finally(() => {
loadingMenus.value = false
})
formPanelIns.value?.open(data?.id)
},
}) })
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.menu-form { .form-items {
padding 20px grid-template-columns: 1fr 1fr;
} }
.dropdown-table-wrapper { .dropdown-table-wrapper {

View File

@ -1,28 +1,13 @@
<template> <template>
<FormPage <ATablePage
ref="formPage" ref="tablePage"
:action-column="actionColumn" v-bind="tablePageProps">
:left-tools="leftTools" <template #simpleFormItem="formData">
:list="listAll"
:table-props="{
treeProps: { children: 'children', hasChildren: 'hasChildren' },
treeLoad: treeLoad,
}"
>
<template #searchFormItem="{ searchForm }">
<ElFormItem label="菜单名称">
<ElInput v-model="searchForm.title" placeholder="菜单名称"/>
</ElFormItem>
<ElFormItem label="路由名称">
<ElInput v-model="searchForm.routeName" placeholder="路由名称"/>
</ElFormItem>
</template>
<template #simpleSearchFormItem="{ searchForm }">
<ElFormItem> <ElFormItem>
<ElInput v-model="searchForm.title" placeholder="菜单名称"/> <ElInput v-model="formData.title" placeholder="菜单名称"/>
</ElFormItem> </ElFormItem>
<ElFormItem> <ElFormItem>
<ElInput v-model="searchForm.routeName" placeholder="路由名称"/> <ElInput v-model="formData.routeName" placeholder="路由名称"/>
</ElFormItem> </ElFormItem>
</template> </template>
<template #columns> <template #columns>
@ -48,12 +33,9 @@
</span> </span>
</template> </template>
</ElTableColumn> </ElTableColumn>
</template> </template>
<MenuForm ref="menuForm" :research="research"/>
<MenuForm ref="menuForm" @editSucc="research"/> </ATablePage>
</FormPage>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -61,17 +43,50 @@ import MenuApi from '@/pages/sys/menus/menu-api.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.vue' import AIcon from '@/components/a-icon/AIcon.vue'
import FormPage from '@/components/page/FormPage.vue' import ATablePage, {
import type { type ATablePageInstance,
ActionColumnType, buildTablePageProps,
ToolType, } from '@/components/a-page/a-table-page/ATablePage.tsx'
} from '@/components/page/a-page-type.ts' import Colls from '@/common/utils/colls.ts'
import type { ComponentExposed } from 'vue-component-type-helpers'
const menuFormIns = useTemplateRef<InstanceType<typeof MenuForm>>('menuForm') const menuFormIns = useTemplateRef<InstanceType<typeof MenuForm>>('menuForm')
const formPageIns = useTemplateRef<ComponentExposed<typeof FormPage>>('formPage') const tablePageIns = useTemplateRef<ATablePageInstance>('tablePage')
const actionColumn = reactive<ActionColumnType<MenuTypes.SysMenu>>({ const tablePageProps = buildTablePageProps<MenuTypes.SearchForm, MenuTypes.SysMenu>({
pageLayout: {
enableHighForm: false,
},
searchForm: {
simpleForm: {
colCount: 2,
},
list() {
return MenuApi.listAll()
.then(res => {
res.data = Colls.toTree(res.data ?? [])
.map(it => {
return {
...it,
hasChildren: !Colls.isEmpty(it.children),
}
})
return res
})
},
},
toolBar: {
leftTools: [
{
icon: 'Plus',
label: '新建',
action() {
menuFormIns.value?.open()
},
},
],
},
table: {
actionColumn: {
tableActions: [ tableActions: [
{ {
tooltip: '编辑', tooltip: '编辑',
@ -98,52 +113,24 @@ const actionColumn = reactive<ActionColumnType<MenuTypes.SysMenu>>({
}, },
}, },
], ],
},
},
}) })
const leftTools: ToolType[] = [
{
icon: 'Plus',
label: '新建',
action() {
menuFormIns.value?.open()
},
},
]
function research() { function research() {
formPageIns.value?.doSearch() tablePageIns.value?.doSearch()
} }
function listAll(param: MenuTypes.SearchForm) {
return MenuApi.listAll({...param, pid: '0'})
.then(res => {
const data = res.data ?? []
for (let it of data) {
it.hasChildren = true
it.children = []
}
return res
})
}
function treeLoad(param: MenuTypes.SearchForm, row: MenuTypes.SysMenu, expanded: any, resolve?: (data: MenuTypes.SysMenu[]) => void) {
if (resolve == null && !expanded) return
MenuApi.listAll({...param, pid: row.id})
.then(res => {
if (resolve != null) {
resolve(res.data?.map(it => {
it.hasChildren = true
return it
}) ?? [])
} else {
formPageIns.value?.dataTableIns?.updateKeyChildren(row.id, res.data?.map(it => {
it.hasChildren = true
it.children = []
return it
}) ?? [])
}
})
}
</script> </script>
<style lang="stylus" scoped>
.expand-btn {
margin-right 5px
cursor pointer
transition transform .3s ease-in-out
margin-left calc(var(--expand-indent) * 16px)
&.expand-btn-expanded {
transform rotateZ(90deg)
}
}
</style>

View File

@ -8,7 +8,7 @@ export default {
return get<G.PageResult<MenuTypes.SysMenu>>('/menu/page_list', data) return get<G.PageResult<MenuTypes.SysMenu>>('/menu/page_list', data)
}, },
paging(data: MenuTypes.SearchForm & G.PageParam) { paging(data: MenuTypes.SearchForm & G.PageParam) {
return get<G.PageResult<MenuTypes.SysMenu>>('/menu/page_list', data) return get<G.PageResult<MenuTypes.SysMenu>>('/menu/paging', data)
}, },
list(data?: MenuTypes.SearchForm | null) { list(data?: MenuTypes.SearchForm | null) {
return get<MenuTypes.SysMenu[]>('/menu/list_all', data) return get<MenuTypes.SysMenu[]>('/menu/list_all', data)

View File

@ -26,6 +26,7 @@ declare global {
// 面包路径 // 面包路径
breadcrumb?: string[] breadcrumb?: string[]
menuCategory?: MenuCategory menuCategory?: MenuCategory
hasChildren?: boolean
children?: SysMenu[] children?: SysMenu[]
} }

View File

@ -1,7 +1,6 @@
<template> <template>
<ElDialog v-model="showDialog" <ADialog v-model:show="showDialog"
:close-on-click-modal="false" title="分配资源"
destroy-on-close
width="900"> width="900">
<ElTransfer <ElTransfer
v-model="rightValue" v-model="rightValue"
@ -53,7 +52,7 @@
<ElButton @click="showDialog = false">取消</ElButton> <ElButton @click="showDialog = false">取消</ElButton>
<ElButton :disabled="rightValue.length <= 0" :loading="submiting" type="primary" @click="submitHandler"></ElButton> <ElButton :disabled="rightValue.length <= 0" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</template> </template>
</ElDialog> </ADialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import RoleApi from '@/pages/sys/role/role-api.ts' import RoleApi from '@/pages/sys/role/role-api.ts'
@ -63,6 +62,7 @@ import {
type TransferDataItem, type TransferDataItem,
} from 'element-plus' } from 'element-plus'
import ResourceApi from '@/pages/sys/resource/resource-api.ts' import ResourceApi from '@/pages/sys/resource/resource-api.ts'
import ADialog from '@/components/a-dialog/ADialog.vue'
const showDialog = ref(false) const showDialog = ref(false)
const submiting = ref(false) const submiting = ref(false)

View File

@ -1,60 +1,64 @@
<template> <template>
<FormPage <ATablePage
ref="formPage" ref="tablePage"
:action-column="actionColumn" v-bind="tablePageProps">
:left-tools="leftTools" <template #simpleFormItem="formData">
:paging="paging"> <ElFormItem>
<template #searchFormItem="{ searchForm }">
<ElFormItem label="角色代码">
<ElInput <ElInput
v-model="searchForm.roleCode" v-model="formData.roleCode"
placeholder="角色代码"/> placeholder="角色代码"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="角色名称"> <ElFormItem>
<ElInput <ElInput
v-model="searchForm.roleName" v-model="formData.roleName"
placeholder="角色名称"/> placeholder="角色名称"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="备注">
<ElInput
v-model="searchForm.memo"
placeholder="备注"/>
</ElFormItem>
</template> </template>
<template #columns> <template #columns>
<ElTableColumn label="角色代码" prop="roleCode"/> <ElTableColumn label="角色代码" prop="roleCode"/>
<ElTableColumn label="角色名称" prop="roleName"/> <ElTableColumn label="角色名称" prop="roleName"/>
<ElTableColumn label="备注" prop="memo"/> <ElTableColumn label="备注" prop="memo"/>
</template> </template>
<RoleForm ref="roleForm" @edit-succ="research"/> <RoleForm ref="roleForm" :research="research"/>
<BindRes ref="bindRes"/> <BindRes ref="bindRes"/>
</FormPage> </ATablePage>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import RoleApi from '@/pages/sys/role/role-api.ts' import RoleApi from '@/pages/sys/role/role-api.ts'
import RoleForm from '@/pages/sys/role/RoleForm.vue' import RoleForm from '@/pages/sys/role/RoleForm.vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import BindRes from '@/pages/sys/role/BindRes.vue' import BindRes from '@/pages/sys/role/BindRes.vue'
import FormPage from '@/components/page/FormPage.vue' import ATablePage, {
import type { type ATablePageInstance,
ActionColumnType, buildTablePageProps,
ToolType, } from '@/components/a-page/a-table-page/ATablePage.tsx'
} from '@/components/page/a-page-type.ts'
import type { ComponentExposed } from 'vue-component-type-helpers'
const roleFormIns = useTemplateRef<InstanceType<typeof RoleForm>>('roleForm') const roleFormIns = useTemplateRef<InstanceType<typeof RoleForm>>('roleForm')
const bindResIns = useTemplateRef<InstanceType<typeof BindRes>>('bindRes') const bindResIns = useTemplateRef<InstanceType<typeof BindRes>>('bindRes')
const formPageIns = useTemplateRef<ComponentExposed<typeof FormPage>>('formPage') const tablePageIns = useTemplateRef<ATablePageInstance>('tablePage')
const tablePageProps = buildTablePageProps<RoleTypes.SearchRoleParam, RoleTypes.SearchRoleResult>({
const actionColumn = reactive<ActionColumnType<RoleTypes.SearchRoleResult>>({ pageLayout: {
tableActions: [ enableHighForm: false,
},
searchForm: {
simpleForm: {colCount: 2},
paging: RoleApi.paging,
},
toolBar: {
leftTools: [
{ {
tooltip: '资源', icon: 'Plus',
icon: 'ScaleToOriginal', label: '新建',
action({row}: { row: RoleTypes.SearchRoleResult }) { action() {
bindResIns.value?.open(row.id!) roleFormIns.value?.open()
}, },
}, },
],
},
table: {
actionColumn: {
tableActions: [
{ {
tooltip: '编辑', tooltip: '编辑',
icon: 'Edit', icon: 'Edit',
@ -70,11 +74,22 @@ const actionColumn = reactive<ActionColumnType<RoleTypes.SearchRoleResult>>({
roleFormIns.value?.open(row) roleFormIns.value?.open(row)
}, },
}, },
{
tooltip: '资源',
type: 'warning',
icon: 'caidan',
action({row}: { row: RoleTypes.SearchRoleResult }) {
bindResIns.value?.open(row.id!)
},
},
{ {
icon: 'Delete', icon: 'Delete',
loading: false, loading: false,
type: 'danger', type: 'danger',
tooltip: '删除', tooltip: '删除',
show({row}) {
return row.id != '1'
},
confirm: { confirm: {
title: '是否删除当前数据', title: '是否删除当前数据',
}, },
@ -91,22 +106,11 @@ const actionColumn = reactive<ActionColumnType<RoleTypes.SearchRoleResult>>({
}, },
}, },
], ],
},
},
}) })
const leftTools: ToolType[] = [
{
icon: 'Plus',
label: '新建',
action() {
roleFormIns.value?.open()
},
},
]
function research() { function research() {
formPageIns.value?.doSearch() tablePageIns.value?.doSearch()
}
function paging(params: RoleTypes.SearchRoleParam) {
return RoleApi.paging(params)
} }
</script> </script>

View File

@ -1,91 +1,77 @@
<template> <template>
<ElDialog v-model="showDialog" <AFormPanel
:close-on-click-modal="false" ref="formPanel"
@close="dialogCloseHandler" v-bind="formPanelProps">
destroy-on-close <template #default="formData">
width="25vw"> <div class="form-items">
<ElForm :model="roleFormData"
class="sys_role-form"
>
<ElFormItem label="角色代码"> <ElFormItem label="角色代码">
<ElInput <ElInput
v-model="roleFormData.roleCode" v-model="formData.roleCode"
placeholder="角色代码"/> placeholder="角色代码"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="角色名称"> <ElFormItem label="角色名称">
<ElInput <ElInput
v-model="roleFormData.roleName" v-model="formData.roleName"
placeholder="角色名称"/> placeholder="角色名称"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="备注"> <ElFormItem label="备注">
<ElInput <ElInput
v-model="roleFormData.memo" v-model="formData.memo"
placeholder="备注"/> placeholder="备注"/>
</ElFormItem> </ElFormItem>
</ElForm> </div>
<template #footer>
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</template> </template>
</ElDialog> </AFormPanel>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import RoleApi from '@/pages/sys/role/role-api.ts' import RoleApi from '@/pages/sys/role/role-api.ts'
import Strings from '@/common/utils/strings.ts' import Strings from '@/common/utils/strings.ts'
import { ElMessage } from 'element-plus' import AFormPanel, {
type AFormPanelInstance,
buildFormPanelProps,
} from '@/components/a-form-panel/AFormPanel.tsx'
const emits = defineEmits([ 'editSucc' ]) const props = withDefaults(defineProps<{
const showDialog = ref(false) research?: () => void
const submiting = ref(false) }>(), {
const status = ref<'add' | 'view' | 'modify'>('add') research: () => {
const roleFormData = ref<RoleTypes.SearchRoleResult>({}) },
function dialogCloseHandler() {
roleFormData.value = {}
}
function submitHandler() {
if (status.value === 'view') return
submiting.value = true
if (roleFormData.value.id != null) {
RoleApi.modify(roleFormData.value)
.then(() => {
ElMessage.success('修改成功')
emits('editSucc')
showDialog.value = false
})
.finally(() => {
submiting.value = false
}) })
const status = ref<'add' | 'modify'>('add')
const formPanelIns = useTemplateRef<AFormPanelInstance>('formPanel')
const formPanelProps = buildFormPanelProps<RoleTypes.SearchRoleResult>({
detailsLoader(id?: string) {
if (Strings.isBlank(id)) {
status.value = 'add'
return Promise.resolve()
} else { } else {
RoleApi.add(roleFormData.value) status.value = 'modify'
.then(() => { return RoleApi.detail(id!)
ElMessage.success('添加成功') .then(res => {
emits('editSucc') return {
showDialog.value = false ...res.data,
}) }
.finally(() => {
submiting.value = false
}) })
} }
},
doSubmit(data) {
if (status.value === 'add') {
return RoleApi.add(data)
.then(props.research)
} else {
return RoleApi.modify(data)
.then(props.research)
} }
},
})
watchEffect(() => {
formPanelProps.title = status.value === 'add' ? '新建角色' : '修改角色'
})
defineExpose({ defineExpose({
open(data: RoleTypes.SearchRoleResult = {}) { open(data?: RoleTypes.SearchRoleResult) {
showDialog.value = true formPanelIns.value?.open(data?.id)
if (!Strings.isBlank(data.id)) {
status.value = 'modify'
RoleApi.detail(data.id!)
.then(res => {
roleFormData.value = res.data
})
} else {
status.value = 'add'
roleFormData.value = data
}
}, },
}) })
</script> </script>

View File

@ -1,39 +1,51 @@
<template> <template>
<FormPage <ATablePage
ref="formPage" ref="tablePage"
:action-column="actionColumn" v-bind="tablePageProps">
:left-tools="leftTools" <template #highFormItem="formData">
:paging="paging">
<template #searchFormItem="{searchForm}">
<ElFormItem label="任务名称"> <ElFormItem label="任务名称">
<ElInput <ElInput
v-model="searchForm.taskName" v-model="formData.taskName"
placeholder="任务名称"/> placeholder="任务名称"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="任务执行函数"> <ElFormItem label="执行函数">
<ElInput <ElInput
v-model="searchForm.fn" v-model="formData.fn"
placeholder="任务执行函数"/> placeholder="执行函数"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="调度方式"> <ElFormItem label="调度方式">
<ElSelect <ElSelect
v-model="searchForm.scheduleType" v-model="formData.scheduleType"
clearable placeholder="请选择调度方式" style="width: 150px" @change="paging" @clear="paging"> clearable placeholder="请选择调度方式" @change="research" @clear="research">
<ElOption v-for="item in scheduleTypeList" :key="item.value" :label="item.label" :value="item.value"/> <ElOption v-for="item in scheduleTypeList" :key="item.value" :label="item.label" :value="item.value"/>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
<ElFormItem label="内部任务">
<ElCheckbox <ElFormItem v-if="!formData.builtin" label="状态">
@change="research" <ElSelect v-model="formData.disabled" clearable placeholder="请选择状态" @clear="research">
v-model="searchForm.builtin"
placeholder="内部任务"/>
</ElFormItem>
<ElFormItem v-if="!searchForm.builtin" label="状态">
<ElSelect v-model="searchForm.disabled" clearable placeholder="请选择状态" style="width: 150px" @clear="paging">
<ElOption :value="true" label="禁用"/> <ElOption :value="true" label="禁用"/>
<ElOption :value="false" label="启用"/> <ElOption :value="false" label="启用"/>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
</template>
<template #simpleFormItem="formData">
<ElFormItem>
<ElSwitch v-model="formData.builtin"
active-text="系统任务"
inactive-text="用户任务"
@change="research"/>
</ElFormItem>
<ElFormItem>
<ElInput
v-model="formData.taskName"
placeholder="任务名称"/>
</ElFormItem>
<ElFormItem>
<ElInput
v-model="formData.fn"
placeholder="任务执行函数"/>
</ElFormItem>
</template> </template>
<template #columns> <template #columns>
<ElTableColumn label="任务名称" prop="taskName"/> <ElTableColumn label="任务名称" prop="taskName"/>
@ -53,49 +65,57 @@
</span> </span>
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="是否禁用" prop="disabled" width="100">
<ElTableColumn label="是否禁用" width="180"> <template #default="{row}">
<template #default="scope"> <ElSwitch v-model="row.disabled" :disabled="row.builtin" @change="disabledUserHandler($event,row.id)"/>
<span>
{{ scope.row.disabled ? '是' : '否' }}
</span>
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="备注" prop="memo"/> <ElTableColumn label="备注" prop="memo"/>
</template> </template>
<TaskForm ref="taskForm" @edit-succ="research"/> <TaskForm ref="taskForm" :research="research"/>
<ScheduleRecodePanel ref="scheduleRecodePanel"/> <ScheduleRecodePanel ref="scheduleRecodePanel"/>
</FormPage> </ATablePage>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import TaskApi from '@/pages/sys/task/task-api.ts' import TaskApi from '@/pages/sys/task/task-api.ts'
import TaskForm from '@/pages/sys/task/TaskForm.vue' import TaskForm from '@/pages/sys/task/TaskForm.vue'
import ScheduleRecodePanel from '@/pages/sys/task/schedule-recode/ScheduleRecodePanel.vue' import ScheduleRecodePanel from '@/pages/sys/task/schedule-recode/ScheduleRecodePanel.vue'
import Times from '@/common/utils/times.ts' import Times from '@/common/utils/times.ts'
import FormPage from '@/components/page/FormPage.vue' import ATablePage, {
import type { type ATablePageInstance,
ActionColumnType, buildTablePageProps,
ToolType, } from '@/components/a-page/a-table-page/ATablePage.tsx'
} from '@/components/page/a-page-type.ts' import { ElMessage } from 'element-plus'
import type { ComponentExposed } from 'vue-component-type-helpers'
const formPageIns = useTemplateRef<ComponentExposed<typeof FormPage>>('formPage')
function research() {
formPageIns.value?.doSearch()
}
const taskFormIns = useTemplateRef<InstanceType<typeof TaskForm>>('taskForm') const taskFormIns = useTemplateRef<InstanceType<typeof TaskForm>>('taskForm')
const scheduleRecodePanelIns = useTemplateRef<InstanceType<typeof ScheduleRecodePanel>>('scheduleRecodePanel') const tablePageIns = useTemplateRef<ATablePageInstance>('tablePage')
const scheduleTypeList = [ const tablePageProps = buildTablePageProps<TaskTypes.SearchTaskParam, TaskTypes.SearchTaskResult>({
{value: 'Manually', label: '手动'}, pageLayout: {
{value: 'Fixed', label: '固定周期'}, searchFormHeight: '64px',
{value: 'Cron', label: '自定义'}, },
] searchForm: {
highForm: {
contentWidth: 200,
const actionColumn = reactive<ActionColumnType<TaskTypes.SearchTaskResult>>({ },
simpleForm: {
colCount: 3,
},
paging: TaskApi.paging,
},
toolBar: {
leftTools: [
{
icon: 'Plus',
label: '新建',
action() {
taskFormIns.value?.open()
},
},
],
},
table: {
actionColumn: {
foldLimit: 4,
tableActions: [ tableActions: [
{ {
tooltip: '编辑', tooltip: '编辑',
@ -108,25 +128,10 @@ const actionColumn = reactive<ActionColumnType<TaskTypes.SearchTaskResult>>({
taskFormIns.value?.open(row) taskFormIns.value?.open(row)
}, },
}, },
{
tooltip({row}) {
return row.disabled ? '启用' : '禁用'
},
show({row}) {
return !row.builtin
},
icon: 'Edit',
action({row}) {
return TaskApi.disable(row.id!, !row.disabled!)
.then(() => {
ElMessage.success(row.disabled ? '启用成功' : '禁用成功')
return true
})
},
},
{ {
tooltip: '执行一次', tooltip: '执行一次',
icon: 'Edit', icon: 'putongzhihang',
type: 'primary',
show({row}) { show({row}) {
return !row.disabled return !row.disabled
}, },
@ -139,7 +144,8 @@ const actionColumn = reactive<ActionColumnType<TaskTypes.SearchTaskResult>>({
}, },
{ {
tooltip: '调度记录', tooltip: '调度记录',
icon: 'Edit', type: 'default',
icon: 'zhihangjilu',
action({row}) { action({row}) {
scheduleRecodePanelIns.value?.open(row.id!) scheduleRecodePanelIns.value?.open(row.id!)
}, },
@ -163,19 +169,27 @@ const actionColumn = reactive<ActionColumnType<TaskTypes.SearchTaskResult>>({
}, },
}, },
], ],
},
},
}) })
const leftTools: ToolType[] = [
{
icon: 'Plus',
label: '新建',
action() {
taskFormIns.value?.open()
},
},
]
function paging(param: TaskTypes.SearchTaskParam) { function disabledUserHandler(val: string | number | boolean, id: string) {
return TaskApi.paging(param) TaskApi.disable(id, val as boolean)
.then(() => {
ElMessage.success(val ? '禁用成功' : '启用成功')
research()
return true
})
} }
function research() {
tablePageIns.value?.doSearch()
}
const scheduleRecodePanelIns = useTemplateRef<InstanceType<typeof ScheduleRecodePanel>>('scheduleRecodePanel')
const scheduleTypeList = [
{value: 'Manually', label: '手动'},
{value: 'Fixed', label: '固定周期'},
{value: 'Cron', label: '自定义'},
]
</script> </script>

View File

@ -1,54 +1,62 @@
<template> <template>
<ElDialog v-model="showDialog" <AFormPanel
:close-on-click-modal="false" ref="formPanel"
destroy-on-close width="25vw" @close="dialogCloseHandler"> v-bind="formPanelProps">
<ElForm :model="taskFormData" class="sys_task-form"> <template #default="formData">
<div class="form-items">
<ElFormItem label="任务名称"> <ElFormItem label="任务名称">
<ElInput v-model="taskFormData.taskName" placeholder="任务名称"/> <ElInput v-model="formData.taskName" placeholder="任务名称"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="任务执行函数"> <ElFormItem label="执行函数">
<ElSelect v-model="taskFormData.fn" placeholder="请选择任务执行函数"> <ElSelect v-model="formData.fn" placeholder="请选择任务执行函数">
<ElOption v-for="item in fnList" :key="item.fn" :label="item.fn" :value="item.fn!"/> <ElOption v-for="item in fnList" :key="item.fn" :label="item.fn" :value="item.fn!"/>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
<ElFormItem label="日志等级"> <ElFormItem label="日志等级">
<ElSelect v-model="taskFormData.logLevel" placeholder="请选择日志等级"> <ElSelect v-model="formData.logLevel" placeholder="请选择日志等级">
<ElOption v-for="item in logLevelList" :key="item.value" :label="item.label" :value="item.value"/> <ElOption v-for="item in logLevelList" :key="item.value" :label="item.label" :value="item.value"/>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
<ElFormItem label="调度方式"> <ElFormItem label="调度方式">
<ElSelect v-model="taskFormData.scheduleType" placeholder="请选择调度方式" @change="scheduleTypeChange"> <ElSelect v-model="formData.scheduleType" placeholder="请选择调度方式">
<ElOption v-for="item in scheduleTypeList" :key="item.value" :label="item.label" :value="item.value"/> <ElOption v-for="item in scheduleTypeList" :key="item.value" :label="item.label" :value="item.value"/>
</ElSelect> </ElSelect>
</ElFormItem> </ElFormItem>
<ElFormItem v-if="taskFormData.scheduleType === 'Cron'" label="调度配置"> <ElFormItem v-if="formData.scheduleType === 'Cron'" label="调度配置">
<Cron v-model="taskFormData.scheduleConf" placeholder="调度配置"/> <Cron v-model="formData.scheduleConf" placeholder="调度配置"/>
</ElFormItem> </ElFormItem>
<ElFormItem v-else-if="taskFormData.scheduleType === 'Fixed'" label="调度配置"> <ElFormItem v-else-if="formData.scheduleType === 'Fixed'" label="调度配置">
<ElInput v-model="taskFormData.scheduleConf" placeholder="调度配置"> <ElInput v-model="formData.scheduleConf" placeholder="调度配置">
<template #suffix>周期</template> <template #suffix>周期</template>
</ElInput> </ElInput>
</ElFormItem> </ElFormItem>
<ElFormItem label="是否禁用"> <ElFormItem label="是否禁用">
<ElSwitch v-model="taskFormData.disabled" active-text="" inactive-text=""/> <ElSwitch v-model="formData.disabled" active-text="" inactive-text=""/>
</ElFormItem> </ElFormItem>
<ElFormItem label="备注"> <ElFormItem label="备注">
<ElInput v-model="taskFormData.memo" placeholder="备注"/> <ElInput v-model="formData.memo" placeholder="备注"/>
</ElFormItem> </ElFormItem>
</ElForm> </div>
<template #footer>
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</template> </template>
</ElDialog> </AFormPanel>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import TaskApi from '@/pages/sys/task/task-api.ts' import TaskApi from '@/pages/sys/task/task-api.ts'
import Strings from '@/common/utils/strings.ts' import Strings from '@/common/utils/strings.ts'
import { ElMessage } from 'element-plus'
import Cron from '@/pages/sys/task/cron/Cron.vue' import Cron from '@/pages/sys/task/cron/Cron.vue'
import AFormPanel, {
type AFormPanelInstance,
buildFormPanelProps,
} from '@/components/a-form-panel/AFormPanel.tsx'
const emits = defineEmits([ 'editSucc' ]) const props = withDefaults(defineProps<{
research?: () => void
}>(), {
research: () => {
},
})
const status = ref<'add' | 'modify'>('add')
const formPanelIns = useTemplateRef<AFormPanelInstance>('formPanel')
const scheduleTypeList = [ const scheduleTypeList = [
{value: 'Manually', label: '手动'}, {value: 'Manually', label: '手动'},
{value: 'Fixed', label: '固定周期'}, {value: 'Fixed', label: '固定周期'},
@ -61,73 +69,66 @@ const logLevelList = [
{value: 'ERROR', label: '错误'}, {value: 'ERROR', label: '错误'},
{value: 'OFF', label: '关闭'}, {value: 'OFF', label: '关闭'},
] ]
const showDialog = ref(false)
const submiting = ref(false)
const status = ref<'add' | 'view' | 'modify'>('add')
let taskFormData = ref<TaskTypes.SearchTaskResult>({})
const fnList = ref<TaskTypes.FnHandle[]>([]) const fnList = ref<TaskTypes.FnHandle[]>([])
const formPanelProps = buildFormPanelProps<TaskTypes.SearchTaskResult>({
function dialogCloseHandler() { detailsLoader(id?: string) {
taskFormData.value = {} if (Strings.isBlank(id)) {
} status.value = 'add'
return Promise.resolve({
function scheduleTypeChange(val: string) { scheduleConf: '* * * * * ?',
if (val === 'Manually') { scheduleType: 'Manually',
taskFormData.value.scheduleConf = '' logLevel: 'DEBUG',
} else if (val === 'Fixed') { disabled: false,
taskFormData.value.scheduleConf = '1'
} else if (val === 'Cron') {
taskFormData.value.scheduleConf = '* * * * * ?'
}
}
function submitHandler() {
if (status.value === 'view') return
submiting.value = true
if (taskFormData.value.id != null) {
TaskApi.modify(taskFormData.value)
.then(() => {
ElMessage.success('修改成功')
emits('editSucc')
showDialog.value = false
})
.finally(() => {
submiting.value = false
}) })
} else { } else {
TaskApi.add(taskFormData.value) status.value = 'modify'
.then(() => { return TaskApi.detail(id!).then((res) => res.data)
ElMessage.success('添加成功')
emits('editSucc')
showDialog.value = false
})
.finally(() => {
submiting.value = false
})
} }
},
doSubmit(data) {
if (status.value === 'add') {
return TaskApi.add(data)
.then(props.research)
} else {
return TaskApi.modify(data)
.then(props.research)
} }
},
watchForm: {
formItems: [],
callback(formData) {
const val = formData.scheduleType
if (val === 'Manually') {
formData.scheduleConf = ''
} else if (val === 'Fixed') {
formData.scheduleConf = '1'
} else if (val === 'Cron') {
formData.scheduleConf = '* * * * * ?'
}
},
},
})
watchEffect(() => {
formPanelProps.title = status.value === 'add' ? '新建任务' : '修改任务'
})
defineExpose({ defineExpose({
open(data: TaskTypes.SearchTaskResult = {}) { open(data?: TaskTypes.SearchTaskResult) {
showDialog.value = true formPanelIns.value?.open(data?.id)
TaskApi.fn().then(res => { TaskApi.fn().then(res => {
fnList.value = res.data fnList.value = res.data
}) })
if (!Strings.isBlank(data.id)) {
status.value = 'modify'
TaskApi.detail(data.id!).then((res) => {
taskFormData.value = res.data
})
} else {
status.value = 'add'
data.scheduleConf = '* * * * * ?'
taskFormData.value = data
}
}, },
}) })
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.sys_task-form { .form-items {
padding 20px grid-template-columns: 1fr 1fr;
grid-template-areas: "picture ." \
"picture .";
:deep(.el-form-item) {
}
} }
</style> </style>

View File

@ -2,6 +2,7 @@
import { elIcons } from '@/common/element/element.ts' import { elIcons } from '@/common/element/element.ts'
import CronPanel from '@/pages/sys/task/cron/cron-panel/CronPanel.vue' import CronPanel from '@/pages/sys/task/cron/cron-panel/CronPanel.vue'
import { useCronStore } from '@/pages/sys/task/cron/cron-store.ts' import { useCronStore } from '@/pages/sys/task/cron/cron-store.ts'
import ADialog from '@/components/a-dialog/ADialog.vue'
const props = defineProps<{ const props = defineProps<{
modelValue?: string modelValue?: string
@ -41,12 +42,11 @@ function openPanel() {
<ElButton :icon="elIcons.Timer" type="text" @click="openPanel"/> <ElButton :icon="elIcons.Timer" type="text" @click="openPanel"/>
</template> </template>
</ElInput> </ElInput>
<ElDialog v-model="showDialog" <ADialog v-model:show="showDialog"
title="配置面板" title="配置面板"
:close-on-click-modal="false" width="50vw">
destroy-on-close width="50vw">
<CronPanel/> <CronPanel/>
</ElDialog> </ADialog>
</template> </template>
<style lang="stylus" scoped> <style lang="stylus" scoped>

View File

@ -23,9 +23,9 @@
</ElFormItem> </ElFormItem>
</ElForm> </ElForm>
<div class="tool-bar"> <!-- <div class="tool-bar">
<ElButton :icon="elIcons.Filter" type="default" @click="showSearchForm = !showSearchForm"/> <ElButton :icon="elIcons.Filter" type="default" @click="showSearchForm = !showSearchForm"/>
</div> </div> -->
<ElTable v-loading="searching" :data="tableData" <ElTable v-loading="searching" :data="tableData"
cell-class-name="table-cell" cell-class-name="table-cell"

View File

@ -1,18 +1,18 @@
<template> <template>
<ElDialog v-model="showDialog" <ADialog v-model:show="showDialog"
:close-on-click-modal="false" :closed="dialogCloseHandler"
destroy-on-close
width="80vw" width="80vw"
@close="dialogCloseHandler"> title="执行日志">
<ExecuteLog :schedule-id="scheduleId"/> <ExecuteLog :schedule-id="scheduleId"/>
<template #footer> <template #footer>
<ElButton @click="showDialog = false">关闭</ElButton> <ElButton @click="showDialog = false">关闭</ElButton>
</template> </template>
</ElDialog> </ADialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import ExecuteLog from '@/pages/sys/task/execute-log/ExecuteLog.vue' import ExecuteLog from '@/pages/sys/task/execute-log/ExecuteLog.vue'
import ADialog from '@/components/a-dialog/ADialog.vue'
const showDialog = ref(false) const showDialog = ref(false)
const scheduleId = ref<string>('') const scheduleId = ref<string>('')

View File

@ -32,9 +32,9 @@
<ElButton :icon="elIcons.Refresh" :loading="searching" @click="reset"></ElButton> <ElButton :icon="elIcons.Refresh" :loading="searching" @click="reset"></ElButton>
</ElFormItem> </ElFormItem>
</ElForm> </ElForm>
<div class="tool-bar"> <!-- <div class="tool-bar">
<ElButton :icon="elIcons.Filter" type="default" @click="showSearchForm = !showSearchForm"/> <ElButton :icon="elIcons.Filter" type="default" @click="showSearchForm = !showSearchForm"/>
</div> </div> -->
<ElTable v-loading="searching" :data="tableData" <ElTable v-loading="searching" :data="tableData"
cell-class-name="table-cell" cell-class-name="table-cell"
class="table-list" class="table-list"

View File

@ -1,16 +1,16 @@
<template> <template>
<ElDialog v-model="showDialog" <ADialog v-model:show="showDialog"
:close-on-click-modal="false" title="执行记录"
destroy-on-close
width="80vw"> width="80vw">
<ScheduleRecode :task-id="taskId"/> <ScheduleRecode :task-id="taskId"/>
<template #footer> <template #footer>
<ElButton @click="showDialog = false">关闭</ElButton> <ElButton @click="showDialog = false">关闭</ElButton>
</template> </template>
</ElDialog> </ADialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import ScheduleRecode from '@/pages/sys/task/schedule-recode/ScheduleRecode.vue' import ScheduleRecode from '@/pages/sys/task/schedule-recode/ScheduleRecode.vue'
import ADialog from '@/components/a-dialog/ADialog.vue'
const showDialog = ref(false) const showDialog = ref(false)

View File

@ -1,9 +1,7 @@
<template> <template>
<ElDialog v-model="showDialog" <ADialog v-model:show="showDialog"
:close-on-click-modal="false" :closed="dialogCloseHandler"
@close="dialogCloseHandler" title="分配角色" width="700px">
destroy-on-close
width="700">
<ElTransfer <ElTransfer
v-model="rightValue" v-model="rightValue"
:button-texts="['解绑', '绑定']" :button-texts="['解绑', '绑定']"
@ -36,7 +34,7 @@
<ElButton @click="showDialog = false">取消</ElButton> <ElButton @click="showDialog = false">取消</ElButton>
<ElButton :disabled="rightValue.length <= 0" :loading="submiting" type="primary" @click="submitHandler"></ElButton> <ElButton :disabled="rightValue.length <= 0" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</template> </template>
</ElDialog> </ADialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import RoleApi from '@/pages/sys/role/role-api.ts' import RoleApi from '@/pages/sys/role/role-api.ts'
@ -46,6 +44,7 @@ import {
type TransferDataItem, type TransferDataItem,
} from 'element-plus' } from 'element-plus'
import UserApi from '@/pages/sys/user/user-api.ts' import UserApi from '@/pages/sys/user/user-api.ts'
import ADialog from '@/components/a-dialog/ADialog.vue'
const showDialog = ref(false) const showDialog = ref(false)
const submiting = ref(false) const submiting = ref(false)
@ -60,6 +59,7 @@ function dialogCloseHandler() {
allRoles.value = [] allRoles.value = []
rightValue.value = [] rightValue.value = []
} }
function filterMethod(query: string, item: TransferDataItem) { function filterMethod(query: string, item: TransferDataItem) {
return Strings.isBlank(query) || (item as RoleTypes.SearchRoleResult).roleCode!.includes(query) || (item as RoleTypes.SearchRoleResult).roleName!.includes(query) return Strings.isBlank(query) || (item as RoleTypes.SearchRoleResult).roleCode!.includes(query) || (item as RoleTypes.SearchRoleResult).roleName!.includes(query)
} }

View File

@ -1,23 +1,20 @@
<template> <template>
<FormPage <ATablePage
ref="formPage" ref="tablePage"
:action-column="actionColumn" v-bind="tablePageProps">
:left-tools="leftTools" <template #simpleFormItem="formData">
:paging="paging"> <ElFormItem>
<template #searchFormItem="{searchForm}">
<ElFormItem label="姓名">
<ElInput <ElInput
v-model="searchForm.nickname" v-model="formData.nickname"
placeholder="姓名"/> placeholder="姓名"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="手机号"> <ElFormItem>
<ElInput <ElInput
v-model="searchForm.phone" v-model="formData.phone"
placeholder="手机号"/> placeholder="手机号"/>
</ElFormItem> </ElFormItem>
</template> </template>
<template #columns> <template #columns>
<ElTableColumn label="姓名" prop="nickname"/>
<ElTableColumn label="头像" prop="avatar" width="60"> <ElTableColumn label="头像" prop="avatar" width="60">
<template #default="{row}"> <template #default="{row}">
<ElImage :preview-src-list="Strings.isBlank(row.avatar)?[]:[AppApi.fileUrl(row.avatar)]" :src="Strings.isBlank(row.avatar)?Avatar:AppApi.fileUrl(row.avatar)" class="avatar" preview-teleported> <ElImage :preview-src-list="Strings.isBlank(row.avatar)?[]:[AppApi.fileUrl(row.avatar)]" :src="Strings.isBlank(row.avatar)?Avatar:AppApi.fileUrl(row.avatar)" class="avatar" preview-teleported>
@ -29,27 +26,29 @@
</ElImage> </ElImage>
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="姓名" prop="nickname"/>
<ElTableColumn label="联系电话" prop="phone"/> <ElTableColumn label="联系电话" prop="phone"/>
<ElTableColumn label="登录手机号" prop="account.phone"/> <!-- <ElTableColumn label="登录手机号" prop="account.phone"/> -->
<ElTableColumn label="用户名" prop="account.username"/> <ElTableColumn label="用户名" prop="account.username"/>
<ElTableColumn label="微信标识" prop="account.wechatOpenid"/> <ElTableColumn label="微信标识" prop="account.wechatOpenid" width="180"/>
<ElTableColumn label="已授权客户端" prop="account.clientCode" width="110"> <ElTableColumn label="已授权客户端" prop="account.clientCode" width="130">
<template #default="{row}"> <template #default="{row}">
<ElCheckboxGroup v-model="row.account.clients" @change="clientChangeHandler($event,row)"> <ElCheckboxGroup v-model="row.account.clients" @change="clientChangeHandler($event,row)">
<ElCheckbox v-for="client in ClientUtil.clients" :key="client.val" :disabled="row.id == '1' && client.val === 1" :label="client.txt" :value="client.val"/> <ElCheckbox v-for="client in ClientUtil.clients" :key="client.val" :disabled="row.id == '1' && client.val === 1" :label="client.txt" :value="client.val"/>
</ElCheckboxGroup> </ElCheckboxGroup>
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="是否禁用" prop="account.disabled"> <ElTableColumn label="是否禁用" prop="account.disabled" width="100">
<template #default="{row}"> <template #default="{row}">
<ElSwitch v-model="row.account.disabled" :disabled="row.id == '1'" @change="disabledUserHandler($event,row.id)"/> <ElSwitch v-model="row.account.disabled" :disabled="row.id == '1'" @change="disabledUserHandler($event,row.id)"/>
</template> </template>
</ElTableColumn> </ElTableColumn>
<ElTableColumn label="注册时间" prop="account.regdate" width="170"/> <ElTableColumn label="注册时间" prop="account.regdate" width="180"/>
</template> </template>
<UserForm ref="userForm" @edit-succ="paging"/> <UserForm ref="userForm" :research="research"/>
<BindRole ref="bindRole"/> <BindRole ref="bindRole"/>
</FormPage> </ATablePage>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import UserApi from '@/pages/sys/user/user-api.ts' import UserApi from '@/pages/sys/user/user-api.ts'
@ -63,19 +62,91 @@ import BindRole from '@/pages/sys/user/BindRole.vue'
import ClientUtil from '@/common/utils/client-util.ts' import ClientUtil from '@/common/utils/client-util.ts'
import Strings from '@/common/utils/strings.ts' import Strings from '@/common/utils/strings.ts'
import Avatar from '@/assets/images/avatar.png' import Avatar from '@/assets/images/avatar.png'
import FormPage from '@/components/page/FormPage.vue' import ATablePage, {
import type { type ATablePageInstance,
ActionColumnType, buildTablePageProps,
ToolType, } from '@/components/a-page/a-table-page/ATablePage.tsx'
} from '@/components/page/a-page-type.ts'
import type { ComponentExposed } from 'vue-component-type-helpers'
const formPageIns = useTemplateRef<ComponentExposed<typeof FormPage>>('formPage')
const userFormIns = useTemplateRef<InstanceType<typeof UserForm>>('userForm') const userFormIns = useTemplateRef<InstanceType<typeof UserForm>>('userForm')
const bindRoleIns = useTemplateRef<InstanceType<typeof BindRole>>('bindRole') const bindRoleIns = useTemplateRef<InstanceType<typeof BindRole>>('bindRole')
const tablePageIns = useTemplateRef<ATablePageInstance>('tablePage')
const tablePageProps = buildTablePageProps<UserTypes.SearchUserParam, UserTypes.SearchUserResult>({
pageLayout: {
enableHighForm: false,
},
searchForm: {
simpleForm: {
colCount: 2,
},
paging(param) {
return UserApi.paging(param)
.then(res => {
(res.data?.records ?? []).map(it => {
it.account.clients = ClientUtil.getClients(it.account.clientCode!).map(it => it.val)
return it
})
return res
})
},
},
toolBar: {
leftTools: [
{
icon: 'Plus',
label: '新建',
action() {
userFormIns.value?.open()
},
},
],
},
table: {
actionColumn: {
tableActions: [
{
tooltip: '编辑',
icon: 'Edit',
type: 'success',
action({row}) {
userFormIns.value?.open(row)
},
},
{
tooltip: '权限',
icon: 'gongnengquanxian',
type: 'warning',
action({row}) {
bindRoleIns.value?.open(row.id!)
},
},
{
tooltip: '重置密码',
icon: 'zhongzhimima',
type: 'danger',
confirm: {
title: '确定重置密码?',
},
show({row}) {
return row.id != '1'
},
action({row}) {
if (row.id == '1') {
ElMessage.error('不能修改管理员')
return
}
UserApi.resetPasswd(row.id!)
.then((res) => {
ElMessage.success(res.msg)
})
},
},
],
},
},
})
function research() { function research() {
formPageIns.value?.doSearch() tablePageIns.value?.doSearch()
} }
function clientChangeHandler(clients: CheckboxValueType[], data: UserTypes.SearchUserResult) { function clientChangeHandler(clients: CheckboxValueType[], data: UserTypes.SearchUserResult) {
@ -98,62 +169,6 @@ function disabledUserHandler(val: string | number | boolean, id: string) {
}) })
} }
const actionColumn = reactive<ActionColumnType<UserTypes.SearchUserResult>>({
tableActions: [
{
tooltip: '编辑',
icon: 'Edit',
type: 'success',
action({row}) {
userFormIns.value?.open(row)
},
},
{
tooltip: '权限',
icon: 'Edit',
action({row}) {
bindRoleIns.value?.open(row.id!)
},
},
{
tooltip: '重置密码',
icon: 'Edit',
show({row}) {
return row.id != '1'
},
action({row}) {
if (row.id == '1') {
ElMessage.error('不能修改管理员')
return
}
UserApi.resetPasswd(row.id!)
.then((res) => {
ElMessage.success(res.msg)
})
},
},
],
})
const leftTools: ToolType[] = [
{
icon: 'Plus',
label: '新建',
action() {
userFormIns.value?.open()
},
},
]
function paging(param: UserTypes.SearchUserParam) {
return UserApi.paging(param)
.then(res => {
(res.data?.records ?? []).map(it => {
it.account.clients = ClientUtil.getClients(it.account.clientCode!).map(it => it.val)
return it
})
return res
})
}
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.avatar { .avatar {

View File

@ -1,139 +1,130 @@
<template> <template>
<ElDialog v-model="showDialog" <AFormPanel
:close-on-click-modal="false" ref="formPanel"
@close="dialogCloseHandler" v-bind="formPanelProps">
destroy-on-close <template #default="formData">
width="25vw"> <div class="form-items">
<ElForm :model="userFormData"
class="sys_user-form"
>
<ElFormItem label="姓名">
<ElInput
:readonly="userFormData.id=='1'"
v-model="userFormData.nickname"
placeholder="姓名"/>
</ElFormItem>
<ElFormItem label="头像"> <ElFormItem label="头像">
<Uploader <Uploader
v-model:file="userFormData.avatar" ref="uploader"
v-model:file="formData.avatar"/>
:upload-props="{ </ElFormItem>
accept: 'image/*', <ElFormItem label="姓名">
listType:'picture' <ElInput
}"> v-model="formData.nickname"
<ElButton>点击上传头像</ElButton> :readonly="formData.id=='1'"
</Uploader> placeholder="姓名"/>
</ElFormItem> </ElFormItem>
<ElFormItem label="联系电话"> <ElFormItem label="联系电话">
<ElInput <ElInput
v-model="userFormData.phone" v-model="formData.phone"
placeholder="联系电话"/> placeholder="联系电话"/>
</ElFormItem> </ElFormItem>
<ElFormItem v-if="status === 'add'" label="用户名"> <ElFormItem v-if="status === 'add'" label="用户名">
<ElInput <ElInput v-model="formData.account.username" placeholder="用户名"/>
v-model="userFormData.account.username"
placeholder="用户名"/>
</ElFormItem> </ElFormItem>
<ElFormItem v-if="status === 'add'" label="密码"> <ElFormItem v-if="status === 'add'" label="密码">
<ElInput <ElInput
v-model="userFormData.account.secret" v-model="formData.account.secret"
autocomplete="new-password" autocomplete="new-password"
placeholder="密码" placeholder="密码"
show-password show-password
type="password"/> type="password"/>
</ElFormItem> </ElFormItem>
</ElForm> </div>
<template #footer>
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</template> </template>
</ElDialog> </AFormPanel>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import UserApi from '@/pages/sys/user/user-api.ts' import UserApi from '@/pages/sys/user/user-api.ts'
import Strings from '@/common/utils/strings.ts' import Strings from '@/common/utils/strings.ts'
import { ElMessage } from 'element-plus'
import Uploader from '@/components/uploader/Uploader.vue' import Uploader from '@/components/uploader/Uploader.vue'
import AFormPanel, {
type AFormPanelInstance,
buildFormPanelProps,
} from '@/components/a-form-panel/AFormPanel.tsx'
const emits = defineEmits([ 'editSucc' ]) const props = withDefaults(defineProps<{
const showDialog = ref(false) research?: () => void
const submiting = ref(false) }>(), {
const status = ref<'add' | 'view' | 'modify'>('add') research: () => {
const userFormData = ref<UserTypes.SearchUserResult>({ },
})
const formPanelIns = useTemplateRef<AFormPanelInstance>('formPanel')
const uploaderIns = useTemplateRef<InstanceType<typeof Uploader>>('uploader')
const status = ref<'add' | 'modify'>('add')
const formPanelProps = buildFormPanelProps<UserTypes.SearchUserResult>({
detailsLoader(id?: string) {
if (Strings.isBlank(id)) {
status.value = 'add'
return Promise.resolve({
account: { account: {
username: '', username: '',
secret: '', secret: '',
}, },
}) } as UserTypes.SearchUserResult)
function dialogCloseHandler() {
userFormData.value = {
account: {
username: '',
secret: '',
},
}
}
function submitHandler() {
if (status.value === 'view') return
submiting.value = true
if (userFormData.value.id != null) {
const {id, nickname, avatar, email, phone} = userFormData.value
UserApi.modify({id, nickname, avatar, email, phone})
.then(() => {
ElMessage.success('修改成功')
emits('editSucc')
showDialog.value = false
})
.finally(() => {
submiting.value = false
})
} else { } else {
const {nickname, avatar, email, phone, account} = userFormData.value
UserApi.add({nickname, avatar, email, phone, account})
.then(() => {
ElMessage.success('添加成功')
emits('editSucc')
showDialog.value = false
})
.finally(() => {
submiting.value = false
})
}
}
defineExpose({
open(data?: UserTypes.SearchUserResult) {
showDialog.value = true
if (data != null && !Strings.isBlank(data.id)) {
status.value = 'modify' status.value = 'modify'
UserApi.detail(data.id!) return UserApi.detail(id!)
.then(res => { .then(res => {
userFormData.value = { if (res.data.avatar != null) uploaderIns.value?.setDefaultFiles([ res.data.avatar ])
return {
...res.data, account: {}, ...res.data, account: {},
} }
}) })
} else { }
status.value = 'add' },
userFormData.value = { defaultFormData: {
account: { account: {
username: '', username: '',
secret: '', secret: '',
}, },
},
doSubmit(data) {
if (status.value === 'add') {
const {nickname, avatar, email, phone, account} = data
return UserApi.add({nickname, avatar, email, phone, account})
.then(props.research)
} else {
const {id, nickname, avatar, email, phone} = data
return UserApi.modify({id, nickname, avatar, email, phone})
.then(props.research)
} }
} },
})
watchEffect(() => {
formPanelProps.title = status.value === 'add' ? '新建用户' : '修改用户'
})
defineExpose({
open(data?: UserTypes.SearchUserResult) {
formPanelIns.value?.open(data?.id)
}, },
}) })
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.sys_user-form { .form-items {
padding 20px grid-template-columns: 1fr 1fr;
grid-template-areas: "picture ." \
"picture .";
:deep(.el-form-item) {
&:first-child {
align-items: start;
grid-area: picture
max-width 300px
.el-form-item__content {
justify-content center
} }
.avatar-uploader { .el-form-item__error {
width 100% width: 100%;
text-align: center;
}
}
}
} }
</style> </style>