feat: 部门管理

This commit is contained in:
kailong321200875 2023-08-05 14:13:42 +08:00
parent 5fc57bdb08
commit 28d0785be8
17 changed files with 655 additions and 60 deletions

View File

@ -14,36 +14,65 @@ for (let i = 0; i < 5; i++) {
// 部门名称
departmentName: citys[i],
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)',
children: [
{
// 部门名称
departmentName: '研发部',
id: toAnyString()
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
id: toAnyString(),
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '产品部',
id: toAnyString()
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
id: toAnyString(),
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '运营部',
id: toAnyString()
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
id: toAnyString(),
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '市场部',
id: toAnyString()
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
id: toAnyString(),
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '销售部',
id: toAnyString()
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
id: toAnyString(),
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '客服部',
id: toAnyString()
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
id: toAnyString(),
remark: '@cword(10, 15)'
}
]
})
@ -65,6 +94,21 @@ export default [
}
}
},
{
url: '/department/table/list',
method: 'get',
response: () => {
return {
data: {
code: code,
data: {
list: departmentList,
total: 5
}
}
}
}
},
{
url: '/department/users',
method: 'get',
@ -136,5 +180,40 @@ export default [
}
}
}
},
// 保存接口
{
url: '/department/save',
method: 'post',
timeout: 1000,
response: () => {
return {
data: {
code: code,
data: 'success'
}
}
}
},
// 删除接口
{
url: '/department/delete',
method: 'post',
response: ({ body }) => {
const ids = body.ids
if (!ids) {
return {
code: '500',
message: '请选择需要删除的数据'
}
} else {
return {
data: {
code: code,
data: 'success'
}
}
}
}
}
] as MockMethod[]

View File

@ -16,3 +16,15 @@ export const deleteUserByIdApi = (ids: string[] | number[]) => {
export const saveUserApi = (data: any) => {
return request.post({ url: '/department/user/save', data })
}
export const saveDepartmentApi = (data: any) => {
return request.post({ url: '/department/save', data })
}
export const deleteDepartmentApi = (ids: string[] | number[]) => {
return request.post({ url: '/department/delete', data: { ids } })
}
export const getDepartmentTableApi = (params: any) => {
return request.get({ url: '/department/table/list', params })
}

View File

@ -24,7 +24,7 @@ import { useRenderRadio } from './components/useRenderRadio'
import { useRenderCheckbox } from './components/useRenderCheckbox'
import { useDesign } from '@/hooks/web/useDesign'
import { findIndex } from '@/utils'
import { set } from 'lodash-es'
import { get, set } from 'lodash-es'
import { FormProps } from './types'
import {
FormSchema,
@ -319,18 +319,18 @@ export default defineComponent({
const Comp = () => {
// field
const fields = item.field.split('.')
// fieldsv-model
const vModel = fields.reduce((prev, next, index) => {
if (index === 0) {
return formModel.value[next]
const itemVal = computed({
get: () => {
return get(formModel.value, item.field)
},
set: (val) => {
set(formModel.value, item.field, val)
}
return prev[next]
}, {})
})
return (
<Com
vModel={vModel}
vModel={itemVal.value}
ref={(el: any) => setComponentRefMap(el, item.field)}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item)}

View File

@ -150,7 +150,11 @@ export const initModel = (schema: FormSchema[], formModel: Recordable) => {
// const hasField = Reflect.has(model, v.field)
const hasField = get(model, v.field)
// 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值
set(model, v.field, hasField ? get(model, v.field) : v.value !== void 0 ? v.value : undefined)
set(
model,
v.field,
hasField !== void 0 ? get(model, v.field) : v.value !== void 0 ? v.value : undefined
)
// model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : undefined
}
})

View File

@ -9,6 +9,7 @@ import { initModel } from '@/components/Form/src/helper'
import ActionButton from './components/ActionButton.vue'
import { SearchProps } from './types'
import { FormItemProp } from 'element-plus'
import { isObject, isEmptyVal } from '@/utils/is'
const props = defineProps({
// Form
@ -130,12 +131,22 @@ watch(
const filterModel = async () => {
const model = await getFormData()
unref(getProps).removeNoValueItem &&
Object.keys(model).forEach((key) => {
if (model[key] === void 0 || model[key] === '') {
delete model[key]
if (unref(getProps).removeNoValueItem) {
// 使reduce
return Object.keys(model).reduce((prev, next) => {
const value = model[next]
if (!isEmptyVal(value)) {
if (isObject(value)) {
if (Object.keys(value).length > 0) {
prev[next] = value
}
} else {
prev[next] = value
}
}
})
return prev
}, {})
}
return model
}

View File

@ -160,7 +160,8 @@ export default {
inputPassword: 'InputPassword',
sticky: 'Sticky',
treeTable: 'Tree table',
PicturePreview: 'Table Image Preview'
PicturePreview: 'Table Image Preview',
department: 'Department management'
},
permission: {
hasPermission: 'Please set the operation permission value'
@ -496,7 +497,12 @@ export default {
email: 'Email',
createTime: 'Create time',
// 所属部门
department: 'Department'
department: 'Department',
departmentName: 'Department name',
status: 'Status',
enable: 'Enable',
disable: 'Disable',
superiorDepartment: 'Superior department'
},
inputPasswordDemo: {
title: 'InputPassword',

View File

@ -160,7 +160,8 @@ export default {
inputPassword: '密码输入框',
sticky: '黏性',
treeTable: '树形表格',
PicturePreview: '表格图片预览'
PicturePreview: '表格图片预览',
department: '部门管理'
},
permission: {
hasPermission: '请设置操作权限值'
@ -488,7 +489,15 @@ export default {
email: '邮箱',
createTime: '创建时间',
// 所属部门
department: '所属部门'
department: '所属部门',
departmentName: '部门名称',
status: '状态',
// 启用
enable: '启用',
// 禁用
disable: '禁用',
// 上级部门
superiorDepartment: '上级部门'
},
inputPasswordDemo: {
title: '密码输入框',

View File

@ -519,7 +519,7 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
children: [
{
path: 'user',
component: () => import('@/views/Authorization/User.vue'),
component: () => import('@/views/Authorization/User/User.vue'),
name: 'User',
meta: {
title: t('router.user')
@ -532,6 +532,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
meta: {
title: t('router.role')
}
},
{
path: 'department',
component: () => import('@/views/Authorization/Department/Department.vue'),
name: 'Department',
meta: {
title: t('router.department')
}
}
]
}

View File

@ -108,3 +108,7 @@ export const isDark = (): boolean => {
export const isImgPath = (path: string): boolean => {
return /(https?:\/\/|data:image\/).*?\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path)
}
export const isEmptyVal = (val: any): boolean => {
return val === '' || val === null || val === undefined
}

View File

@ -0,0 +1,337 @@
<script setup lang="tsx">
import { ContentWrap } from '@/components/ContentWrap'
import { Search } from '@/components/Search'
import { Dialog } from '@/components/Dialog'
import { useI18n } from '@/hooks/web/useI18n'
import { ElButton, ElTag } from 'element-plus'
import { Table } from '@/components/Table'
import {
getDepartmentApi,
getDepartmentTableApi,
saveDepartmentApi,
deleteDepartmentApi
} from '@/api/department'
import { useTable } from '@/hooks/web/useTable'
import { TableData } from '@/api/table/types'
import { ref, unref, reactive } from 'vue'
import Write from './components/Write.vue'
import Detail from './components/Detail.vue'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const ids = ref<string[]>([])
const { tableRegister, tableState, tableMethods } = useTable({
fetchDataApi: async () => {
const { currentPage, pageSize } = tableState
const res = await getDepartmentTableApi({
pageIndex: unref(currentPage),
pageSize: unref(pageSize),
...unref(searchParams)
})
return {
list: res.data.list,
total: res.data.total
}
},
fetchDelApi: async () => {
const res = await deleteDepartmentApi(unref(ids))
return !!res
}
})
const { loading, dataList, total, currentPage, pageSize } = tableState
const { getList, getElTableExpose, delList } = tableMethods
const searchParams = ref({})
const setSearchParams = (params: any) => {
searchParams.value = params
getList()
}
const { t } = useI18n()
const crudSchemas = reactive<CrudSchema[]>([
{
field: 'selection',
search: {
hidden: true
},
form: {
hidden: true
},
detail: {
hidden: true
},
table: {
type: 'selection'
}
},
{
field: 'index',
label: t('tableDemo.index'),
type: 'index',
search: {
hidden: true
},
form: {
hidden: true
},
detail: {
hidden: true
}
},
{
field: 'id',
label: t('userDemo.departmentName'),
table: {
slots: {
default: (data: any) => {
return <>{data[0].row.departmentName}</>
}
}
},
form: {
component: 'TreeSelect',
componentProps: {
nodeKey: 'id',
props: {
label: 'departmentName'
}
},
optionApi: async () => {
const res = await getDepartmentApi()
return res.data.list
}
},
detail: {
slots: {
default: (data: any) => {
return <>{data.departmentName}</>
}
}
}
},
{
field: 'status',
label: t('userDemo.status'),
search: {
hidden: true
},
table: {
slots: {
default: (data: any) => {
const status = data[0].row.status
return (
<>
<ElTag type={status === 0 ? 'danger' : 'success'}>
{status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
</ElTag>
</>
)
}
}
},
form: {
component: 'Select',
componentProps: {
options: [
{
value: 0,
label: t('userDemo.disable')
},
{
value: 1,
label: t('userDemo.enable')
}
]
}
},
detail: {
slots: {
default: (data: any) => {
return (
<>
<ElTag type={data.status === 0 ? 'danger' : 'success'}>
{data.status === 1 ? t('userDemo.enable') : t('userDemo.disable')}
</ElTag>
</>
)
}
}
}
},
{
field: 'createTime',
label: t('tableDemo.displayTime'),
search: {
hidden: true
},
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
valueFormat: 'YYYY-MM-DD HH:mm:ss'
}
}
},
{
field: 'remark',
label: t('userDemo.remark'),
search: {
hidden: true
},
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 5
},
colProps: {
span: 24
}
},
detail: {
slots: {
default: (data: any) => {
return <>{data.remark}</>
}
}
}
},
{
field: 'action',
width: '260px',
label: t('tableDemo.action'),
search: {
hidden: true
},
form: {
hidden: true
},
detail: {
hidden: true
},
table: {
slots: {
default: (data: any) => {
return (
<>
<ElButton type="primary" onClick={() => action(data[0].row, 'edit')}>
{t('exampleDemo.edit')}
</ElButton>
<ElButton type="success" onClick={() => action(data[0].row, 'detail')}>
{t('exampleDemo.detail')}
</ElButton>
<ElButton type="danger" onClick={() => delData(data[0].row)}>
{t('exampleDemo.del')}
</ElButton>
</>
)
}
}
}
}
])
// @ts-ignore
const { allSchemas } = useCrudSchemas(crudSchemas)
const dialogVisible = ref(false)
const dialogTitle = ref('')
const currentRow = ref<TableData | null>(null)
const actionType = ref('')
const AddAction = () => {
dialogTitle.value = t('exampleDemo.add')
currentRow.value = null
dialogVisible.value = true
actionType.value = ''
}
const delLoading = ref(false)
const delData = async (row: TableData | null) => {
const elTableExpose = await getElTableExpose()
ids.value = row ? [row.id] : elTableExpose?.getSelectionRows().map((v: TableData) => v.id) || []
delLoading.value = true
await delList(unref(ids).length).finally(() => {
delLoading.value = false
})
}
const action = (row: TableData, type: string) => {
dialogTitle.value = t(type === 'edit' ? 'exampleDemo.edit' : 'exampleDemo.detail')
actionType.value = type
currentRow.value = row
dialogVisible.value = true
}
const writeRef = ref<ComponentRef<typeof Write>>()
const saveLoading = ref(false)
const save = async () => {
const write = unref(writeRef)
const formData = await write?.submit()
if (formData) {
saveLoading.value = true
const res = await saveDepartmentApi(formData)
.catch(() => {})
.finally(() => {
saveLoading.value = false
})
if (res) {
dialogVisible.value = false
currentPage.value = 1
getList()
}
}
}
</script>
<template>
<ContentWrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
<div class="mb-10px">
<ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
<ElButton :loading="delLoading" type="danger" @click="delData(null)">
{{ t('exampleDemo.del') }}
</ElButton>
</div>
<Table
v-model:pageSize="pageSize"
v-model:currentPage="currentPage"
:columns="allSchemas.tableColumns"
:data="dataList"
:loading="loading"
:pagination="{
total: total
}"
@register="tableRegister"
/>
</ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<Write
v-if="actionType !== 'detail'"
ref="writeRef"
:form-schema="allSchemas.formSchema"
:current-row="currentRow"
/>
<Detail
v-if="actionType === 'detail'"
:detail-schema="allSchemas.detailSchema"
:current-row="currentRow"
/>
<template #footer>
<ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</template>
</Dialog>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import { PropType } from 'vue'
import type { TableData } from '@/api/table/types'
import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
defineProps({
currentRow: {
type: Object as PropType<Nullable<TableData>>,
default: () => null
},
detailSchema: {
type: Array as PropType<DescriptionsSchema[]>,
default: () => []
}
})
</script>
<template>
<Descriptions :schema="detailSchema" :data="currentRow || {}" />
</template>

View File

@ -0,0 +1,61 @@
<script setup lang="ts">
import { Form, FormSchema } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { PropType, reactive, watch } from 'vue'
import { TableData } from '@/api/table/types'
import { useValidator } from '@/hooks/web/useValidator'
const { required } = useValidator()
const props = defineProps({
currentRow: {
type: Object as PropType<Nullable<TableData>>,
default: () => null
},
formSchema: {
type: Array as PropType<FormSchema[]>,
default: () => []
}
})
const rules = reactive({
id: [required()],
status: [required()],
createTime: [required()],
remark: [required()]
})
const { formRegister, formMethods } = useForm()
const { setValues, getFormData, getElFormExpose } = formMethods
const submit = async () => {
const elForm = await getElFormExpose()
const valid = await elForm?.validate().catch((err) => {
console.log(err)
})
if (valid) {
const formData = getFormData()
return formData
}
}
watch(
() => props.currentRow,
(currentRow) => {
if (!currentRow) return
setValues(currentRow)
},
{
deep: true,
immediate: true
}
)
defineExpose({
submit
})
</script>
<template>
<Form :rules="rules" @register="formRegister" :schema="formSchema" />
</template>

View File

@ -4,11 +4,11 @@ import { Search } from '@/components/Search'
import { Dialog } from '@/components/Dialog'
import { useI18n } from '@/hooks/web/useI18n'
import { ElButton, ElTag } from 'element-plus'
import { Table, TableColumn } from '@/components/Table'
import { Table } from '@/components/Table'
import { getTableListApi, saveTableApi, delTableListApi } from '@/api/table'
import { useTable } from '@/hooks/web/useTable'
import { TableData } from '@/api/table/types'
import { h, ref, unref, reactive } from 'vue'
import { ref, unref, reactive } from 'vue'
import Write from './components/Write.vue'
import Detail from './components/Detail.vue'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
@ -47,6 +47,9 @@ const { t } = useI18n()
const crudSchemas = reactive<CrudSchema[]>([
{
field: 'selection',
search: {
hidden: true
},
form: {
hidden: true
},
@ -61,6 +64,9 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'index',
label: t('tableDemo.index'),
type: 'index',
search: {
hidden: true
},
form: {
hidden: true
},
@ -86,11 +92,17 @@ const crudSchemas = reactive<CrudSchema[]>([
},
{
field: 'author',
label: t('tableDemo.author')
label: t('tableDemo.author'),
search: {
hidden: true
}
},
{
field: 'display_time',
label: t('tableDemo.displayTime'),
search: {
hidden: true
},
form: {
component: 'DatePicker',
componentProps: {
@ -102,19 +114,8 @@ const crudSchemas = reactive<CrudSchema[]>([
{
field: 'importance',
label: t('tableDemo.importance'),
formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
return h(
ElTag,
{
type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
},
() =>
cellValue === 1
? t('tableDemo.important')
: cellValue === 2
? t('tableDemo.good')
: t('tableDemo.commonly')
)
search: {
hidden: true
},
form: {
component: 'Select',
@ -161,6 +162,9 @@ const crudSchemas = reactive<CrudSchema[]>([
{
field: 'pageviews',
label: t('tableDemo.pageviews'),
search: {
hidden: true
},
form: {
component: 'InputNumber',
value: 0
@ -169,6 +173,9 @@ const crudSchemas = reactive<CrudSchema[]>([
{
field: 'content',
label: t('exampleDemo.content'),
search: {
hidden: true
},
table: {
show: false
},
@ -191,6 +198,9 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'action',
width: '260px',
label: t('tableDemo.action'),
search: {
hidden: true
},
form: {
hidden: true
},

View File

@ -3,11 +3,11 @@ import { ContentWrap } from '@/components/ContentWrap'
import { Search } from '@/components/Search'
import { useI18n } from '@/hooks/web/useI18n'
import { ElButton, ElTag } from 'element-plus'
import { Table, TableColumn } from '@/components/Table'
import { Table } from '@/components/Table'
import { getTableListApi, delTableListApi } from '@/api/table'
import { useTable } from '@/hooks/web/useTable'
import { TableData } from '@/api/table/types'
import { h, reactive, ref, unref } from 'vue'
import { reactive, ref, unref } from 'vue'
import { useRouter } from 'vue-router'
import { useEmitt } from '@/hooks/event/useEmitt'
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
@ -64,6 +64,9 @@ const { t } = useI18n()
const crudSchemas = reactive<CrudSchema[]>([
{
field: 'selection',
search: {
hidden: true
},
form: {
hidden: true
},
@ -78,6 +81,9 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'index',
label: t('tableDemo.index'),
type: 'index',
search: {
hidden: true
},
form: {
hidden: true
},
@ -103,11 +109,17 @@ const crudSchemas = reactive<CrudSchema[]>([
},
{
field: 'author',
label: t('tableDemo.author')
label: t('tableDemo.author'),
search: {
hidden: true
}
},
{
field: 'display_time',
label: t('tableDemo.displayTime'),
search: {
hidden: true
},
form: {
component: 'DatePicker',
componentProps: {
@ -119,19 +131,8 @@ const crudSchemas = reactive<CrudSchema[]>([
{
field: 'importance',
label: t('tableDemo.importance'),
formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
return h(
ElTag,
{
type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
},
() =>
cellValue === 1
? t('tableDemo.important')
: cellValue === 2
? t('tableDemo.good')
: t('tableDemo.commonly')
)
search: {
hidden: true
},
form: {
component: 'Select',
@ -154,11 +155,33 @@ const crudSchemas = reactive<CrudSchema[]>([
}
]
}
},
detail: {
slots: {
default: (data: any) => {
return (
<ElTag
type={
data.importance === 1 ? 'success' : data.importance === 2 ? 'warning' : 'danger'
}
>
{data.importance === 1
? t('tableDemo.important')
: data.importance === 2
? t('tableDemo.good')
: t('tableDemo.commonly')}
</ElTag>
)
}
}
}
},
{
field: 'pageviews',
label: t('tableDemo.pageviews'),
search: {
hidden: true
},
form: {
component: 'InputNumber',
value: 0
@ -167,9 +190,12 @@ const crudSchemas = reactive<CrudSchema[]>([
{
field: 'content',
label: t('exampleDemo.content'),
table: {
search: {
hidden: true
},
table: {
show: false
},
form: {
component: 'Editor',
colProps: {
@ -177,13 +203,21 @@ const crudSchemas = reactive<CrudSchema[]>([
}
},
detail: {
span: 24
span: 24,
slots: {
default: (data: any) => {
return <div innerHTML={data.content}></div>
}
}
}
},
{
field: 'action',
width: '260px',
label: t('tableDemo.action'),
search: {
hidden: true
},
form: {
hidden: true
},