feat: add vue3(element-plus)

This commit is contained in:
xingyu
2022-07-18 19:06:37 +08:00
parent c6b58dca52
commit 80a3ae8d74
423 changed files with 41039 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
import { required } from '@/utils/formRules'
import { reactive } from 'vue'
export const modelSchema = reactive<FormSchema[]>([
{
label: '上级部门',
field: 'parentId',
component: 'Input'
},
{
label: '部门名称',
field: 'name',
component: 'Input',
formItemProps: {
rules: [required]
}
},
{
label: '负责人',
field: 'email',
component: 'Input'
},
{
label: '联系电话',
field: 'phone',
component: 'Input'
},
{
label: '邮箱',
field: 'email',
component: 'Input'
},
{
label: '显示排序',
field: 'sort',
component: 'Input'
},
{
label: '状态',
field: 'status',
component: 'RadioButton',
componentProps: {
options: [
{
label: '开启',
value: 0
},
{
label: '关闭',
value: 1
}
]
}
}
])

View File

@@ -0,0 +1,189 @@
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
import { ElCard, ElTree, ElTreeSelect, ElMessage, ElMessageBox } from 'element-plus'
import { handleTree } from '@/utils/tree'
import { onMounted, ref, unref } from 'vue'
import * as DeptApi from '@/api/system/dept'
import { Form, FormExpose } from '@/components/Form'
import { modelSchema } from './dept.data'
import { DeptVO } from '@/api/system/dept/types'
interface Tree {
id: number
name: string
children?: Tree[]
}
const defaultProps = {
children: 'children',
label: 'name',
value: 'id'
}
const { t } = useI18n() // 国际化
const loading = ref(false) // 遮罩层
const dialogVisible = ref(false) // 是否显示弹出层
const showForm = ref(false) // 显示form表单
const formTitle = ref('部门信息') // 显示form标题
const deptParentId = ref(0) // 上级ID
// 创建form表单
const formRef = ref<FormExpose>()
// ========== 创建部门树结构 ==========
const deptOptions = ref([]) // 树形结构
const treeRef = ref<InstanceType<typeof ElTree>>()
const getTree = async () => {
const res = await DeptApi.listSimpleDeptApi()
deptOptions.value = handleTree(res)
}
const filterNode = (value: string, data: Tree) => {
if (!value) return true
return data.name.includes(value)
}
// 新增
const handleAdd = () => {
// 重置表单
formTitle.value = '新增部门'
unref(formRef)?.getElFormRef()?.resetFields()
showForm.value = true
}
// 编辑
const handleUpdate = async (data: { id: number }) => {
showForm.value = true
const res = await DeptApi.getDeptApi(data.id)
formTitle.value = '修改- ' + res?.name
deptParentId.value = res.parentId
unref(formRef)?.setValues(res)
}
// 删除
const handleDelete = async (data: { id: number }) => {
ElMessageBox.confirm(t('common.delDataMessage'), t('common.confirmTitle'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(async () => {
await DeptApi.deleteDeptApi(data.id)
ElMessage.success(t('common.delSuccess'))
})
.catch(() => {})
await getTree()
}
// 提交按钮
const submitForm = async () => {
loading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as DeptVO
deptParentId.value = data.parentId
// TODO: 表单提交待完善
if (formTitle.value.startsWith('新增')) {
await DeptApi.createDeptApi(data)
} else if (formTitle.value.startsWith('修改')) {
await DeptApi.updateDeptApi(data)
}
// 操作成功,重新加载列表
dialogVisible.value = false
} finally {
loading.value = false
}
}
onMounted(async () => {
await getTree()
})
</script>
<template>
<div class="flex">
<el-card class="w-1/3 dept" :gutter="12" shadow="always">
<template #header>
<div class="card-header">
<span>部门列表</span>
<el-button type="primary" v-hasPermi="['system:dept:create']" @click="handleAdd">
新增根节点
</el-button>
</div>
</template>
<div class="custom-tree-container">
<!-- <p>部门列表</p> -->
<!-- 操作工具栏 -->
<el-tree
ref="treeRef"
node-key="id"
:data="deptOptions"
:props="defaultProps"
:highlight-current="true"
default-expand-all
:filter-method="filterNode"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<span>
<el-button link v-hasPermi="['system:dept:create']" @click="handleAdd()">
<Icon icon="ep:plus" class="mr-5px" />
</el-button>
<el-button link v-hasPermi="['system:dept:update']" @click="handleUpdate(data)">
<Icon icon="ep:edit" class="mr-5px" />
</el-button>
<el-button link v-hasPermi="['system:dept:delete']" @click="handleDelete(data)">
<Icon icon="ep:delete" class="mr-5px" />
</el-button>
</span>
</span>
</template>
</el-tree>
</div>
</el-card>
<el-card class="w-2/3 dept" style="margin-left: 10px" :gutter="12" shadow="hover">
<template #header>
<div class="card-header">
<span>{{ formTitle }}</span>
</div>
</template>
<div v-if="!showForm">
<span><p>请从左侧选择部门</p></span>
</div>
<div v-if="showForm">
<!-- 操作工具栏 -->
<Form :schema="modelSchema" ref="formRef">
<template #parentId>
<el-tree-select
node-key="id"
v-model="deptParentId"
:props="defaultProps"
:data="deptOptions"
check-strictly
/>
</template>
</Form>
<!-- 操作按钮 -->
<el-button
type="primary"
v-hasPermi="['system:dept:update']"
:loading="loading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button type="danger" @click="showForm = false">取消</el-button>
</div>
</el-card>
</div>
</template>
<style scoped>
.dept {
height: 600px;
max-height: 1800px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>

View File

@@ -0,0 +1,128 @@
import { reactive } from 'vue'
import { DICT_TYPE } from '@/utils/dict'
import { required } from '@/utils/formRules'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
// 国际化
const { t } = useI18n()
// 表单校验
export const dictDataRules = reactive({
dictType: [required],
label: [required],
value: [required]
})
// crudSchemas
export const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '字典类型',
field: 'dictType',
table: {
show: false
}
},
{
label: '数据标签',
field: 'label',
search: {
show: true
}
},
{
label: '数据键值',
field: 'value'
},
{
label: '颜色类型',
field: 'colorType',
form: {
component: 'Select',
componentProps: {
options: [
{
label: 'default',
value: ''
},
{
label: 'success',
value: 'success'
},
{
label: 'info',
value: 'info'
},
{
label: 'warning',
value: 'warning'
},
{
label: 'danger',
value: 'danger'
}
]
}
},
table: {
show: false
}
},
{
label: 'CSS Class',
field: 'cssClass',
table: {
show: false
}
},
{
label: '显示排序',
field: 'sort',
table: {
show: false
}
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS
},
{
label: t('form.remark'),
field: 'remark',
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
},
table: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '180px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,74 @@
import { reactive } from 'vue'
import { DICT_TYPE } from '@/utils/dict'
import { required } from '@/utils/formRules'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
// 国际化
const { t } = useI18n()
// 表单校验
export const dictTypeRules = reactive({
name: [required],
type: [required]
})
// 新增 + 修改
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '字典名称',
field: 'name',
search: {
show: true
}
},
{
label: '字典类型',
field: 'type',
search: {
show: true
}
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS
},
{
label: t('form.remark'),
field: 'remark',
table: {
show: false
},
form: {
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
}
},
{
field: 'action',
width: '180px',
label: t('table.action'),
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,298 @@
<script setup lang="ts">
import { ref, unref, onMounted } from 'vue'
import { DICT_TYPE } from '@/utils/dict'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import * as DictTypeSchemas from './dict.type'
import * as DictDataSchemas from './dict.data'
import { useTable } from '@/hooks/web/useTable'
import { ElCard, ElMessage } from 'element-plus'
import * as DictTypeApi from '@/api/system/dict/dict.type'
import * as DictDataApi from '@/api/system/dict/dict.data'
import { DictDataVO, DictTypeVO } from '@/api/system/dict/types'
const { t } = useI18n() // 国际化
// ========== 字典分类列表相关 ==========
const {
register: typeRegister,
tableObject: typeTableObject,
methods: typeMethods
} = useTable<PageResult<DictTypeVO>, DictTypeVO>({
getListApi: DictTypeApi.getDictTypePageApi,
delListApi: DictTypeApi.deleteDictTypeApi
})
const {
getList: getTypeList,
setSearchParams: setTypeSearchParams,
delList: delTypeList
} = typeMethods
// 字典分类修改操作
const handleTypeCreate = () => {
setDialogTile('typeCreate')
// 重置表单
unref(typeFormRef)?.getElFormRef()?.resetFields()
}
const handleTypeUpdate = async (row: DictTypeVO) => {
setDialogTile('typeUpdate')
// 设置数据
const res = await DictTypeApi.getDictTypeApi(row.id)
unref(typeFormRef)?.setValues(res)
}
// 字典分类删除操作
const handleTypeDelete = async (row: DictTypeVO) => {
await delTypeList(row.id, false)
}
// ========== 字典数据列表相关 ==========
const tableTypeSelect = ref(false)
const {
register: dataRegister,
tableObject: dataTableObject,
methods: dataMethods
} = useTable<PageResult<DictDataVO>, DictDataVO>({
getListApi: DictDataApi.getDictDataPageApi,
delListApi: DictDataApi.deleteDictDataApi
})
const {
getList: getDataList,
setSearchParams: setDataSearchParams,
delList: delDataList
} = dataMethods
// 字典数据修改操作
const handleDataCreate = () => {
setDialogTile('dataCreate')
// 重置表单
unref(dataFormRef)?.getElFormRef()?.resetFields()
}
const handleDataUpdate = async (row: DictDataVO) => {
setDialogTile('dataUpdate')
// 设置数据
const res = await DictDataApi.getDictDataApi(row.id)
unref(dataFormRef)?.setValues(res)
}
// 字典数据删除操作
const handleDataDelete = async (row: DictTypeVO) => {
await delDataList(row.id, false)
}
// 字典分类点击行事件
const parentType = ref('')
const onClickType = async (data: { [key: string]: any }) => {
tableTypeSelect.value = true
dataTableObject.paramsObj.params = {
dictType: data.type
}
getDataList()
parentType.value = data.type
}
// 弹出框
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const actionLoading = ref(false) // 遮罩层
const typeFormRef = ref<FormExpose>() // 分类表单 Ref
const dataFormRef = ref<FormExpose>() // 数据表单 Ref
const actionType = ref('') // 操作按钮的类型
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 提交按钮
const submitTypeForm = async () => {
actionLoading.value = true
// 提交请求
try {
const data = unref(typeFormRef)?.formModel as DictTypeVO
if (actionType.value === 'typeCreate') {
await DictTypeApi.createDictTypeApi(data)
ElMessage.success(t('common.createSuccess'))
} else if (actionType.value === 'typeUpdate') {
await DictTypeApi.updateDictTypeApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
await getTypeList()
dialogVisible.value = false
} finally {
actionLoading.value = false
}
}
const submitDataForm = async () => {
actionLoading.value = true
// 提交请求
try {
const data = unref(dataFormRef)?.formModel as DictDataVO
if (actionType.value === 'dataCreate') {
data.dictType = parentType.value
await DictDataApi.createDictDataApi(data)
ElMessage.success(t('common.createSuccess'))
} else if (actionType.value === 'dataUpdate') {
await DictDataApi.updateDictDataApi(data)
ElMessage.success(t('common.updateSuccess'))
}
await getDataList()
// 操作成功,重新加载列表
dialogVisible.value = false
} finally {
actionLoading.value = false
}
}
// 初始化查询
onMounted(async () => {
await getTypeList()
typeTableObject.tableList[0] && onClickType(typeTableObject.tableList[0])
})
</script>
<template>
<div class="flex">
<el-card class="w-1/2 dict" :gutter="12" shadow="always">
<template #header>
<div class="card-header">
<span>字典分类</span>
</div>
</template>
<Search
:schema="DictTypeSchemas.allSchemas.searchSchema"
@search="setTypeSearchParams"
@reset="setTypeSearchParams"
/>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:dict:create']" @click="handleTypeCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<!-- 列表 -->
<Table
@row-click="onClickType"
:columns="DictTypeSchemas.allSchemas.tableColumns"
:selection="false"
:data="typeTableObject.tableList"
:loading="typeTableObject.loading"
:pagination="{
total: typeTableObject.total
}"
:highlight-current-row="true"
v-model:pageSize="typeTableObject.pageSize"
v-model:currentPage="typeTableObject.currentPage"
@register="typeRegister"
>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:dict:update']"
@click="handleTypeUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:dict:delete']"
@click="handleTypeDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</el-card>
<el-card class="w-1/2 dict" style="margin-left: 10px" :gutter="12" shadow="hover">
<template #header>
<div class="card-header">
<span>字典数据</span>
</div>
</template>
<!-- 列表 -->
<div v-if="!tableTypeSelect">
<span>请从左侧选择</span>
</div>
<div v-if="tableTypeSelect">
<Search
:schema="DictDataSchemas.allSchemas.searchSchema"
@search="setDataSearchParams"
@reset="setDataSearchParams"
/>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:dict:create']" @click="handleDataCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<Table
:columns="DictDataSchemas.allSchemas.tableColumns"
:selection="false"
:data="dataTableObject.tableList"
:loading="dataTableObject.loading"
:pagination="{
total: dataTableObject.total
}"
v-model:pageSize="dataTableObject.pageSize"
v-model:currentPage="dataTableObject.currentPage"
@register="dataRegister"
>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:dict:update']"
@click="handleDataUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:dict:delete']"
@click="handleDataDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</div>
</el-card>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<Form
v-if="['typeCreate', 'typeUpdate'].includes(actionType)"
:schema="DictTypeSchemas.allSchemas.formSchema"
:rules="DictTypeSchemas.dictTypeRules"
ref="typeFormRef"
/>
<Form
v-if="['dataCreate', 'dataUpdate'].includes(actionType)"
:schema="DictDataSchemas.allSchemas.formSchema"
:rules="DictDataSchemas.dictDataRules"
ref="dataFormRef"
/>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['typeCreate', 'typeUpdate'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitTypeForm"
>
{{ t('action.save') }}
</el-button>
<el-button
v-if="['dataCreate', 'dataUpdate'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitDataForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</div>
</template>

View File

@@ -0,0 +1,94 @@
import { reactive } from 'vue'
import { required } from '@/utils/formRules'
import { useI18n } from '@/hooks/web/useI18n'
import { DICT_TYPE } from '@/utils/dict'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
// 国际化
const { t } = useI18n()
// 表单校验
export const rules = reactive({
applicationName: [required],
code: [required],
message: [required]
})
// 新增 + 修改
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '错误码类型',
field: 'type',
component: 'InputNumber',
dictType: DICT_TYPE.SYSTEM_ERROR_CODE_TYPE,
search: {
show: true
}
},
{
label: '应用名',
field: 'applicationName',
search: {
show: true
}
},
{
label: '错误码编码',
field: 'code',
search: {
show: true
}
},
{
label: '错误码错误提示',
field: 'message'
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('common.createTime'),
field: 'daterange',
table: {
show: false
},
form: {
show: false
},
detail: {
show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'daterange',
valueFormat: 'YYYY-MM-DD'
}
}
},
{
field: 'action',
width: '240px',
label: t('table.action'),
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,184 @@
<script setup lang="ts">
import { ref, unref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { ErrorCodeVO } from '@/api/system/errorCode/types'
import { rules, allSchemas } from './errorCode.data'
import * as ErrorCodeApi from '@/api/system/errorCode'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<ErrorCodeVO>, ErrorCodeVO>({
getListApi: ErrorCodeApi.getErrorCodePageApi,
delListApi: ErrorCodeApi.deleteErrorCodeApi
})
const { getList, setSearchParams, delList } = methods
// ========== CRUD 相关 ==========
const loading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: ErrorCodeVO) => {
setDialogTile('update')
// 设置数据
const res = await ErrorCodeApi.getErrorCodeApi(row.id)
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
loading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as ErrorCodeVO
if (actionType.value === 'create') {
await ErrorCodeApi.createErrorCodeApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await ErrorCodeApi.updateErrorCodeApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
loading.value = false
}
}
// 删除操作
const handleDelete = (row: ErrorCodeVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: ErrorCodeVO) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button v-hasPermi="['system:error-code:create']" type="primary" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_ERROR_CODE_TYPE" :value="row.type" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:error-code:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:error-code:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:error-code:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_ERROR_CODE_TYPE" :value="row.type" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="loading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,80 @@
<script setup lang="ts">
import { ref } from 'vue'
import dayjs from 'dayjs'
import { useTable } from '@/hooks/web/useTable'
import { allSchemas } from './loginLog.data'
import { DICT_TYPE } from '@/utils/dict'
import type { LoginLogVO } from '@/api/system/loginLog/types'
import { getLoginLogPageApi, exportLoginLogApi } from '@/api/system/loginLog'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<LoginLogVO>, LoginLogVO>({
getListApi: getLoginLogPageApi,
exportListApi: exportLoginLogApi
})
const { getList, setSearchParams } = methods
// 详情操作
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref(t('action.detail')) // 弹出层标题
const detailRef = ref() // 详情 Ref
const handleDetail = async (row: LoginLogVO) => {
// 设置数据
detailRef.value = row
dialogVisible.value = true
}
getList()
</script>
<template>
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #logType="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="row.logType" />
</template>
<template #result="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="row.result" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button link type="primary" @click="handleDetail(row)">
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle" maxHeight="500px" width="50%">
<!-- 对话框(详情) -->
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
<template #logType="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="row.logType" />
</template>
<template #result="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="row.result" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,92 @@
import { reactive } from 'vue'
import { DICT_TYPE } from '@/utils/dict'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const { t } = useI18n() // 国际化
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '日志类型',
field: 'logType',
dictType: DICT_TYPE.SYSTEM_LOGIN_TYPE,
search: {
show: true
}
},
{
label: '用户名称',
field: 'username',
search: {
show: true
}
},
{
label: '登录地址',
field: 'userIp',
search: {
show: true
}
},
{
label: 'userAgent',
field: 'userAgent'
},
{
label: '登陆结果',
field: 'result',
dictType: DICT_TYPE.SYSTEM_LOGIN_RESULT,
search: {
show: true
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('common.createTime'),
field: 'daterange',
table: {
show: false
},
form: {
show: false
},
detail: {
show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'daterange',
valueFormat: 'YYYY-MM-DD'
}
}
},
{
label: t('table.action'),
field: 'action',
width: '80px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,404 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { handleTree } from '@/utils/tree'
import dayjs from 'dayjs'
import { IconSelect } from '@/components/Icon'
import { Tooltip } from '@/components/Tooltip'
import * as MenuApi from '@/api/system/menu'
import { useI18n } from '@/hooks/web/useI18n'
import {
ElRow,
ElCol,
ElTable,
ElTableColumn,
ElForm,
ElFormItem,
ElInput,
ElInputNumber,
ElSelect,
ElOption,
ElMessageBox,
ElMessage,
ElCascader,
ElRadioGroup,
ElRadioButton
} from 'element-plus'
import { MenuVO } from '@/api/system/menu/types'
import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
const { t } = useI18n() // 国际化
// ========== 创建菜单树结构 ==========
const loading = ref(true)
const menuData = ref([]) // 树形结构
const getList = async () => {
const res = await MenuApi.getMenuListApi(queryParams)
menuData.value = handleTree(res)
loading.value = false
}
const menuProps = {
checkStrictly: true,
children: 'children',
label: 'name',
value: 'id'
}
const menuOptions = ref([]) // 树形结构
const getTree = async () => {
const res = await MenuApi.listSimpleMenusApi()
menuOptions.value = handleTree(res)
}
// ========== 查询 ==========
const queryParams = reactive({
name: undefined,
status: undefined
})
// 查询操作
const handleQuery = async () => {
await getList()
}
// 重置操作
const resetQuery = async () => {
queryParams.name = undefined
queryParams.status = undefined
await getList()
}
// ========== CRUD 相关 ==========
const actionLoading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const menuForm = ref<MenuVO>({
id: 0,
name: '',
permission: '',
type: SystemMenuTypeEnum.DIR,
sort: 1,
parentId: 0,
path: '',
icon: '',
component: '',
status: CommonStatusEnum.ENABLE,
visible: true,
keepAlive: true,
createTime: ''
})
// 表单校验
const rules = {
name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
}
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新建操作
const handleCreate = () => {
// 重置表单
setDialogTile('create')
}
// 修改操作
const handleUpdate = async (row: MenuVO) => {
// 设置数据
const res = await MenuApi.getMenuApi(row.id)
menuForm.value = res
setDialogTile('update')
}
// 删除操作
const handleDelete = async (row: MenuVO) => {
ElMessageBox.confirm(t('common.delDataMessage'), t('common.confirmTitle'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(async () => {
await MenuApi.deleteMenuApi(row.id)
ElMessage.success(t('common.delSuccess'))
})
.catch(() => {})
await getList()
}
// 保存操作
function isExternal(path: string) {
return /^(https?:|mailto:|tel:)/.test(path)
}
const submitForm = async () => {
actionLoading.value = true
// 提交请求
try {
if (
menuForm.value.type === SystemMenuTypeEnum.DIR ||
menuForm.value.type === SystemMenuTypeEnum.MENU
) {
if (!isExternal(menuForm.value.path)) {
if (menuForm.value.parentId === 0 && menuForm.value.path.charAt(0) !== '/') {
ElMessage.error('路径必须以 / 开头')
return
} else if (menuForm.value.parentId !== 0 && menuForm.value.path.charAt(0) === '/') {
ElMessage.error('路径不能以 / 开头')
return
}
}
}
if (actionType.value === 'create') {
await MenuApi.createMenuApi(menuForm.value)
ElMessage.success(t('common.createSuccess'))
} else {
await MenuApi.updateMenuApi(menuForm.value)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
actionLoading.value = false
}
}
// ========== 初始化 ==========
onMounted(async () => {
await getList()
getTree()
})
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<el-form :model="queryParams" ref="queryForm" :inline="true">
<el-form-item label="菜单名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入菜单名称" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择菜单状态">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" />
{{ t('common.query') }}
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh-right" class="mr-5px" />
{{ t('common.reset') }}
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:notice:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<el-table
v-loading="loading"
table-layout="auto"
row-key="id"
:data="menuData"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column label="菜单名称" prop="name" width="240px">
<template #default="scope">
<Icon :icon="scope.row.icon" />
<span class="ml-3">{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="菜单类型" prop="type">
<template #default="scope">
<DictTag :type="DICT_TYPE.SYSTEM_MENU_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="路由地址" prop="path" />
<el-table-column label="组件路径" prop="component" />
<el-table-column label="权限标识" prop="permission" />
<el-table-column label="排序" prop="sort" />
<el-table-column label="状态" prop="status">
<template #default="scope">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime">
<template #default="scope">
<span>{{ dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button
link
type="primary"
v-hasPermi="['system:menu:update']"
@click="handleUpdate(scope.row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:menu:delete']"
@click="handleDelete(scope.row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 添加或修改菜单对话框 -->
<Dialog v-model="dialogVisible" :title="dialogTitle" maxHeight="400px" width="40%">
<el-form
:model="menuForm"
:rules="rules"
:inline="true"
label-width="120px"
label-position="right"
>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="上级菜单">
<el-cascader
:options="menuData"
:props="menuProps"
placeholder="请选择上级菜单"
v-model="menuForm.parentId"
class="w-100"
clearable
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="menuForm.type">
<el-radio-button
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="菜单名称" prop="name">
<el-input v-model="menuForm.name" placeholder="请输入菜单名称" clearable />
</el-form-item>
</el-col>
<template v-if="menuForm.type !== 3">
<el-col :span="12">
<el-form-item label="菜单图标">
<IconSelect v-model="menuForm.icon" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="路由地址" prop="path">
<template #label>
<Tooltip
titel="路由地址"
message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
/>
</template>
<el-input v-model="menuForm.path" placeholder="请输入路由地址" clearable />
</el-form-item>
</el-col>
</template>
<template v-if="menuForm.type === 2">
<el-col :span="12">
<el-form-item label="路由地址" prop="component">
<el-input v-model="menuForm.component" placeholder="请输入组件地址" clearable />
</el-form-item>
</el-col>
</template>
<template v-if="menuForm.type !== 1">
<el-col :span="12">
<el-form-item label="权限标识" prop="permission">
<template #label>
<Tooltip
titel="权限标识"
message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
/>
</template>
<el-input v-model="menuForm.permission" placeholder="请输入权限标识" clearable />
</el-form-item>
</el-col>
</template>
<el-col :span="12">
<el-form-item label="显示排序" prop="sort">
<el-input-number v-model="menuForm.sort" controls-position="right" :min="0" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="菜单状态" prop="status">
<el-radio-group v-model="menuForm.status">
<el-radio-button
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<template v-if="menuForm.type !== 3">
<el-col :span="12">
<el-form-item label="显示状态" prop="status">
<template #label>
<Tooltip
titel="显示状态"
message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问"
/>
</template>
<el-radio-group v-model="menuForm.visible">
<el-radio-button key="true" :label="true">显示</el-radio-button>
<el-radio-button key="false" :label="false">隐藏</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</template>
<template v-if="menuForm.type === 2">
<el-col :span="12">
<el-form-item label="缓存状态" prop="keepAlive">
<template #label>
<Tooltip
titel="缓存状态"
message="选择缓存时,则会被 `keep-alive` 缓存,需要匹配组件的 `name` 和路由地址保持一致"
/>
</template>
<el-radio-group v-model="menuForm.keepAlive">
<el-radio-button key="true" :label="true">缓存</el-radio-button>
<el-radio-button key="false" :label="false">不缓存</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>
<style lang="less" scoped>
:deep(.el-button.is-text) {
margin-left: 0;
padding: 8px 10px;
}
</style>

View File

@@ -0,0 +1,166 @@
import { reactive } from 'vue'
import { required } from '@/utils/formRules'
import { useI18n } from '@/hooks/web/useI18n'
// 国际化
const { t } = useI18n()
// 修改
export const modelSchema = reactive<FormSchema[]>([
{
label: '上级菜单',
field: 'parentId',
component: 'Input',
formItemProps: {
rules: [required]
}
},
{
label: '菜单类型',
field: 'type',
component: 'RadioButton',
formItemProps: {
rules: [required]
},
componentProps: {
options: [
{
label: '目录',
value: 1
},
{
label: '菜单',
value: 2
},
{
label: '按钮',
value: 3
}
]
}
},
{
label: '菜单图标',
field: 'icon',
component: 'Input',
formItemProps: {
rules: [required]
}
},
{
label: '菜单名称',
field: 'name',
component: 'Input',
formItemProps: {
rules: [required]
}
},
{
label: '显示排序',
field: 'sort',
component: 'Input'
},
{
label: '路由地址',
field: 'path',
component: 'Input',
labelMessage: '访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头'
},
{
label: '组件路径',
field: 'component',
component: 'Input'
},
{
label: '权限标识',
field: 'permission',
component: 'Input',
labelMessage:
'Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission(`system:user:list`)`)'
},
{
label: t('common.status'),
field: 'status',
component: 'RadioButton',
value: 0,
formItemProps: {
rules: [required]
},
componentProps: {
options: [
{
label: '开启',
value: 0
},
{
label: '关闭',
value: 1
}
]
},
labelMessage: '选择停用时,路由将不会出现在侧边栏,也不能被访问'
},
{
label: '是否显示',
field: 'visible',
component: 'RadioButton',
value: 0,
formItemProps: {
rules: [required]
},
componentProps: {
options: [
{
label: '显示',
value: 0
},
{
label: '隐藏',
value: 1
}
]
},
labelMessage: '选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问'
},
{
label: '是否缓存',
field: 'keepAlive',
component: 'RadioButton',
value: 0,
componentProps: {
options: [
{
label: '缓存',
value: 0
},
{
label: '不缓存',
value: 1
}
]
},
labelMessage: '选择缓存时,则会被 `keep-alive` 缓存,需要匹配组件的 `name` 和路由地址保持一致'
}
])
// 列表
export const columns = reactive<TableColumn[]>([
{
label: '菜单名称',
field: 'name'
},
{
label: '权限标识',
field: 'permission',
component: 'Input',
formItemProps: {
rules: [required]
}
},
{
label: '排序',
field: 'sort'
},
{
label: t('table.action'),
field: 'action',
width: '180px'
}
])

View File

@@ -0,0 +1,226 @@
<script setup lang="ts">
import { onMounted, ref, unref } from 'vue'
import { handleTree } from '@/utils/tree'
import { useI18n } from '@/hooks/web/useI18n'
import { IconSelect } from '@/components/Icon'
import { ElCard, ElMessage, ElMessageBox, ElTree, ElTreeSelect } from 'element-plus'
import { columns, modelSchema } from './menu.data'
import { Form, FormExpose } from '@/components/Form'
import * as MenuApi from '@/api/system/menu'
import { MenuVO } from '@/api/system/menu/types'
const { t } = useI18n() // 国际化
interface Tree {
id: number
name: string
children?: Tree[]
}
const defaultProps = {
children: 'children',
label: 'name',
value: 'id'
}
// ========== 创建菜单树结构 ==========
const menuOptions = ref([]) // 树形结构
const treeRef = ref<InstanceType<typeof ElTree>>()
const getTree = async () => {
const res = await MenuApi.listSimpleMenusApi()
menuOptions.value = handleTree(res)
}
const filterNode = (value: string, data: Tree) => {
if (!value) return true
return data.name.includes(value)
}
// ========== 菜单信息form表单 ==========
const loading = ref(false) // 遮罩层
const formRef = ref<FormExpose>()
const iconModel = ref('ep:user')
const menuParentId = ref()
const menuStatus = ref('add')
const menuTitle = ref('菜单信息')
const showEdit = ref(false)
// 提交按钮
const submitForm = async () => {
loading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as MenuVO
data.parentId = menuParentId.value
// TODO: 表单提交待完善
if (menuStatus.value === 'add') {
await MenuApi.createMenuApi(data)
} else if (menuStatus.value === 'edit') {
await MenuApi.updateMenuApi(data)
}
} finally {
loading.value = false
}
}
// ========== 按钮列表相关 ==========
const tableData = ref([])
const tableLoading = ref(false)
const onDisabled = ref(true)
const tableTitle = ref('按钮信息')
// 树点击事件
const handleMenuNodeClick = async (data: { [key: string]: any }) => {
showEdit.value = true
const res = await MenuApi.getMenuApi(data.id)
menuTitle.value = res.name + '-菜单信息'
tableTitle.value = res.name + '-按钮列表'
menuParentId.value = data.id
tableData.value = await MenuApi.getMenuListApi({ name: res.name })
unref(formRef)?.setValues(res)
onDisabled.value = true
changeDisabled()
}
const handleCreate = () => {
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
menuParentId.value = 0
onDisabled.value = false
changeDisabled()
}
const handleEdit = () => {
onDisabled.value = false
changeDisabled()
}
const changeDisabled = () => {
unref(formRef)?.setProps({
disabled: onDisabled
})
}
// 修改操作
const handleUpdate = async (row: MenuVO) => {
// 设置数据
const res = await MenuApi.getMenuApi(row.id)
unref(formRef)?.setValues(res)
}
// 删除操作
const handleDelete = (row: MenuVO) => {
ElMessageBox.confirm(t('common.delDataMessage'), t('common.confirmTitle'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(async () => {
await MenuApi.deleteMenuApi(row.id)
ElMessage.success(t('common.delSuccess'))
})
.catch(() => {})
}
onMounted(async () => {
await getTree()
})
</script>
<template>
<div class="flex">
<el-card class="w-1/4 menu" :gutter="12" shadow="always">
<template #header>
<div class="card-header">
<span>菜单列表</span>
<el-button type="primary" v-hasPermi="['system:menu:create']" @click="handleCreate">
新增根节点
</el-button>
</div>
</template>
<!-- <p>菜单列表</p> -->
<el-tree
ref="treeRef"
node-key="id"
:accordion="true"
:data="menuOptions"
:props="defaultProps"
:highlight-current="true"
:filter-method="filterNode"
@node-click="handleMenuNodeClick"
/>
</el-card>
<el-card class="w-1/2 menu" style="margin-left: 10px" :gutter="12" shadow="hover">
<template #header>
<div class="card-header">
<span>{{ menuTitle }}</span>
</div>
</template>
<div v-if="!showEdit">
<span>请从左侧选择菜单</span>
</div>
<div v-if="showEdit">
<Form :loading="loading" :schema="modelSchema" ref="formRef">
<template #parentId>
<el-tree-select
node-key="id"
v-model="menuParentId"
:props="defaultProps"
:data="menuOptions"
check-strictly
/>
</template>
<template #icon>
<IconSelect v-model="iconModel" />
</template>
</Form>
<el-button
v-if="!onDisabled"
type="primary"
v-hasPermi="['system:menu:update']"
:loading="loading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button v-if="!onDisabled" :loading="loading" @click="showEdit = false">
{{ t('common.cancel') }}
</el-button>
<el-button
v-if="onDisabled"
v-hasPermi="['system:menu:update']"
type="primary"
:loading="loading"
@click="handleEdit"
>
{{ t('action.edit') }}
</el-button>
</div>
</el-card>
<el-card class="w-1/2 menu" style="margin-left: 10px" :gutter="12" shadow="hover">
<template #header>
<div class="card-header">
<span>{{ tableTitle }}</span>
<!-- <el-button type="primary">新增根节点</el-button> -->
</div>
</template>
<!-- 列表 -->
<Table :loading="tableLoading" :columns="columns" :data="tableData">
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:menu:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:menu:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</el-card>
</div>
</template>
<style scoped>
.menu {
height: 1000px;
max-height: 1800px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@@ -0,0 +1,189 @@
<script setup lang="ts">
import { ref, unref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { NoticeVO } from '@/api/system/notice/types'
import { rules, allSchemas } from './notice.data'
import * as NoticeApi from '@/api/system/notice'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<NoticeVO>, NoticeVO>({
getListApi: NoticeApi.getNoticePageApi,
delListApi: NoticeApi.deleteNoticeApi
})
const { getList, setSearchParams, delList } = methods
// ========== CRUD 相关 ==========
const actionLoading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: NoticeVO) => {
setDialogTile('update')
// 设置数据
const res = await NoticeApi.getNoticeApi(row.id)
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
actionLoading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as NoticeVO
if (actionType.value === 'create') {
await NoticeApi.createNoticeApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await NoticeApi.updateNoticeApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
actionLoading.value = false
}
}
// 删除操作
const handleDelete = (row: NoticeVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: NoticeVO) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:notice:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_NOTICE_TYPE" :value="row.type" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:notice:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:notice:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:notice:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle" maxHeight="500px" width="50%">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_NOTICE_TYPE" :value="row.type" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,86 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { DICT_TYPE } from '@/utils/dict'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
title: [required],
type: [required],
status: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '公告标题',
field: 'title',
search: {
show: true
}
},
{
label: '公告类型',
field: 'type',
dictType: DICT_TYPE.SYSTEM_NOTICE_TYPE,
search: {
show: true
}
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
search: {
show: true
},
form: {
component: 'RadioButton'
}
},
{
label: '公告内容',
field: 'content',
form: {
component: 'Editor',
colProps: {
span: 24
},
componentProps: {
valueHtml: ''
}
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '240px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,141 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n()
// 国际化
// 表单校验
export const rules = reactive({
clientId: [required],
secret: [required],
name: [required],
logo: [required],
status: [required],
accessTokenValiditySeconds: [required],
refreshTokenValiditySeconds: [required],
redirectUris: [required],
authorizedGrantTypes: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: '客户端编号',
field: 'clientId',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '客户端密钥',
field: 'secret'
},
{
label: '应用名',
field: 'name',
search: {
show: true
}
},
{
label: '应用图标',
field: 'logo'
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
search: {
show: true
}
},
{
label: '访问令牌的有效期',
field: 'accessTokenValiditySeconds'
},
{
label: '刷新令牌的有效期',
field: 'refreshTokenValiditySeconds'
},
{
label: '授权类型',
field: 'authorizedGrantTypes',
dictType: DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE
},
{
label: '授权范围',
field: 'scopes',
table: {
show: false
}
},
{
label: '自动授权范围',
field: 'autoApproveScopes',
table: {
show: false
}
},
{
label: '可重定向的 URI 地址',
field: 'redirectUris',
table: {
show: false
}
},
{
label: '权限',
field: 'authorities',
table: {
show: false
}
},
{
label: '资源',
field: 'resourceIds',
table: {
show: false
}
},
{
label: '附加信息',
field: 'additionalInformation',
table: {
show: false
},
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '240px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,187 @@
<script setup lang="ts">
import { ref, unref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage, ElImage } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { OAuth2ClientVo } from '@/api/system/oauth2/client.types'
import { rules, allSchemas } from './client.data'
import * as ClientApi from '@/api/system/oauth2/client'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<OAuth2ClientVo>, OAuth2ClientVo>({
getListApi: ClientApi.getOAuth2ClientPageApi,
delListApi: ClientApi.deleteOAuth2ClientApi
})
const { getList, setSearchParams, delList } = methods
// ========== CRUD 相关 ==========
const actionLoading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: OAuth2ClientVo) => {
setDialogTile('update')
// 设置数据
const res = await ClientApi.getOAuth2ClientApi(row.id)
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
actionLoading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as OAuth2ClientVo
if (actionType.value === 'create') {
await ClientApi.createOAuth2ClientApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await ClientApi.updateOAuth2ClientApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
actionLoading.value = false
}
}
// 删除操作
const handleDelete = (row: OAuth2ClientVo) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: OAuth2ClientVo) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:oauth2-client:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #logo="{ row }">
<el-image :src="row.logo" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:oauth2-client:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:oauth2-client:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:oauth2-client:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,92 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { useTable } from '@/hooks/web/useTable'
import { allSchemas } from './token.data'
import { DICT_TYPE } from '@/utils/dict'
import { useI18n } from '@/hooks/web/useI18n'
import type { OAuth2TokenVo } from '@/api/system/oauth2/token.types'
import * as TokenApi from '@/api/system/oauth2/token'
import { ref } from 'vue'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<OAuth2TokenVo>, OAuth2TokenVo>({
getListApi: TokenApi.getAccessTokenPageApi,
delListApi: TokenApi.deleteAccessTokenApi
})
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref(t('action.detail')) // 弹出层标题
const { getList, setSearchParams, delList } = methods
// 详情
const handleDetail = (row: OAuth2TokenVo) => {
// 设置数据
detailRef.value = row
dialogVisible.value = true
}
// 强退操作
const handleForceLogout = (row: OAuth2TokenVo) => {
delList(row.id, false)
}
getList()
</script>
<template>
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #userType="{ row }">
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #expiresTime="{ row }">
<span>{{ dayjs(row.expiresTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button link type="primary" @click="handleDetail(row)">
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:oauth2-token:delete']"
@click="handleForceLogout(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.logout') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(详情) -->
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
<template #userType="{ row }">
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #expiresTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,68 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '用户编号',
field: 'userId',
search: {
show: true
}
},
{
label: '访问令牌',
field: 'accessToken'
},
{
label: '刷新令牌',
field: 'refreshToken'
},
{
label: '用户类型',
field: 'userType',
dictType: DICT_TYPE.USER_TYPE,
search: {
show: true
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: '过期时间',
field: 'expiresTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '200px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { useTable } from '@/hooks/web/useTable'
import { allSchemas } from './operateLog.data'
import { DICT_TYPE } from '@/utils/dict'
import { useI18n } from '@/hooks/web/useI18n'
import type { OperateLogVO } from '@/api/system/operateLog/types'
import * as OperateLogApi from '@/api/system/operateLog'
import { ref } from 'vue'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<OperateLogVO>, OperateLogVO>({
getListApi: OperateLogApi.getOperateLogPageApi,
exportListApi: OperateLogApi.exportOperateLogApi
})
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref(t('action.detail')) // 弹出层标题
const { getList, setSearchParams, exportList } = methods
// 导出操作
const handleExport = async () => {
await exportList('数据.xls')
}
// 详情
const handleDetail = (row: OperateLogVO) => {
// 设置数据
detailRef.value = row
dialogVisible.value = true
}
getList()
</script>
<template>
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button
type="warning"
v-hasPermi="['system:operate-log:export']"
:loading="tableObject.exportLoading"
@click="handleExport"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
</div>
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_OPERATE_TYPE" :value="row.type" />
</template>
<template #duration="{ row }">
<span>{{ row.duration + 'ms' }}</span>
</template>
<template #resultCode="{ row }">
<span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span>
</template>
<template #startTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button link type="primary" @click="handleDetail(row)">
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(详情) -->
<Descriptions :schema="allSchemas.detailSchema" :data="detailRef">
<template #resultCode="{ row }">
<span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span>
</template>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_OPERATE_TYPE" :value="row.type" />
</template>
<template #duration="{ row }">
<span>{{ row.duration + 'ms' }}</span>
</template>
<template #startTime="{ row }">
<span>{{ dayjs(row.startTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,112 @@
import { reactive } from 'vue'
import { DICT_TYPE } from '@/utils/dict'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const { t } = useI18n() // 国际化
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
}
},
{
label: '操作模块',
field: 'module',
search: {
show: true
}
},
{
label: '操作名',
field: 'name'
},
{
label: '操作类型',
field: 'type',
dictType: DICT_TYPE.SYSTEM_OPERATE_TYPE,
search: {
show: true
}
},
{
label: '请求方法名',
field: 'requestMethod'
},
{
label: '请求地址',
field: 'requestUrl'
},
{
label: '操作人员',
field: 'userNickname'
},
{
label: '操作明细',
field: 'content',
table: {
show: false
}
},
{
label: '用户 IP',
field: 'userIp',
table: {
show: false
}
},
{
label: 'userAgent',
field: 'userAgent'
},
{
label: '操作结果',
field: 'resultCode'
},
{
label: '操作日期',
field: 'startTime',
form: {
show: false
}
},
{
label: '执行时长',
field: 'duration'
},
{
label: '操作日期',
field: 'daterange',
table: {
show: false
},
form: {
show: false
},
detail: {
show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'daterange',
valueFormat: 'YYYY-MM-DD'
}
}
},
{
label: t('table.action'),
field: 'action',
width: '80px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,198 @@
<script setup lang="ts">
import { ref, unref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { PostVO } from '@/api/system/post/types'
import { rules, allSchemas } from './post.data'
import * as PostApi from '@/api/system/post'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<PostVO>, PostVO>({
getListApi: PostApi.getPostPageApi,
delListApi: PostApi.deletePostApi,
exportListApi: PostApi.exportPostApi
})
const { getList, setSearchParams, delList, exportList } = methods
// 导出操作
const handleExport = async () => {
await exportList('用户数据.xls')
}
// ========== CRUD 相关 ==========
const actionLoading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: PostVO) => {
setDialogTile('update')
// 设置数据
const res = await PostApi.getPostApi(row.id)
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
actionLoading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as PostVO
if (actionType.value === 'create') {
await PostApi.createPostApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await PostApi.updatePostApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
actionLoading.value = false
}
}
// 删除操作
const handleDelete = (row: PostVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: PostVO) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:post:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
<el-button
type="warning"
v-hasPermi="['system:post:export']"
:loading="tableObject.exportLoading"
@click="handleExport"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:post:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:post:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:post:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,74 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
code: [required],
sort: [required],
status: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '岗位名称',
field: 'name',
search: {
show: true
}
},
{
label: '岗位编码',
field: 'code',
search: {
show: true
}
},
{
label: '岗位顺序',
field: 'sort'
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
search: {
show: true
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '240px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,327 @@
<script setup lang="ts">
import { reactive, ref, unref } from 'vue'
import dayjs from 'dayjs'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { RoleVO } from '@/api/system/role/types'
import { rules, allSchemas } from './role.data'
import * as RoleApi from '@/api/system/role'
import Dialog from '@/components/Dialog/src/Dialog.vue'
import {
ElForm,
ElFormItem,
ElInput,
ElSelect,
ElOption,
ElMessage,
ElTree,
ElCard,
ElCheckbox
} from 'element-plus'
import { listSimpleMenusApi } from '@/api/system/menu'
import { listSimpleDeptApi } from '@/api/system/dept'
import { handleTree } from '@/utils/tree'
import { SystemDataScopeEnum } from '@/utils/constants'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<RoleVO>, RoleVO>({
getListApi: RoleApi.getRolePageApi,
delListApi: RoleApi.deleteRoleApi
})
const { getList, setSearchParams, delList } = methods
// ========== CRUD 相关 ==========
const loading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: RoleVO) => {
setDialogTile('update')
// 设置数据
const res = await RoleApi.getRoleApi(row.id)
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
loading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as RoleVO
if (actionType.value === 'create') {
await RoleApi.createRoleApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await RoleApi.updateRoleApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
loading.value = false
}
}
// 删除操作
const handleDelete = (row: RoleVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: RoleVO) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
// ========== 数据权限 ==========
const dataScopeForm = reactive({
name: '',
code: '',
dataScope: 0,
checkStrictly: true,
checkList: []
})
const defaultProps = {
children: 'children',
label: 'name',
value: 'id'
}
const treeOptions = ref([]) // 菜单树形结构
const treeRef = ref<InstanceType<typeof ElTree>>()
const dialogScopeVisible = ref(false)
const dialogScopeTitle = ref('数据权限')
const actionScopeType = ref('')
const dataScopeDictDatas = ref()
// 选项
const treeNodeAll = ref(false)
// 权限操作
const handleScope = async (type: string, row: RoleVO) => {
dataScopeForm.name = row.name
dataScopeForm.code = row.code
if (type === 'menu') {
const menuRes = await listSimpleMenusApi()
treeOptions.value = handleTree(menuRes)
dataScopeDictDatas.value = getDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE)
} else if (type === 'dept') {
const deptRes = await listSimpleDeptApi()
treeOptions.value = handleTree(deptRes)
}
actionScopeType.value = type
dialogScopeVisible.value = true
}
// 树权限(父子联动)
const handleCheckedTreeConnect = (value) => {
dataScopeForm.checkStrictly = value ? true : false
}
// 全选/全不选
const handleCheckedTreeNodeAll = (value) => {
treeRef.value?.setCheckedNodes(value ? dataScopeForm.checkList : [])
}
// TODO:保存
const submitScope = () => {
console.info()
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:role:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_ROLE_TYPE" :value="row.type" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:role:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:role:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:permission:assign-role-menu']"
@click="handleScope('menu', row)"
>
<Icon icon="ep:basketball" class="mr-5px" /> 菜单权限
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:permission:assign-role-data-scope']"
@click="handleScope('data', row)"
>
<Icon icon="ep:coin" class="mr-5px" /> 数据权限
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:role:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_ROLE_TYPE" :value="row.type" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="loading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
<Dialog v-model="dialogScopeVisible" :title="dialogScopeTitle">
<el-form :model="dataScopeForm">
<el-form-item label="角色名称">
<el-input v-model="dataScopeForm.name" :disabled="true" />
</el-form-item>
<el-form-item label="角色标识">
<el-input v-model="dataScopeForm.code" :disabled="true" />
</el-form-item>
<!-- 分配角色的数据权限对话框 -->
<el-form-item label="权限范围" v-if="actionScopeType === 'data'">
<el-select v-model="dataScopeForm.dataScope">
<el-option
v-for="item in dataScopeDictDatas"
:key="parseInt(item.value)"
:label="item.label"
:value="parseInt(item.value)"
/>
</el-select>
</el-form-item>
<!-- 分配角色的菜单权限对话框 -->
<el-form-item
label="权限范围"
v-if="
actionScopeType === 'menu' || dataScopeForm.dataScope === SystemDataScopeEnum.DEPT_CUSTOM
"
>
<el-card class="box-card">
<template #header>
<el-checkbox
:checked="!dataScopeForm.checkStrictly"
@change="handleCheckedTreeConnect($event)"
>父子联动(选中父节点,自动选择子节点)
</el-checkbox>
<el-checkbox v-model="treeNodeAll" @change="handleCheckedTreeNodeAll($event)">
全选/全不选
</el-checkbox>
</template>
<el-tree
ref="treeRef"
node-key="id"
show-checkbox
default-expand-all
:check-strictly="dataScopeForm.checkStrictly"
:props="defaultProps"
:data="treeOptions"
empty-text="加载中,请稍后"
/>
</el-card>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<template #footer>
<el-button type="primary" :loading="loading" @click="submitScope">
{{ t('action.save') }}
</el-button>
<el-button @click="dialogScopeVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,89 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { DICT_TYPE } from '@/utils/dict'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
// 国际化
const { t } = useI18n()
// 表单校验
export const rules = reactive({
name: [required],
code: [required],
sort: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '角色名称',
field: 'name',
search: {
show: true
}
},
{
label: '角色类型',
field: 'type',
dictType: DICT_TYPE.SYSTEM_ROLE_TYPE
},
{
label: '角色标识',
field: 'code',
search: {
show: true
}
},
{
label: '显示顺序',
field: 'sort',
form: {
component: 'InputNumber',
value: 0
}
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
search: {
show: true
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'daterange',
valueFormat: 'YYYY-MM-DD'
}
}
},
{
field: 'action',
width: '450px',
label: t('table.action'),
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,222 @@
<script setup lang="ts">
import { onMounted, ref, unref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage, ElTag, ElSelect, ElOption } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { SensitiveWordVO } from '@/api/system/sensitiveWord/types'
import { rules, allSchemas } from './sensitiveWord.data'
import * as SensitiveWordApi from '@/api/system/sensitiveWord'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<SensitiveWordVO>, SensitiveWordVO>({
getListApi: SensitiveWordApi.getSensitiveWordPageApi,
delListApi: SensitiveWordApi.deleteSensitiveWordApi,
exportListApi: SensitiveWordApi.exportSensitiveWordApi
})
const { getList, setSearchParams, delList, exportList } = methods
// 导出操作
const handleExport = async () => {
await exportList('敏感词数据.xls')
}
// 获取标签
const tagsOptions = ref()
const getTags = async () => {
const res = await SensitiveWordApi.getSensitiveWordTagsApi()
tagsOptions.value = res
}
// ========== CRUD 相关 ==========
const actionLoading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
const tags = ref()
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: SensitiveWordVO) => {
setDialogTile('update')
// 设置数据
const res = await SensitiveWordApi.getSensitiveWordApi(row.id)
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
actionLoading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as SensitiveWordVO
if (actionType.value === 'create') {
await SensitiveWordApi.createSensitiveWordApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await SensitiveWordApi.updateSensitiveWordApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
actionLoading.value = false
}
}
// 删除操作
const handleDelete = (row: SensitiveWordVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: SensitiveWordVO) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
// ========== 初始化 ==========
onMounted(async () => {
await getTags()
await getList()
})
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:post:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
<el-button
type="warning"
v-hasPermi="['system:post:export']"
:loading="tableObject.exportLoading"
@click="handleExport"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #tags="{ row }">
<el-tag
:disable-transitions="true"
:key="index"
v-for="(tag, index) in row.tags"
:index="index"
>
{{ tag }}
</el-tag>
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:post:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:post:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:post:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
>
<template #tags>
<el-select v-model="tags" multiple placeholder="请选择">
<el-option v-for="item in tagsOptions" :key="item" :label="item" :value="item" />
</el-select>
</template>
</Form>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,79 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
tags: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '敏感词',
field: 'name',
search: {
show: true
}
},
{
label: '标签',
field: 'tags'
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
search: {
show: true
}
},
{
label: '描述',
field: 'description',
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '240px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,190 @@
<script setup lang="ts">
import { ref, unref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { SmsChannelVO } from '@/api/system/sms/smsChannel/types'
import { rules, allSchemas } from './sms.channel.data'
import * as SmsChannelApi from '@/api/system/sms/smsChannel'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<SmsChannelVO>, SmsChannelVO>({
getListApi: SmsChannelApi.getSmsChannelPageApi,
delListApi: SmsChannelApi.deleteSmsChannelApi
})
const { getList, setSearchParams, delList } = methods
// ========== CRUD 相关 ==========
const loading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: SmsChannelVO) => {
setDialogTile('update')
// 设置数据
const res = await SmsChannelApi.getSmsChannelApi(row.id)
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
loading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as SmsChannelVO
if (actionType.value === 'create') {
await SmsChannelApi.createSmsChannelApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await SmsChannelApi.updateSmsChannelApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
loading.value = false
}
}
// 删除操作
const handleDelete = (row: SmsChannelVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: SmsChannelVO) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:sms-channel:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #code="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="row.code" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:sms-channel:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:sms-channel:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:sms-channel:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #code="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="row.code" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="loading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,104 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
signature: [required],
code: [required],
apiKey: [required],
status: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '短信签名',
field: 'signature',
search: {
show: true
}
},
{
label: '渠道编码',
field: 'code',
dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE,
search: {
show: true
}
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
search: {
show: true
}
},
{
label: '短信 API 的账号',
field: 'apiKey'
},
{
label: '短信 API 的密钥',
field: 'apiSecret'
},
{
label: '短信发送回调 URL',
field: 'callbackUrl'
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('common.createTime'),
field: 'daterange',
table: {
show: false
},
form: {
show: false
},
detail: {
show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'daterange',
valueFormat: 'YYYY-MM-DD'
}
}
},
{
label: t('table.action'),
field: 'action',
width: '240px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,99 @@
<script setup lang="ts">
import { ref } from 'vue'
import dayjs from 'dayjs'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import type { SmsLogVO } from '@/api/system/sms/smsLog/types'
import { allSchemas } from './sms.log.data'
import * as SmsLoglApi from '@/api/system/sms/smsLog'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<SmsLogVO>, SmsLogVO>({
getListApi: SmsLoglApi.getSmsLogPageApi
})
const { getList, setSearchParams } = methods
// ========== CRUD 相关 ==========
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref(t('action.detail')) // 弹出层标题
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
const handleDetail = (row: SmsLogVO) => {
// 设置数据
detailRef.value = row
dialogVisible.value = true
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #code="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="row.code" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #receiveTime="{ row }">
<span>{{ dayjs(row.receiveTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:sms-channel:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #code="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="row.code" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,114 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '手机号',
field: 'mobile',
search: {
show: true
}
},
{
label: '短信内容',
field: 'templateContent'
},
{
label: '短信渠道',
field: 'channelId',
dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE,
search: {
show: true
}
},
{
label: '发送状态',
field: 'sendStatus',
dictType: DICT_TYPE.SYSTEM_SMS_SEND_STATUS,
search: {
show: true
}
},
{
label: '接收状态',
field: 'receiveTime',
dictType: DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS,
search: {
show: true
}
},
{
label: '模板编号',
field: 'templateId',
search: {
show: true
}
},
{
label: '短信类型',
field: 'channelId',
dictType: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE,
search: {
show: true
}
},
{
label: '接收时间',
field: 'receiveTime',
form: {
show: false
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: '接收时间',
field: 'daterange',
table: {
show: false
},
form: {
show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'daterange',
valueFormat: 'YYYY-MM-DD'
}
}
},
{
label: t('table.action'),
field: 'action',
width: '80px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,216 @@
<script setup lang="ts">
import { reactive, ref, unref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { SmsTemplateVO } from '@/api/system/sms/smsTemplate/types'
import { rules, allSchemas } from './sms.template.data'
import * as SmsTemplateApi from '@/api/system/sms/smsTemplate'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<SmsTemplateVO>, SmsTemplateVO>({
getListApi: SmsTemplateApi.getSmsTemplatePageApi,
delListApi: SmsTemplateApi.deleteSmsTemplateApi
})
const { getList, setSearchParams, delList } = methods
// ========== CRUD 相关 ==========
const loading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: SmsTemplateVO) => {
setDialogTile('update')
// 设置数据
const res = await SmsTemplateApi.getSmsTemplateApi(row.id)
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
loading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as SmsTemplateVO
if (actionType.value === 'create') {
await SmsTemplateApi.createSmsTemplateApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await SmsTemplateApi.updateSmsTemplateApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
loading.value = false
}
}
// 删除操作
const handleDelete = (row: SmsTemplateVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: SmsTemplateVO) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
const sendSmsForm = reactive({
content: '',
params: '',
mobile: '',
templateCode: '',
templateParams: {}
})
// TODO 发送短信
// ========== 测试相关 ==========
const handleSendSms = (row: any) => {
sendSmsForm.content = row.content
sendSmsForm.params = row.params
sendSmsForm.templateCode = row.code
sendSmsForm.templateParams = row.params.reduce(function (obj, item) {
obj[item] = undefined
return obj
}, {})
}
// ========== 初始化 ==========
getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:sms-channel:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="row.type" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:sms-template:send-sms']"
@click="handleSendSms(row)"
>
<Icon icon="ep:cpu" class="mr-5px" /> {{ t('action.test') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:sms-template:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:sms-template:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:sms-template:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #type="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="row.code" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="loading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,115 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
type: [required],
name: [required],
content: [required],
apiTemplateId: [required],
channelId: [required],
code: [required],
sort: [required],
status: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '模板编码',
field: 'code',
search: {
show: true
}
},
{
label: '模板名称',
field: 'name',
search: {
show: true
}
},
{
label: '模板内容',
field: 'content'
},
{
label: '短信 API 的模板编号',
field: 'apiTemplateId',
search: {
show: true
}
},
{
label: '短信类型',
field: 'type',
dictType: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS
},
{
label: t('form.remark'),
field: 'remark',
table: {
show: false
}
},
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('common.createTime'),
field: 'daterange',
table: {
show: false
},
form: {
show: false
},
detail: {
show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'daterange',
valueFormat: 'YYYY-MM-DD'
}
}
},
{
label: t('table.action'),
field: 'action',
width: '320px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,254 @@
<script setup lang="ts">
import { ref, unref, onMounted } from 'vue'
import dayjs from 'dayjs'
import { ElMessage, ElTag, ElSelect, ElOption } from 'element-plus'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import type { TenantVO } from '@/api/system/tenant/types'
import { rules, allSchemas } from './tenant.data'
import * as TenantApi from '@/api/system/tenant'
import { getTenantPackageList } from '@/api/system/tenantPackage'
import { TenantPackageVO } from '@/api/system/tenantPackage/types'
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<TenantVO>, TenantVO>({
getListApi: TenantApi.getTenantPageApi,
delListApi: TenantApi.deleteTenantApi,
exportListApi: TenantApi.exportTenantApi
})
const { getList, setSearchParams, delList, exportList } = methods
// 导出操作
const handleExport = async () => {
await exportList('租户数据.xls')
}
// ========== 套餐 ==========
const tenantPackageId = ref() // 套餐
const tenantPackageOptions = ref<TenantPackageVO[]>([]) //套餐列表
const getTenantPackageOptions = async () => {
const res = await getTenantPackageList()
tenantPackageOptions.value.push(...res)
}
const getPackageName = (packageId: number) => {
for (let item of tenantPackageOptions.value) {
if (item.id === packageId) {
return item.name
}
}
return '未知套餐'
}
// ========== CRUD 相关 ==========
const actionLoading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
tenantPackageId.value = ''
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: any) => {
setDialogTile('update')
// 设置数据
const res = await TenantApi.getTenantApi(row.id)
tenantPackageId.value = res.packageId
res.expireTime = dayjs(res.expireTime).format('YYYY-MM-DD HH:mm:ss')
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
actionLoading.value = true
// 提交请求 unix()
try {
const data = unref(formRef)?.formModel as TenantVO
data.packageId = tenantPackageId.value
if (actionType.value === 'create') {
data.expireTime = dayjs(data.expireTime).valueOf().toString()
await TenantApi.createTenantApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
data.expireTime = dayjs(data.expireTime).valueOf().toString()
await TenantApi.updateTenantApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
actionLoading.value = false
}
}
// 删除操作
const handleDelete = (row: TenantVO) => {
delList(row.id, false)
}
// ========== 详情相关 ==========
const detailRef = ref() // 详情 Ref
// 详情操作
const handleDetail = async (row: any) => {
// 设置数据
detailRef.value = row
setDialogTile('detail')
}
// ========== 初始化 ==========
onMounted(async () => {
await getList()
await getTenantPackageOptions()
})
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:tenant:create']" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
<el-button
type="warning"
v-hasPermi="['system:tenant:export']"
:loading="tableObject.exportLoading"
@click="handleExport"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #accountCount="{ row }">
<el-tag> {{ row.accountCount }} </el-tag>
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #packageId="{ row }">
<!-- <DictTag :type="DICT_TYPE.SYSTEM_TENANT_PACKAGE_ID" :value="row.packageId" />-->
<el-tag v-if="row.packageId === 0" type="danger">系统租户</el-tag>
<el-tag v-else type="success"> {{ getPackageName(row.packageId) }} </el-tag>
</template>
<template #expireTime="{ row }">
<span>{{ dayjs(row.expireTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<!-- <el-button type="text" v-hasPermi="['system:tenant:update']" @click="handleUpdate(row)">-->
<el-button
link
type="primary"
v-hasPermi="['system:tenant:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:tenant:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:tenant:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
>
<template #packageId>
<el-select v-model="tenantPackageId" placeholder="Select">
<el-option
v-for="item in tenantPackageOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
</Form>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #packageId="{ row }">
<el-tag v-if="row.packageId === 0" type="danger">系统租户</el-tag>
<el-tag v-else type="success"> {{ getPackageName(row.packageId) }} </el-tag>
</template>
<template #expireTime="{ row }">
<span>{{ dayjs(row.expireTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,109 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
packageId: [required],
contactName: [required],
contactMobile: [required],
accountCount: [required],
expireTime: [required],
domain: [required],
status: [required]
})
// CrudSchema.
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '租户名称',
field: 'name',
search: {
show: true
}
},
{
label: '租户套餐',
field: 'packageId'
},
{
label: '联系人',
field: 'contactName',
search: {
show: true
}
},
{
label: '联系手机',
field: 'contactMobile',
search: {
show: true
}
},
{
label: '账号额度',
field: 'accountCount',
form: {
component: 'InputNumber',
value: 0
}
},
{
label: '过期时间',
field: 'expireTime',
form: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetime',
valueFormat: 'YYYY-MM-DD HH:mm:ss'
}
}
},
{
label: '绑定域名',
field: 'domain'
},
{
label: '租户状态',
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
search: {
show: true
}
},
{
label: t('table.createTime'),
field: 'createTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '240px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,202 @@
<script setup lang="ts">
import { onMounted, ref, unref } from 'vue'
import dayjs from 'dayjs'
import { handleTree } from '@/utils/tree'
import { DICT_TYPE } from '@/utils/dict'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { FormExpose } from '@/components/Form'
import { TenantPackageVO } from '@/api/system/tenantPackage/types'
import { ElMessage, ElCard, ElCheckbox, ElTree } from 'element-plus'
import { rules, allSchemas } from './tenantPackage.data'
import * as TenantPackageApi from '@/api/system/tenantPackage'
import { listSimpleMenusApi } from '@/api/system/menu'
const { t } = useI18n() // 国际化
const defaultProps = {
children: 'children',
label: 'name',
value: 'id'
}
// ========== 创建菜单树结构 ==========
const menuOptions = ref([]) // 树形结构
const treeRef = ref<InstanceType<typeof ElTree>>()
const getTree = async () => {
const res = await listSimpleMenusApi()
menuOptions.value = handleTree(res)
}
let menuCheckStrictly = true
const menuExpand = ref(false)
const menuNodeAll = ref(false)
// ========== 列表相关 ==========
const { register, tableObject, methods } = useTable<PageResult<TenantPackageVO>, TenantPackageVO>({
getListApi: TenantPackageApi.getTenantPackageTypePageApi,
delListApi: TenantPackageApi.deleteTenantPackageTypeApi
})
const { getList, setSearchParams, delList } = methods
// ========== CRUD 相关 ==========
const loading = ref(false) // 遮罩层
const formRef = ref<FormExpose>() // 表单 Ref
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const menuParentId = ref()
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleCreate = () => {
setDialogTile('create')
// 重置表单
unref(formRef)?.getElFormRef()?.resetFields()
//重置菜单树
unref(treeRef)?.setCheckedKeys([])
menuCheckStrictly = true
menuExpand.value = false
menuNodeAll.value = false
}
// 修改操作
const handleUpdate = async (row: any) => {
setDialogTile('update')
// 设置数据
const res = await TenantPackageApi.getTenantPackageApi(row.id)
unref(formRef)?.setValues(res)
// 设置菜单项
// 设置为严格,避免设置父节点自动选中子节点,解决半选中问题
menuCheckStrictly = true
// 设置选中
unref(treeRef)?.setCheckedKeys(res.menuIds)
// 设置为非严格,继续使用半选中
menuCheckStrictly = false
}
// 提交按钮
const submitForm = async () => {
loading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as TenantPackageVO
data.menuIds = treeRef.value!.getCheckedKeys(false) as string[]
if (actionType.value === 'create') {
await TenantPackageApi.createTenantPackageTypeApi(data)
ElMessage.success(t('common.createSuccess'))
console.log('new data')
} else {
await TenantPackageApi.updateTenantPackageTypeApi(data)
ElMessage.success(t('common.updateSuccess'))
console.log('edit data')
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
loading.value = false
}
}
// 删除操作
const handleDelete = (row: TenantPackageVO) => {
delList(row.id, false)
}
// ========== 初始化 ==========
onMounted(async () => {
await getList()
await getTree()
})
// getList()
</script>
<template>
<!-- 搜索工作区 -->
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</ContentWrap>
<ContentWrap>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" @click="handleCreate">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #createTime="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button link type="primary" @click="handleUpdate(row)">
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button link type="primary" @click="handleDelete(row)">
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle" maxHeight="500px" width="50%">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
>
<template #menuIds>
<el-card class="box-card">
<template #header>
<div class="card-header">
<el-checkbox>展开/折叠</el-checkbox>
<el-checkbox>全选/全不选</el-checkbox>
</div>
</template>
<!-- default-expand-all-->
<el-tree
ref="treeRef"
node-key="id"
v-model="menuParentId"
:props="defaultProps"
:data="menuOptions"
show-checkbox
:check-strictly="menuCheckStrictly"
empty-text="加载菜单中..."
/>
</el-card>
</template>
</Form>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="loading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>

View File

@@ -0,0 +1,89 @@
import { reactive } from 'vue'
import { required } from '@/utils/formRules'
import { useI18n } from '@/hooks/web/useI18n'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
import { DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
id: [required],
type: [required],
remark: [required],
status: [required],
menuIds: [required]
})
// CrudSchema
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '套餐名称',
field: 'name',
search: {
show: true
}
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
search: {
show: true
}
},
{
label: '菜单权限',
field: 'menuIds',
table: {
show: false
}
},
{
label: t('form.remark'),
field: 'remark',
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
},
table: {
show: false
}
},
{
label: '创建时间',
field: 'createTime',
form: {
show: false
}
},
{
label: t('table.action'),
field: 'action',
width: '240px',
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -0,0 +1,475 @@
<script setup lang="ts">
import { onMounted, ref, unref } from 'vue'
import dayjs from 'dayjs'
import {
ElMessage,
ElCard,
ElTree,
ElTreeSelect,
ElSelect,
ElOption,
ElForm,
ElFormItem,
ElUpload,
ElSwitch,
ElCheckbox,
ElMessageBox,
UploadInstance,
UploadRawFile
} from 'element-plus'
import { handleTree } from '@/utils/tree'
import { DICT_TYPE } from '@/utils/dict'
import { useI18n } from '@/hooks/web/useI18n'
import { useTable } from '@/hooks/web/useTable'
import { FormExpose } from '@/components/Form'
import type { UserVO } from '@/api/system/user/types'
import type { PostVO } from '@/api/system/post/types'
import { listSimpleDeptApi } from '@/api/system/dept'
import { listSimplePostsApi } from '@/api/system/post'
import { rules, allSchemas } from './user.data'
import * as UserApi from '@/api/system/user'
import download from '@/utils/download'
import { useCache } from '@/hooks/web/useCache'
import { CommonStatusEnum } from '@/utils/constants'
const { wsCache } = useCache()
interface Tree {
id: number
name: string
children?: Tree[]
}
const defaultProps = {
children: 'children',
label: 'name',
value: 'id'
}
const { t } = useI18n() // 国际化
// ========== 列表相关 ==========
const tableTitle = ref('用户列表')
const { register, tableObject, methods } = useTable<PageResult<UserVO>, UserVO>({
getListApi: UserApi.getUserPageApi,
delListApi: UserApi.deleteUserApi,
exportListApi: UserApi.exportUserApi
})
const { getList, setSearchParams, delList, exportList } = methods
// ========== 创建部门树结构 ==========
const deptOptions = ref([]) // 树形结构
const searchForm = ref<FormExpose>()
const treeRef = ref<InstanceType<typeof ElTree>>()
const getTree = async () => {
const res = await listSimpleDeptApi()
deptOptions.value.push(...handleTree(res))
}
const filterNode = (value: string, data: Tree) => {
if (!value) return true
return data.name.includes(value)
}
const handleDeptNodeClick = (data: { [key: string]: any }) => {
tableObject.paramsObj.params = {
deptId: data.id
}
tableTitle.value = data.name
methods.getList()
}
// ========== CRUD 相关 ==========
const loading = ref(false) // 遮罩层
const actionType = ref('') // 操作按钮的类型
const dialogVisible = ref(false) // 是否显示弹出层
const dialogTitle = ref('edit') // 弹出层标题
const formRef = ref<FormExpose>() // 表单 Ref
const deptId = ref(0) // 部门ID
const postIds = ref<string[]>([]) // 岗位ID
const postOptions = ref<PostVO[]>([]) //岗位列表
// 获取岗位列表
const getPostOptions = async () => {
const res = await listSimplePostsApi()
postOptions.value.push(...res)
}
// 设置标题
const setDialogTile = async (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
// 新增操作
const handleAdd = () => {
setDialogTile('create')
// 重置表单
deptId.value = 0
unref(formRef)?.getElFormRef()?.resetFields()
}
// 修改操作
const handleUpdate = async (row: UserVO) => {
await setDialogTile('update')
// 设置数据
const res = await UserApi.getUserApi(row.id)
deptId.value = res.deptId
postIds.value = res.postIds
unref(formRef)?.setValues(res)
}
// 提交按钮
const submitForm = async () => {
loading.value = true
// 提交请求
try {
const data = unref(formRef)?.formModel as UserVO
data.deptId = deptId.value
data.postIds = postIds.value
if (actionType.value === 'create') {
await UserApi.createUserApi(data)
ElMessage.success(t('common.createSuccess'))
} else {
await UserApi.updateUserApi(data)
ElMessage.success(t('common.updateSuccess'))
}
// 操作成功,重新加载列表
dialogVisible.value = false
await getList()
} finally {
loading.value = false
}
}
// 改变用户状态操作
const handleStatusChange = async (row: UserVO) => {
const text = row.status === CommonStatusEnum.ENABLE ? '停用' : '启用'
ElMessageBox.confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder'), {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(async () => {
row.status =
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
const res = await UserApi.updateUserStatusApi(row.id, row.status)
console.info(res)
ElMessage.success(text + '成功')
await getList()
})
.catch(() => {
row.status =
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
})
}
// 删除操作
const handleDelete = (row: UserVO) => {
delList(row.id, false)
}
// 导出操作
const handleExport = async () => {
await exportList('用户数据.xls')
}
// ========== 详情相关 ==========
const detailRef = ref()
// 详情操作
const handleDetail = async (row: UserVO) => {
// 设置数据
detailRef.value = row
await setDialogTile('detail')
}
// ========== 导入相关 ==========
const importDialogVisible = ref(false)
const uploadDisabled = ref(false)
const importDialogTitle = ref('用户导入')
const updateSupport = ref(0)
let updateUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import'
const uploadHeaders = ref()
// 下载导入模版
const handleImportTemp = async () => {
const res = await UserApi.importUserTemplateApi()
download.excel(res, '用户导入模版.xls')
}
// 文件上传之前判断
const beforeExcelUpload = (file: UploadRawFile) => {
const isExcel =
file.type === 'application/vnd.ms-excel' ||
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
const isLt5M = file.size / 1024 / 1024 < 5
if (!isExcel) ElMessage.error('上传文件只能是 xls / xlsx 格式!')
if (!isLt5M) ElMessage.error('上传文件大小不能超过 5MB!')
return isExcel && isLt5M
}
// 文件上传
const uploadRef = ref<UploadInstance>()
const submitFileForm = () => {
uploadHeaders.value = {
Authorization: 'Bearer ' + wsCache.get('ACCESS_TOKEN'),
'tenant-id': wsCache.get('tenantId')
}
uploadDisabled.value = true
uploadRef.value!.submit()
}
// 文件上传成功
const handleFileSuccess = (response: any): void => {
if (response.code !== 0) {
ElMessage.error(response.msg)
return
}
importDialogVisible.value = false
uploadDisabled.value = false
const data = response.data
let text = '上传成功数量:' + data.createUsernames.length + ';'
for (let username of data.createUsernames) {
text += '< ' + username + ' >'
}
text += '更新成功数量:' + data.updateUsernames.length + ';'
for (const username of data.updateUsernames) {
text += '< ' + username + ' >'
}
text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';'
for (const username in data.failureUsernames) {
text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
}
ElMessageBox.alert(text)
}
// 文件数超出提示
const handleExceed = (): void => {
ElMessage.error('最多只能上传一个文件!')
}
// 上传错误提示
const excelUploadError = (): void => {
ElMessage.error('导入数据失败,请您重新上传!')
}
// ========== 初始化 ==========
onMounted(async () => {
await getTree()
await getPostOptions()
})
getList()
</script>
<template>
<div class="flex">
<el-card class="w-1/5 user" :gutter="12" shadow="always">
<template #header>
<div class="card-header">
<span>部门列表</span>
</div>
</template>
<el-tree
ref="treeRef"
node-key="id"
default-expand-all
:data="deptOptions"
:props="defaultProps"
:highlight-current="true"
:filter-method="filterNode"
:expand-on-click-node="false"
@node-click="handleDeptNodeClick"
/>
</el-card>
<!-- 搜索工作区 -->
<el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover">
<template #header>
<div class="card-header">
<span>{{ tableTitle }}</span>
</div>
</template>
<Search
:schema="allSchemas.searchSchema"
@search="setSearchParams"
@reset="setSearchParams"
ref="searchForm"
/>
<!-- 操作工具栏 -->
<div class="mb-10px">
<el-button type="primary" v-hasPermi="['system:user:create']" @click="handleAdd">
<Icon icon="el:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button>
<el-button v-hasPermi="['system:user:import']" @click="importDialogVisible = true">
<Icon icon="ep:upload" class="mr-5px" /> {{ t('action.import') }}
</el-button>
<el-button v-hasPermi="['system:user:export']" @click="handleExport">
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
</div>
<!-- 列表 -->
<Table
:columns="allSchemas.tableColumns"
:selection="false"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
@register="register"
>
<template #sex="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
</template>
<template #status="{ row }">
<el-switch
v-model="row.status"
:active-value="0"
:inactive-value="1"
@change="handleStatusChange(row)"
/>
</template>
<template #loginDate="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
<template #action="{ row }">
<el-button
link
type="primary"
v-hasPermi="['system:user:update']"
@click="handleUpdate(row)"
>
<Icon icon="ep:edit" class="mr-5px" /> {{ t('action.edit') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:user:update']"
@click="handleDetail(row)"
>
<Icon icon="ep:view" class="mr-5px" /> {{ t('action.detail') }}
</el-button>
<el-button
link
type="primary"
v-hasPermi="['system:user:delete']"
@click="handleDelete(row)"
>
<Icon icon="ep:delete" class="mr-5px" /> {{ t('action.del') }}
</el-button>
</template>
</Table>
</el-card>
</div>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:rules="rules"
:schema="allSchemas.formSchema"
ref="formRef"
>
<template #deptId>
<el-tree-select
node-key="id"
v-model="deptId"
:props="defaultProps"
:data="deptOptions"
check-strictly
/>
</template>
<template #postIds>
<el-select v-model="postIds" multiple placeholder="Select">
<el-option
v-for="item in postOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</template>
</Form>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailRef"
>
<template #deptId="{ row }">
<span>{{ row.dept.name }}</span>
</template>
<template #postIds="{ row }">
<span>{{ row.dept.name }}</span>
</template>
<template #sex="{ row }">
<DictTag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
</template>
<template #status="{ row }">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
<template #loginDate="{ row }">
<span>{{ dayjs(row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<el-button
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="loading"
@click="submitForm"
>
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
<Dialog
v-model="importDialogVisible"
:title="importDialogTitle"
:destroy-on-close="true"
maxHeight="350px"
>
<el-form class="drawer-multiColumn-form" label-width="150px">
<el-form-item label="模板下载 :">
<el-button type="primary" @click="handleImportTemp">
<Icon icon="ep:download" />
点击下载
</el-button>
</el-form-item>
<el-form-item label="文件上传 :">
<el-upload
ref="uploadRef"
:action="updateUrl + '?updateSupport=' + updateSupport"
:headers="uploadHeaders"
:drag="true"
:limit="1"
:multiple="true"
:show-file-list="true"
:disabled="uploadDisabled"
:before-upload="beforeExcelUpload"
:on-exceed="handleExceed"
:on-success="handleFileSuccess"
:on-error="excelUploadError"
:auto-upload="false"
accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
>
<Icon icon="ep:upload-filled" />
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div>
</template>
</el-upload>
</el-form-item>
<el-form-item label="是否更新已经存在的用户数据:">
<el-checkbox v-model="updateSupport" />
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitFileForm">
<Icon icon="ep:upload-filled" />
{{ t('action.save') }}
</el-button>
<el-button @click="importDialogVisible = false">{{ t('dialog.close') }}</el-button>
</template>
</Dialog>
</template>
<style scoped>
.user {
height: 900px;
max-height: 960px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@@ -0,0 +1,134 @@
import { reactive } from 'vue'
import { required } from '@/utils/formRules'
import { useI18n } from '@/hooks/web/useI18n'
import { DICT_TYPE } from '@/utils/dict'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
// 国际化
const { t } = useI18n()
// 表单校验
export const rules = reactive({
name: [required],
code: [required],
sort: [required],
status: [required]
})
// crudSchemas
const crudSchemas = reactive<CrudSchema[]>([
{
label: t('common.index'),
field: 'id',
type: 'index',
form: {
show: false
},
detail: {
show: false
}
},
{
label: '用户账号',
field: 'username',
search: {
show: true
}
},
{
label: '用户昵称',
field: 'nickname'
},
{
label: '用户邮箱',
field: 'email'
},
{
label: '手机号码',
field: 'mobile',
search: {
show: true
}
},
{
label: '用户性别',
field: 'sex',
dictType: DICT_TYPE.SYSTEM_USER_SEX
},
{
label: '部门',
field: 'deptId',
table: {
show: false
}
},
{
label: '岗位',
field: 'postIds',
table: {
show: false
}
},
{
label: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
search: {
show: true
}
},
{
label: '最后登录时间',
field: 'loginDate',
form: {
show: false
}
},
{
label: '最后登录IP',
field: 'loginIp',
table: {
show: false
},
form: {
show: false
}
},
{
label: t('form.remark'),
field: 'remark',
table: {
show: false
}
},
{
label: t('common.createTime'),
field: 'daterange',
table: {
show: false
},
form: {
show: false
},
detail: {
show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'daterange',
valueFormat: 'YYYY-MM-DD HH:mm:ss'
}
}
},
{
field: 'action',
width: '240px',
label: t('table.action'),
form: {
show: false
},
detail: {
show: false
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)