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

View File

@ -5,6 +5,62 @@
"css_prefix_text": "icon-",
"description": "",
"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",
"name": "重置密码",

View File

@ -5,6 +5,62 @@ export const icons = {
'css_prefix_text': 'icon-',
'description': '',
'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',
'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
*/
paging: (param: P) => Promise<R<G.PageResult<T>>>
list?: (param: P) => Promise<R<T[]>>
/**
*
* @param param
@ -329,9 +330,19 @@ const component = defineComponent(
loading.value = false
})
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 = () => {
showHighForm.value = !showHighForm.value
formData.$reset()
@ -634,7 +645,7 @@ const component = defineComponent(
}
</ElTable>, [ [ ElLoading.directive, loading.value ] ])
}
<ElPagination
{props.searchForm.paging != null ? <ElPagination
current-page={(formData as G.PageParam).current}
page-size={(formData as G.PageParam).size}
onUpdate:current-page={(val) => (formData as G.PageParam).current = val}
@ -645,7 +656,7 @@ const component = defineComponent(
total={totalCount.value}
background={true}
layout="total, prev, pager, next, sizes, jumper"
onChange={doSearch}/>
onChange={doSearch}/> : <></>}
{
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
tableData: T[]
}
export default component

View File

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

View File

@ -19,7 +19,7 @@
<ElInput v-model="formData.categoryName" placeholder="分类名称"/>
</ElFormItem>
<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 label="备注" prop="memo">
<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 status = ref<'add' | 'modify'>('add')
const formPanelProps = buildFormPanelProps<GoodsTypes.GoodsForm>({
// title: status.value === 'add' ? '' : '',
detailsLoader(id?: string) {
if (Strings.isBlank(id)) {
status.value = 'add'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,80 +1,48 @@
<template>
<ElDialog v-model="showDialog"
:close-on-click-modal="false"
destroy-on-close width="25vw" @close="dialogCloseHandler">
<ElForm :model="menuForm" class="menu-form">
<AFormPanel
ref="formPanel"
v-bind="formPanelProps">
<template #default="formData">
<div class="form-items">
<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 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"/>
</ElCheckboxGroup>
</ElFormItem>
<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"/>
</ElSelect>
</ElFormItem>
<ElFormItem label="名称">
<ElInput v-model="menuForm.title" placeholder="名称"/>
<ElInput v-model="formData.title" placeholder="名称"/>
</ElFormItem>
<ElFormItem v-if="menuForm.menuCategory === MenuCategory.Page || menuForm.menuCategory === MenuCategory.SubPage" label="路由名称">
<ElInput v-model="menuForm.routeName" :min="0" placeholder="路由名称"/>
<ElFormItem v-if="formData.menuCategory === MenuCategory.Page || formData.menuCategory === MenuCategory.SubPage" label="路由名称">
<ElInput v-model="formData.routeName" :min="0" placeholder="路由名称"/>
</ElFormItem>
<ElFormItem v-if="menuForm.menuCategory === MenuCategory.Page || menuForm.menuCategory === MenuCategory.SubPage" label="路由地址">
<ElInput v-model="menuForm.routePath" :min="0" placeholder="路由地址"/>
<ElFormItem v-if="formData.menuCategory === MenuCategory.Page || formData.menuCategory === MenuCategory.SubPage" label="路由地址">
<ElInput v-model="formData.routePath" :min="0" placeholder="路由地址"/>
</ElFormItem>
<ElFormItem label="编码">
<ElInput v-model="menuForm.sn" :min="0" placeholder="编码"/>
<ElInput v-model="formData.sn" :min="0" placeholder="编码"/>
</ElFormItem>
<ElFormItem label="图标">
<ElDropdown closable header="图标列表" placement="bottom" style="width: 100%" trigger="click">
<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>
<AIconDropTable v-model="formData.icon"/>
</ElFormItem>
<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>
</ElForm>
<template #footer>
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</div>
</template>
</ElDialog>
</AFormPanel>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import {
MenuCategory,
MenuCategoryDict,
@ -82,62 +50,23 @@ import {
import MenuApi from '@/pages/sys/menus/menu-api.ts'
import Colls from '@/common/utils/colls.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 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 pagination = reactive<G.Pagination>({
total: icons.glyphs.length,
pages: Math.ceil(icons.glyphs.length / 5),
current: 1,
size: 5,
const props = withDefaults(defineProps<{
research?: () => void
}>(), {
research: () => {
},
})
function dialogCloseHandler() {
menuForm.value = {
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 status = ref<'add' | 'modify'>('add')
const loadingMenus = ref<boolean>(false)
const formPanelIns = useTemplateRef<AFormPanelInstance>('formPanel')
const menuCategoryData = computed(() => {
const data: { key: string; label: string }[] = []
@ -151,66 +80,61 @@ const menuCategoryData = computed(() => {
})
const menuTreeDataSource = ref<MenuTypes.SysMenu[]>()
function submitHandler() {
if (status.value === 'view') return
submiting.value = true
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 = {
const formPanelProps = buildFormPanelProps<MenuTypes.MenuForm>({
detailsLoader(id?: string) {
if (Strings.isBlank(id)) {
status.value = 'add'
return Promise.resolve({
icon: 'dianzixiaopiao',
}) {
if (!Strings.isBlank(data.id)) {
pid: '0',
clients: [ 1 ],
menuCategory: MenuCategory.Catalog,
sn: nanoid(10),
} as MenuTypes.MenuForm)
} else {
status.value = 'modify'
data = Utils.clone(data)
data.clients = ClientUtil.getClients(data.clientCode!).map(it => it.val)
menuForm.value = data
return MenuApi
.detail(id!)
.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 {
menuForm.value = {
icon: 'dianzixiaopiao',
return MenuApi.modify(data)
.then(props.research)
}
}
showDialog.value = true
dropdownTableIns.value?.setCurrentRow(icons.glyphs.find((it) => it.font_class === data.icon))
},
})
onMounted(() => {
MenuApi.list().then(({data}) => {
watchEffect(() => {
formPanelProps.title = status.value === 'add' ? '新建菜单' : '修改菜单'
})
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})))
menuTreeDataSource.value = [ {value: '0', label: '顶级菜单', id: '0', pid: '0', children: nodes} ]
})
.finally(() => {
loadingMenus.value = false
})
formPanelIns.value?.open(data?.id)
},
})
</script>
<style lang="stylus" scoped>
.menu-form {
padding 20px
.form-items {
grid-template-columns: 1fr 1fr;
}
.dropdown-table-wrapper {

View File

@ -1,28 +1,13 @@
<template>
<FormPage
ref="formPage"
:action-column="actionColumn"
:left-tools="leftTools"
: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 }">
<ATablePage
ref="tablePage"
v-bind="tablePageProps">
<template #simpleFormItem="formData">
<ElFormItem>
<ElInput v-model="searchForm.title" placeholder="菜单名称"/>
<ElInput v-model="formData.title" placeholder="菜单名称"/>
</ElFormItem>
<ElFormItem>
<ElInput v-model="searchForm.routeName" placeholder="路由名称"/>
<ElInput v-model="formData.routeName" placeholder="路由名称"/>
</ElFormItem>
</template>
<template #columns>
@ -48,12 +33,9 @@
</span>
</template>
</ElTableColumn>
</template>
<MenuForm ref="menuForm" @editSucc="research"/>
</FormPage>
<MenuForm ref="menuForm" :research="research"/>
</ATablePage>
</template>
<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 Strings from '@/common/utils/strings.ts'
import AIcon from '@/components/a-icon/AIcon.vue'
import FormPage from '@/components/page/FormPage.vue'
import type {
ActionColumnType,
ToolType,
} from '@/components/page/a-page-type.ts'
import type { ComponentExposed } from 'vue-component-type-helpers'
import ATablePage, {
type ATablePageInstance,
buildTablePageProps,
} from '@/components/a-page/a-table-page/ATablePage.tsx'
import Colls from '@/common/utils/colls.ts'
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: [
{
tooltip: '编辑',
@ -98,52 +113,24 @@ const actionColumn = reactive<ActionColumnType<MenuTypes.SysMenu>>({
},
},
],
},
},
})
const leftTools: ToolType[] = [
{
icon: 'Plus',
label: '新建',
action() {
menuFormIns.value?.open()
},
},
]
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>
<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)
},
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) {
return get<MenuTypes.SysMenu[]>('/menu/list_all', data)

View File

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

View File

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

View File

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

View File

@ -1,91 +1,77 @@
<template>
<ElDialog v-model="showDialog"
:close-on-click-modal="false"
@close="dialogCloseHandler"
destroy-on-close
width="25vw">
<ElForm :model="roleFormData"
class="sys_role-form"
>
<AFormPanel
ref="formPanel"
v-bind="formPanelProps">
<template #default="formData">
<div class="form-items">
<ElFormItem label="角色代码">
<ElInput
v-model="roleFormData.roleCode"
v-model="formData.roleCode"
placeholder="角色代码"/>
</ElFormItem>
<ElFormItem label="角色名称">
<ElInput
v-model="roleFormData.roleName"
v-model="formData.roleName"
placeholder="角色名称"/>
</ElFormItem>
<ElFormItem label="备注">
<ElInput
v-model="roleFormData.memo"
v-model="formData.memo"
placeholder="备注"/>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</div>
</template>
</ElDialog>
</AFormPanel>
</template>
<script lang="ts" setup>
import RoleApi from '@/pages/sys/role/role-api.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 showDialog = ref(false)
const submiting = ref(false)
const status = ref<'add' | 'view' | 'modify'>('add')
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 props = withDefaults(defineProps<{
research?: () => void
}>(), {
research: () => {
},
})
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 {
RoleApi.add(roleFormData.value)
.then(() => {
ElMessage.success('添加成功')
emits('editSucc')
showDialog.value = false
})
.finally(() => {
submiting.value = false
status.value = 'modify'
return RoleApi.detail(id!)
.then(res => {
return {
...res.data,
}
})
}
},
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({
open(data: RoleTypes.SearchRoleResult = {}) {
showDialog.value = true
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
}
open(data?: RoleTypes.SearchRoleResult) {
formPanelIns.value?.open(data?.id)
},
})
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,7 @@
<template>
<ElDialog v-model="showDialog"
:close-on-click-modal="false"
@close="dialogCloseHandler"
destroy-on-close
width="700">
<ADialog v-model:show="showDialog"
:closed="dialogCloseHandler"
title="分配角色" width="700px">
<ElTransfer
v-model="rightValue"
:button-texts="['解绑', '绑定']"
@ -36,7 +34,7 @@
<ElButton @click="showDialog = false">取消</ElButton>
<ElButton :disabled="rightValue.length <= 0" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</template>
</ElDialog>
</ADialog>
</template>
<script lang="ts" setup>
import RoleApi from '@/pages/sys/role/role-api.ts'
@ -46,6 +44,7 @@ import {
type TransferDataItem,
} from 'element-plus'
import UserApi from '@/pages/sys/user/user-api.ts'
import ADialog from '@/components/a-dialog/ADialog.vue'
const showDialog = ref(false)
const submiting = ref(false)
@ -60,6 +59,7 @@ function dialogCloseHandler() {
allRoles.value = []
rightValue.value = []
}
function filterMethod(query: string, item: TransferDataItem) {
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>
<FormPage
ref="formPage"
:action-column="actionColumn"
:left-tools="leftTools"
:paging="paging">
<template #searchFormItem="{searchForm}">
<ElFormItem label="姓名">
<ATablePage
ref="tablePage"
v-bind="tablePageProps">
<template #simpleFormItem="formData">
<ElFormItem>
<ElInput
v-model="searchForm.nickname"
v-model="formData.nickname"
placeholder="姓名"/>
</ElFormItem>
<ElFormItem label="手机号">
<ElFormItem>
<ElInput
v-model="searchForm.phone"
v-model="formData.phone"
placeholder="手机号"/>
</ElFormItem>
</template>
<template #columns>
<ElTableColumn label="姓名" prop="nickname"/>
<ElTableColumn label="头像" prop="avatar" width="60">
<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>
@ -29,27 +26,29 @@
</ElImage>
</template>
</ElTableColumn>
<ElTableColumn label="姓名" prop="nickname"/>
<ElTableColumn label="联系电话" prop="phone"/>
<ElTableColumn label="登录手机号" prop="account.phone"/>
<!-- <ElTableColumn label="登录手机号" prop="account.phone"/> -->
<ElTableColumn label="用户名" prop="account.username"/>
<ElTableColumn label="微信标识" prop="account.wechatOpenid"/>
<ElTableColumn label="已授权客户端" prop="account.clientCode" width="110">
<ElTableColumn label="微信标识" prop="account.wechatOpenid" width="180"/>
<ElTableColumn label="已授权客户端" prop="account.clientCode" width="130">
<template #default="{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"/>
</ElCheckboxGroup>
</template>
</ElTableColumn>
<ElTableColumn label="是否禁用" prop="account.disabled">
<ElTableColumn label="是否禁用" prop="account.disabled" width="100">
<template #default="{row}">
<ElSwitch v-model="row.account.disabled" :disabled="row.id == '1'" @change="disabledUserHandler($event,row.id)"/>
</template>
</ElTableColumn>
<ElTableColumn label="注册时间" prop="account.regdate" width="170"/>
<ElTableColumn label="注册时间" prop="account.regdate" width="180"/>
</template>
<UserForm ref="userForm" @edit-succ="paging"/>
<UserForm ref="userForm" :research="research"/>
<BindRole ref="bindRole"/>
</FormPage>
</ATablePage>
</template>
<script lang="ts" setup>
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 Strings from '@/common/utils/strings.ts'
import Avatar from '@/assets/images/avatar.png'
import FormPage from '@/components/page/FormPage.vue'
import type {
ActionColumnType,
ToolType,
} from '@/components/page/a-page-type.ts'
import type { ComponentExposed } from 'vue-component-type-helpers'
import ATablePage, {
type ATablePageInstance,
buildTablePageProps,
} from '@/components/a-page/a-table-page/ATablePage.tsx'
const formPageIns = useTemplateRef<ComponentExposed<typeof FormPage>>('formPage')
const userFormIns = useTemplateRef<InstanceType<typeof UserForm>>('userForm')
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() {
formPageIns.value?.doSearch()
tablePageIns.value?.doSearch()
}
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>
<style lang="stylus" scoped>
.avatar {

View File

@ -1,139 +1,130 @@
<template>
<ElDialog v-model="showDialog"
:close-on-click-modal="false"
@close="dialogCloseHandler"
destroy-on-close
width="25vw">
<ElForm :model="userFormData"
class="sys_user-form"
>
<ElFormItem label="姓名">
<ElInput
:readonly="userFormData.id=='1'"
v-model="userFormData.nickname"
placeholder="姓名"/>
</ElFormItem>
<AFormPanel
ref="formPanel"
v-bind="formPanelProps">
<template #default="formData">
<div class="form-items">
<ElFormItem label="头像">
<Uploader
v-model:file="userFormData.avatar"
:upload-props="{
accept: 'image/*',
listType:'picture'
}">
<ElButton>点击上传头像</ElButton>
</Uploader>
ref="uploader"
v-model:file="formData.avatar"/>
</ElFormItem>
<ElFormItem label="姓名">
<ElInput
v-model="formData.nickname"
:readonly="formData.id=='1'"
placeholder="姓名"/>
</ElFormItem>
<ElFormItem label="联系电话">
<ElInput
v-model="userFormData.phone"
v-model="formData.phone"
placeholder="联系电话"/>
</ElFormItem>
<ElFormItem v-if="status === 'add'" label="用户名">
<ElInput
v-model="userFormData.account.username"
placeholder="用户名"/>
<ElInput v-model="formData.account.username" placeholder="用户名"/>
</ElFormItem>
<ElFormItem v-if="status === 'add'" label="密码">
<ElInput
v-model="userFormData.account.secret"
v-model="formData.account.secret"
autocomplete="new-password"
placeholder="密码"
show-password
type="password"/>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="showDialog = false">{{ status === 'view' ? '关闭' : '取消' }}</ElButton>
<ElButton v-if="status !== 'view'" :loading="submiting" type="primary" @click="submitHandler"></ElButton>
</div>
</template>
</ElDialog>
</AFormPanel>
</template>
<script lang="ts" setup>
import UserApi from '@/pages/sys/user/user-api.ts'
import Strings from '@/common/utils/strings.ts'
import { ElMessage } from 'element-plus'
import Uploader from '@/components/uploader/Uploader.vue'
import AFormPanel, {
type AFormPanelInstance,
buildFormPanelProps,
} from '@/components/a-form-panel/AFormPanel.tsx'
const emits = defineEmits([ 'editSucc' ])
const showDialog = ref(false)
const submiting = ref(false)
const status = ref<'add' | 'view' | 'modify'>('add')
const userFormData = ref<UserTypes.SearchUserResult>({
const props = withDefaults(defineProps<{
research?: () => void
}>(), {
research: () => {
},
})
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: {
username: '',
secret: '',
},
})
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
})
} as UserTypes.SearchUserResult)
} 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'
UserApi.detail(data.id!)
return UserApi.detail(id!)
.then(res => {
userFormData.value = {
if (res.data.avatar != null) uploaderIns.value?.setDefaultFiles([ res.data.avatar ])
return {
...res.data, account: {},
}
})
} else {
status.value = 'add'
userFormData.value = {
}
},
defaultFormData: {
account: {
username: '',
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>
<style lang="stylus" scoped>
.sys_user-form {
padding 20px
.form-items {
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 {
width 100%
.el-form-item__error {
width: 100%;
text-align: center;
}
}
}
}
</style>