This commit is contained in:
ktianc 2022-10-02 23:19:13 +08:00
parent 051ddca964
commit 331b233e60
25 changed files with 1111 additions and 334 deletions

View File

@ -1,13 +1,26 @@
import request from '@/config/axios'
import { List } from 'echarts'
export const getMenuListApi = (params: any): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/menus/', params })
}
export const delMenuListApi = (params: any): Promise<IResponse> => {
return request.delete({ url: '/vadmin/auth/menus/', params })
export const delMenuListApi = (data: List): Promise<IResponse> => {
return request.delete({ url: '/vadmin/auth/menus/', data })
}
export const addMenuListApi = (data: any): Promise<IResponse> => {
return request.post({ url: '/vadmin/auth/menus/', data })
}
export const putMenuListApi = (data: any): Promise<IResponse> => {
return request.put({ url: `/vadmin/auth/menus/${data.id}/`, data })
}
export const getMenuTreeOptionsApi = (): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/menus/tree/options/' })
}
export const getMenuRoleTreeOptionsApi = (): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/menus/role/tree/options/' })
}

View File

@ -3,3 +3,23 @@ import request from '@/config/axios'
export const getRoleListApi = (params: any): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/roles/', params })
}
export const addRoleListApi = (data: any): Promise<IResponse> => {
return request.post({ url: '/vadmin/auth/roles/', data })
}
export const delRoleListApi = (data: any): Promise<IResponse> => {
return request.delete({ url: '/vadmin/auth/roles/', data })
}
export const putRoleListApi = (data: any): Promise<IResponse> => {
return request.put({ url: `/vadmin/auth/roles/${data.id}/`, data })
}
export const getRoleApi = (dataId: number): Promise<IResponse> => {
return request.get({ url: `/vadmin/auth/roles/${dataId}/` })
}
export const getRoleOptionsApi = (): Promise<IResponse> => {
return request.get({ url: `/vadmin/auth/roles/options/` })
}

View File

@ -0,0 +1,21 @@
import request from '@/config/axios'
export const getUserListApi = (params: any): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/users/', params })
}
export const addUserListApi = (data: any): Promise<IResponse> => {
return request.post({ url: '/vadmin/auth/users/', data })
}
export const delUserListApi = (data: any): Promise<IResponse> => {
return request.delete({ url: '/vadmin/auth/users/', data })
}
export const putUserListApi = (data: any): Promise<IResponse> => {
return request.put({ url: `/vadmin/auth/users/${data.id}/`, data })
}
export const getUserApi = (dataId: number): Promise<IResponse> => {
return request.get({ url: `/vadmin/auth/users/${dataId}/` })
}

View File

@ -1,5 +1,5 @@
<script lang="tsx">
import { PropType, defineComponent, ref, computed, unref, watch, onMounted, Ref, isRef } from 'vue'
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
import { componentMap } from './componentMap'
import { propTypes } from '@/utils/propTypes'
@ -150,9 +150,23 @@ export default defineComponent({
const renderFormItemWrap = () => {
// hidden
const { schema = [], isCol } = unref(getProps)
return schema
.filter((v) => !v.hidden)
.filter((v) => {
if (v.hidden !== undefined) {
return !v.hidden
} else if (v.ifshow !== undefined) {
const show = v.ifshow(unref(formModel))
if (!show) {
if (v.value !== undefined) {
formModel.value[v.field] = v.value
} else {
formModel.value[v.field] = undefined
}
}
return show
}
return true
})
.map((item) => {
// Divider
const isDivider = item.component === 'Divider'
@ -207,14 +221,6 @@ export default defineComponent({
)
}
}
if (item?.componentProps?.placeholder === undefined) {
if (item.componentProps === undefined) {
item.componentProps = {}
}
if (item?.component === 'Input') {
item.componentProps.placeholder = `请输入${item.label}`
}
}
return (
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>
{{

View File

@ -17,7 +17,8 @@ import {
ElTransfer,
ElAutocomplete,
ElDivider,
ElTreeSelect
ElTreeSelect,
ElTree
} from 'element-plus'
import { InputPassword } from '@/components/InputPassword'
import { Editor } from '@/components/Editor'
@ -44,7 +45,8 @@ const componentMap: Recordable<Component, ComponentName> = {
RadioButton: ElRadioGroup,
InputPassword: InputPassword,
Editor: Editor,
TreeSelect: ElTreeSelect
TreeSelect: ElTreeSelect,
Tree: ElTree
}
export { componentMap }

View File

@ -16,7 +16,7 @@ export const setTextPlaceholder = (schema: FormSchema): PlaceholderMoel => {
const selectMap = ['Select', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect']
if (textMap.includes(schema?.component as string)) {
return {
placeholder: t('common.inputText')
placeholder: `请输入${schema.label}`
}
}
if (selectMap.includes(schema?.component as string)) {
@ -34,7 +34,7 @@ export const setTextPlaceholder = (schema: FormSchema): PlaceholderMoel => {
}
} else {
return {
placeholder: t('common.selectText')
placeholder: `请选择${schema.label}`
}
}
}
@ -121,7 +121,7 @@ export const initModel = (schema: FormSchema[], formModel: Recordable) => {
} else if (v.component && v.component !== 'Divider') {
const hasField = Reflect.has(model, v.field)
// 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值
model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : ''
model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : undefined
}
})
return model

View File

@ -1,6 +1,6 @@
import type { Form, FormExpose } from '@/components/Form'
import type { ElForm } from 'element-plus'
import { ref, unref, nextTick, Ref } from 'vue'
import { ref, unref, nextTick } from 'vue'
import type { FormProps } from '@/components/Form/src/types'
export const useForm = (props?: FormProps) => {
@ -23,7 +23,7 @@ export const useForm = (props?: FormProps) => {
await nextTick()
const form = unref(formRef)
if (!form) {
console.error('Form 没有注册。请使用注册方式进行注册')
console.error('Form 没有注册。请使用 register 方法进行注册')
}
return form
}

View File

@ -6,6 +6,7 @@ import { TableData } from '@/api/table/types'
import { useValidator } from '@/hooks/web/useValidator'
import { getMenuTreeOptionsApi } from '@/api/vadmin/auth/menu'
import { ElButton, ElInput } from 'element-plus'
import { schema } from './menu.data'
const { required } = useValidator()
@ -16,153 +17,6 @@ const props = defineProps({
}
})
const handleRadioChange = (item: string) => {
console.log(item)
}
const schema = reactive<FormSchema[]>([
{
field: 'parent_id',
label: '上级菜单',
colProps: {
span: 24
},
component: 'TreeSelect',
componentProps: {
style: {
width: '100%'
},
checkStrictly: true,
placeholder: '请选择上级菜单'
}
},
{
field: 'menu_type',
label: '菜单类型',
colProps: {
span: 24
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '目录',
value: '0'
},
{
label: '菜单',
value: '1'
},
{
label: '按钮',
value: '2'
}
],
onChange: handleRadioChange
},
value: '0'
},
{
field: 'icon',
label: '菜单图标',
colProps: {
span: 24
}
},
{
field: 'title',
label: '菜单名称',
component: 'Input',
colProps: {
span: 12
}
},
{
field: 'order',
label: '显示排序',
component: 'InputNumber',
colProps: {
span: 12
}
},
{
field: 'path',
label: '路由地址',
component: 'Input',
colProps: {
span: 12
}
},
{
field: 'component',
label: '组件路径',
component: 'Input',
colProps: {
span: 12
}
},
{
field: 'hidden',
label: '显示状态',
colProps: {
span: 12
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '显示',
value: true
},
{
label: '隐藏',
value: false
}
]
},
value: true
},
{
field: 'disabled',
label: '菜单状态',
colProps: {
span: 12
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '正常',
value: false
},
{
label: '停用',
value: true
}
]
},
value: false
},
{
field: 'perms',
label: '权限标识',
component: 'Input',
colProps: {
span: 24
},
hidden: true
}
])
const rules = reactive({
title: [required()],
disabled: [required()],

View File

@ -0,0 +1,199 @@
import { reactive } from 'vue'
export const columns = reactive<TableColumn[]>([
{
field: 'title',
label: '菜单名称',
width: '200px'
},
{
field: 'icon',
label: '图标',
width: '120px'
},
{
field: 'order',
label: '排序',
width: '120px'
},
{
field: 'menu_type',
label: '菜单类型',
width: '120px'
},
{
field: 'perms',
label: '权限标识',
width: '150px'
},
{
field: 'path',
label: '路由地址'
},
{
field: 'component',
label: '组件路径'
},
{
field: 'hidden',
label: '显示状态',
width: '120px'
},
{
field: 'disabled',
label: '菜单状态',
width: '120px'
},
{
field: 'action',
width: '200px',
label: '操作'
}
])
export const schema = reactive<FormSchema[]>([
{
field: 'parent_id',
label: '上级菜单',
colProps: {
span: 24
},
component: 'TreeSelect',
componentProps: {
style: {
width: '100%'
},
checkStrictly: true,
placeholder: '请选择上级菜单'
}
},
{
field: 'menu_type',
label: '菜单类型',
colProps: {
span: 24
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '目录',
value: '0'
},
{
label: '菜单',
value: '1'
},
{
label: '按钮',
value: '2'
}
]
},
value: '0'
},
{
field: 'icon',
label: '菜单图标',
colProps: {
span: 24
},
ifshow: (values) => values.menu_type !== '2'
},
{
field: 'title',
label: '菜单名称',
component: 'Input',
colProps: {
span: 12
}
},
{
field: 'order',
label: '显示排序',
component: 'InputNumber',
colProps: {
span: 12
}
},
{
field: 'path',
label: '路由地址',
component: 'Input',
colProps: {
span: 12
},
ifshow: (values) => values.menu_type !== '2'
},
{
field: 'component',
label: '组件路径',
component: 'Input',
colProps: {
span: 12
},
ifshow: (values) => values.menu_type !== '2'
},
{
field: 'hidden',
label: '显示状态',
colProps: {
span: 12
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '显示',
value: false
},
{
label: '隐藏',
value: true
}
]
},
value: false,
ifshow: (values) => values.menu_type !== '2'
},
{
field: 'disabled',
label: '菜单状态',
colProps: {
span: 12
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '正常',
value: false
},
{
label: '停用',
value: true
}
]
},
value: false,
ifshow: (values) => values.menu_type !== '2'
},
{
field: 'perms',
label: '权限标识',
component: 'Input',
colProps: {
span: 24
},
ifshow: (values) => values.menu_type !== '0'
}
])

View File

@ -1,52 +1,23 @@
<script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table'
import { getMenuListApi, delMenuListApi } from '@/api/vadmin/auth/menu'
import {
getMenuListApi,
delMenuListApi,
addMenuListApi,
putMenuListApi
} from '@/api/vadmin/auth/menu'
import { TableData } from '@/api/table/types'
import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n'
import { ElButton, ElTag } from 'element-plus'
import { h, ref, unref, reactive } from 'vue'
import { ElButton } from 'element-plus'
import { ref, unref } from 'vue'
import { Dialog } from '@/components/Dialog'
import Write from './components/Write.vue'
import { columns } from './components/menu.data'
const { t } = useI18n()
const columns = reactive<TableColumn[]>([
{
field: 'title',
label: '菜单名称'
},
{
field: 'icon',
label: '图标'
},
{
field: 'order',
label: '排序'
},
{
field: 'menu_type',
label: '菜单类型'
},
{
field: 'perms',
label: '权限标识'
},
{
field: 'component',
label: '组件路径'
},
{
field: 'action',
width: '260px',
label: t('tableDemo.action'),
form: {
show: false
}
}
])
const { register, tableObject, methods } = useTable<TableData>({
getListApi: getMenuListApi,
delListApi: delMenuListApi,
@ -60,25 +31,31 @@ const { register, tableObject, methods } = useTable<TableData>({
const dialogVisible = ref(false)
const dialogTitle = ref('')
const actionType = ref('')
const delLoading = ref(false)
const actionType = ref('')
//
const AddAction = () => {
dialogTitle.value = t('exampleDemo.add')
tableObject.currentRow = null
dialogVisible.value = true
actionType.value = ''
actionType.value = 'add'
}
const delData = async (row: TableData | null, multiple: boolean) => {
//
const updateAction = (row: any) => {
dialogTitle.value = '编辑'
tableObject.currentRow = row
const { delListApi, getSelections } = methods
const selections = await getSelections()
dialogVisible.value = true
actionType.value = 'edit'
}
//
const delData = async (row: any) => {
tableObject.currentRow = row
const { delListApi } = methods
delLoading.value = true
await delListApi(
multiple ? selections.map((v) => v.id) : [tableObject.currentRow?.id as string],
multiple
).finally(() => {
await delListApi([row.id], false).finally(() => {
delLoading.value = false
})
}
@ -92,8 +69,17 @@ const save = async () => {
await write?.elFormRef?.validate(async (isValid) => {
if (isValid) {
loading.value = true
const data = (await write?.getFormData()) as TableData
console.log('a', data)
const data = await write?.getFormData()
const res = ref({})
if (actionType.value === 'add') {
res.value = await addMenuListApi(data)
} else if (actionType.value === 'edit') {
res.value = await putMenuListApi(data)
}
if (res.value) {
dialogVisible.value = false
getList()
}
loading.value = false
}
})
@ -117,11 +103,19 @@ getList()
row-key="id"
@register="register"
>
<template #action="{}">
<ElButton type="primary" text>
<template #title="{ row }">
{{ t(row.title) }}
</template>
<template #icon="{ row }">
<div v-if="row.icon">
<Icon :icon="row.icon" />
</div>
</template>
<template #action="{ row }">
<ElButton type="primary" text size="small" @click="updateAction(row)">
{{ t('exampleDemo.edit') }}
</ElButton>
<ElButton type="danger" text>
<ElButton type="danger" text size="small" @click="delData(row)">
{{ t('exampleDemo.del') }}
</ElButton>
</template>

View File

@ -0,0 +1,94 @@
<script setup lang="ts">
import { Form } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { PropType, reactive, watch, ref } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { getMenuRoleTreeOptionsApi } from '@/api/vadmin/auth/menu'
import { schema } from './role.data'
import { ElTree } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
const { required } = useValidator()
const { t } = useI18n()
const props = defineProps({
currentRow: {
type: Object as PropType<Nullable<any>>,
default: () => null
},
defaultCheckedKeys: {
type: Array as PropType<number[]>,
default: () => []
}
})
const rules = reactive({
name: [required()],
role_key: [required()],
order: [required()]
})
const { register, methods, elFormRef } = useForm({
schema: schema
})
watch(
() => props.currentRow,
(currentRow) => {
if (!currentRow) return
const { setValues } = methods
setValues(currentRow)
},
{
deep: true,
immediate: true
}
)
const defaultProps = {
children: 'children',
label: 'label'
}
let data = ref([])
const getMenuRoleTreeOptions = async () => {
const res = await getMenuRoleTreeOptionsApi()
if (res) {
data.value = res.data
}
}
getMenuRoleTreeOptions()
const treeRef = ref<InstanceType<typeof ElTree>>()
const getTreeCheckedKeys = () => {
return treeRef.value!.getCheckedKeys(false)
}
defineExpose({
elFormRef,
getFormData: methods.getFormData,
getTreeCheckedKeys: getTreeCheckedKeys
})
</script>
<template>
<Form :rules="rules" @register="register">
<template #menu_ids>
<ElTree
ref="treeRef"
:data="data"
show-checkbox
node-key="value"
:props="defaultProps"
:default-checked-keys="defaultCheckedKeys"
>
<template #default="{ node }">
<span>{{ t(node.label) }}</span>
</template>
</ElTree>
</template>
</Form>
</template>

View File

@ -0,0 +1,127 @@
import { reactive } from 'vue'
export const columns = reactive<TableColumn[]>([
{
field: 'id',
label: '角色编号'
},
{
field: 'name',
label: '角色名称'
},
{
field: 'role_key',
label: '权限字符'
},
{
field: 'order',
label: '显示顺序'
},
{
field: 'disabled',
label: '角色状态'
},
{
field: 'is_admin',
label: '最高权限'
},
{
field: 'create_datetime',
label: '创建时间'
},
{
field: 'action',
width: '260px',
label: '操作'
}
])
export const schema = reactive<FormSchema[]>([
{
field: 'name',
label: '角色名称',
colProps: {
span: 24
},
component: 'Input'
},
{
field: 'role_key',
label: '权限字符',
colProps: {
span: 24
},
component: 'Input'
},
{
field: 'order',
label: '显示排序',
colProps: {
span: 24
},
component: 'InputNumber'
},
{
field: 'disabled',
label: '角色状态',
colProps: {
span: 24
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '正常',
value: false
},
{
label: '禁用',
value: true
}
]
},
value: false
},
{
field: 'is_admin',
label: '最高权限',
colProps: {
span: 24
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '使用',
value: true
},
{
label: '不使用',
value: false
}
]
},
value: false
},
{
field: 'desc',
label: '描述',
colProps: {
span: 24
},
component: 'Input'
},
{
field: 'menu_ids',
label: '菜单权限',
colProps: {
span: 24
}
}
])

View File

@ -1,59 +1,26 @@
<script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table'
import { getRoleListApi } from '@/api/vadmin/auth/role'
import { TableData } from '@/api/table/types'
import { reactive } from 'vue'
import {
getRoleListApi,
addRoleListApi,
delRoleListApi,
putRoleListApi,
getRoleApi
} from '@/api/vadmin/auth/role'
import { useTable } from '@/hooks/web/useTable'
import { ElButton, ElMessage } from 'element-plus'
import { columns } from './components/role.data'
import { ref, unref } from 'vue'
import Write from './components/Write.vue'
import { Dialog } from '@/components/Dialog'
import { useI18n } from '@/hooks/web/useI18n'
import { ElButton } from 'element-plus'
const { t } = useI18n()
const columns = reactive<TableColumn[]>([
{
field: 'id',
label: '角色编号'
},
{
field: 'name',
label: '角色名称'
},
{
field: 'role_key',
label: '权限字符'
},
{
field: 'index',
label: '显示顺序'
},
{
field: 'is_active',
label: '状态'
},
{
field: 'is_admin',
label: '最高权限'
},
{
field: 'create_datetime',
label: '创建时间'
},
{
field: 'action',
width: '260px',
label: t('tableDemo.action'),
form: {
show: false
},
detail: {
show: false
}
}
])
const { register, tableObject, methods } = useTable<TableData>({
const { register, tableObject, methods } = useTable({
getListApi: getRoleListApi,
delListApi: delRoleListApi,
response: {
data: 'data',
count: 'count'
@ -63,6 +30,69 @@ const { register, tableObject, methods } = useTable<TableData>({
}
})
const dialogVisible = ref(false)
const dialogTitle = ref('')
const actionType = ref('')
const loading = ref(false)
const defaultCheckedKeys = ref([])
//
const AddAction = () => {
dialogTitle.value = t('exampleDemo.add')
tableObject.currentRow = null
dialogVisible.value = true
actionType.value = 'add'
defaultCheckedKeys.value = []
}
//
const updateAction = async (row: any) => {
const res = await getRoleApi(row.id)
dialogTitle.value = '编辑'
tableObject.currentRow = res.data
defaultCheckedKeys.value = res.data.menus.map((item: any) => item.id)
dialogVisible.value = true
actionType.value = 'edit'
}
//
const delData = async (row: any) => {
tableObject.currentRow = row
const { delListApi } = methods
loading.value = true
await delListApi([row.id], false).finally(() => {
loading.value = false
})
}
const writeRef = ref<ComponentRef<typeof Write>>()
const save = async () => {
const write = unref(writeRef)
await write?.elFormRef?.validate(async (isValid) => {
if (isValid) {
loading.value = true
let data = await write?.getFormData()
if (!data) {
loading.value = false
return ElMessage.error('未获取到数据')
}
data.menu_ids = write?.getTreeCheckedKeys()
const res = ref({})
if (actionType.value === 'add') {
res.value = await addRoleListApi(data)
} else if (actionType.value === 'edit') {
res.value = await putRoleListApi(data)
}
if (res.value) {
dialogVisible.value = false
getList()
}
loading.value = false
}
})
}
const { getList } = methods
getList()
@ -70,6 +100,10 @@ getList()
<template>
<ContentWrap>
<div class="mb-10px">
<ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
</div>
<Table
v-model:limit="tableObject.limit"
v-model:page="tableObject.page"
@ -81,17 +115,29 @@ getList()
}"
@register="register"
>
<template #action="{}">
<ElButton type="primary" text>
<template #action="{ row }">
<ElButton type="primary" text size="small" @click="updateAction(row)">
{{ t('exampleDemo.edit') }}
</ElButton>
<ElButton type="success" text>
{{ t('exampleDemo.detail') }}
</ElButton>
<ElButton type="danger" text>
<ElButton type="danger" text size="small" @click="delData(row)">
{{ t('exampleDemo.del') }}
</ElButton>
</template>
</Table>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
<Write
ref="writeRef"
:current-row="tableObject.currentRow"
:default-checked-keys="defaultCheckedKeys"
/>
<template #footer>
<ElButton type="primary" :loading="loading" @click="save">
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</template>
</Dialog>
</ContentWrap>
</template>

View File

@ -0,0 +1,66 @@
<script setup lang="ts">
import { Form } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { PropType, reactive, watch } from 'vue'
import { useValidator } from '@/hooks/web/useValidator'
import { schema } from './user.data'
import { getRoleOptionsApi } from '@/api/vadmin/auth/role'
const { required } = useValidator()
const props = defineProps({
currentRow: {
type: Object as PropType<Nullable<any>>,
default: () => null
}
})
const rules = reactive({
name: [required()],
role_key: [required()],
order: [required()]
})
const { register, methods, elFormRef } = useForm({
schema: schema
})
watch(
() => props.currentRow,
(currentRow) => {
if (!currentRow) return
const { setValues } = methods
setValues(currentRow)
},
{
deep: true,
immediate: true
}
)
const getRoleOptions = async () => {
const res = await getRoleOptionsApi()
console.log('111111', res)
if (res) {
const { setSchema } = methods
setSchema([
{
field: 'role_ids',
path: 'componentProps.options',
value: res.data
}
])
}
}
// getRoleOptions()
defineExpose({
elFormRef,
getFormData: methods.getFormData
})
</script>
<template>
<Form :rules="rules" @register="register" />
</template>

View File

@ -0,0 +1,170 @@
import { reactive } from 'vue'
export const columns = reactive<TableColumn[]>([
{
field: 'id',
label: '用户编号'
},
{
field: 'name',
label: '姓名'
},
{
field: 'nickname',
label: '昵称'
},
{
field: 'telephone',
label: '手机号'
},
{
field: 'gender',
label: '性别'
},
{
field: 'is_active',
label: '是否可用'
},
{
field: 'last_login',
label: '最近一次登录时间'
},
{
field: 'create_datetime',
label: '创建时间'
},
{
field: 'action',
width: '260px',
label: '操作'
}
])
export const schema = reactive<FormSchema[]>([
{
field: 'name',
label: '用户名称',
colProps: {
span: 12
},
component: 'Input'
},
{
field: 'telephone',
label: '手机号码',
colProps: {
span: 12
},
component: 'Input'
},
{
field: 'nickname',
label: '用户昵称',
colProps: {
span: 12
},
component: 'Input'
},
{
field: 'password',
label: '用户密码',
colProps: {
span: 12
},
component: 'InputPassword',
componentProps: {
style: {
width: '100%'
}
}
},
{
field: 'gender',
label: '性别',
colProps: {
span: 12
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '男',
value: '0'
},
{
label: '女',
value: '1'
}
]
},
value: '0'
},
{
field: 'is_active',
label: '状态',
colProps: {
span: 12
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '正常',
value: true
},
{
label: '停用',
value: false
}
]
},
value: true
},
{
field: 'role_ids',
label: '角色',
colProps: {
span: 24
},
component: 'Select',
componentProps: {
style: {
width: '100%'
},
// optionsAlias: {
// labelField: 'name',
// valueField: 'id'
// },
options: [
{
value: 'Option1',
label: 'Option1'
},
{
value: 'Option2',
label: 'Option2',
disabled: true
},
{
value: 'Option3',
label: 'Option3'
},
{
value: 'Option4',
label: 'Option4'
},
{
value: 'Option5',
label: 'Option5'
}
]
},
value: ''
}
])

View File

@ -1,50 +1,94 @@
<script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table'
import { getTableListApi } from '@/api/table'
import { TableData } from '@/api/table/types'
import { reactive } from 'vue'
import {
getUserListApi,
addUserListApi,
delUserListApi,
putUserListApi,
getUserApi
} from '@/api/vadmin/auth/user'
import { useTable } from '@/hooks/web/useTable'
import { columns } from './components/user.data'
import { ref, unref } from 'vue'
import Write from './components/Write.vue'
import { Dialog } from '@/components/Dialog'
import { ElButton, ElMessage } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
const columns = reactive<TableColumn[]>([
{
field: 'index',
label: 'index',
type: 'index'
},
{
field: 'title',
label: 'title'
},
{
field: 'author',
label: 'author'
},
{
field: 'display_time',
label: 'displayTime'
},
{
field: 'pageviews',
label: 'pageviews'
},
{
field: 'action',
label: 'action'
}
])
const { t } = useI18n()
const { register, tableObject, methods } = useTable<TableData>({
getListApi: getTableListApi,
const { register, tableObject, methods } = useTable({
getListApi: getUserListApi,
delListApi: delUserListApi,
response: {
list: 'list',
total: 'total'
data: 'data',
count: 'count'
},
props: {
columns
}
})
const dialogVisible = ref(false)
const dialogTitle = ref('')
const actionType = ref('')
const loading = ref(false)
//
const AddAction = () => {
dialogTitle.value = t('exampleDemo.add')
tableObject.currentRow = null
dialogVisible.value = true
actionType.value = 'add'
}
//
const updateAction = async (row: any) => {
const res = await getUserApi(row.id)
dialogTitle.value = '编辑'
tableObject.currentRow = res.data
dialogVisible.value = true
actionType.value = 'edit'
}
//
const delData = async (row: any) => {
tableObject.currentRow = row
const { delListApi } = methods
loading.value = true
await delListApi([row.id], false).finally(() => {
loading.value = false
})
}
const writeRef = ref<ComponentRef<typeof Write>>()
const save = async () => {
const write = unref(writeRef)
await write?.elFormRef?.validate(async (isValid) => {
if (isValid) {
loading.value = true
let data = await write?.getFormData()
if (!data) {
loading.value = false
return ElMessage.error('未获取到数据')
}
const res = ref({})
if (actionType.value === 'add') {
res.value = await addUserListApi(data)
} else if (actionType.value === 'edit') {
res.value = await putUserListApi(data)
}
if (res.value) {
dialogVisible.value = false
getList()
}
loading.value = false
}
})
}
const { getList } = methods
getList()
@ -52,15 +96,40 @@ getList()
<template>
<ContentWrap>
<div class="mb-10px">
<ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
</div>
<Table
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
:data="tableObject.tableList"
v-model:limit="tableObject.limit"
v-model:page="tableObject.page"
:data="tableObject.tableData"
:loading="tableObject.loading"
:selection="false"
:pagination="{
total: tableObject.total
total: tableObject.count
}"
@register="register"
/>
>
<template #action="{ row }">
<ElButton type="primary" text size="small" @click="updateAction(row)">
{{ t('exampleDemo.edit') }}
</ElButton>
<ElButton type="danger" text size="small" @click="delData(row)">
{{ t('exampleDemo.del') }}
</ElButton>
</template>
</Table>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
<Write ref="writeRef" :current-row="tableObject.currentRow" />
<template #footer>
<ElButton type="primary" :loading="loading" @click="save">
{{ t('exampleDemo.save') }}
</ElButton>
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
</template>
</Dialog>
</ContentWrap>
</template>

View File

@ -24,6 +24,7 @@ declare global {
| 'InputPassword'
| 'Editor'
| 'TreeSelect'
| 'Tree'
declare type ColProps = {
span?: number
@ -86,6 +87,8 @@ declare global {
value?: FormValueType
// 是否隐藏
hidden?: boolean
// 是否显示
ifshow?: (values: Recordable) => boolean
// 远程加载下拉项
api?: <T = any>() => AxiosPromise<T>
}

View File

@ -0,0 +1,30 @@
"""update
Revision ID: 5947e4ed42db
Revises: d37b76a689c1
Create Date: 2022-10-01 16:52:06.752039
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '5947e4ed42db'
down_revision = 'd37b76a689c1'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('vadmin_auth_role', sa.Column('order', sa.Integer(), nullable=True, comment='排序'))
op.drop_column('vadmin_auth_role', 'index')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('vadmin_auth_role', sa.Column('index', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True, comment='排序'))
op.drop_column('vadmin_auth_role', 'order')
# ### end Alembic commands ###

View File

@ -0,0 +1,30 @@
"""update
Revision ID: ab5fb033599e
Revises: 5947e4ed42db
Create Date: 2022-10-02 22:40:45.967326
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'ab5fb033599e'
down_revision = '5947e4ed42db'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('vadmin_auth_role', sa.Column('disabled', sa.Boolean(), nullable=True, comment='是否禁用'))
op.drop_column('vadmin_auth_role', 'is_active')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('vadmin_auth_role', sa.Column('is_active', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True, comment='是否可用'))
op.drop_column('vadmin_auth_role', 'disabled')
# ### end Alembic commands ###

View File

@ -7,6 +7,7 @@
# @desc : 增删改查
from typing import List
from fastapi.encoders import jsonable_encoder
from sqlalchemy import select
from core.crud import DalBase
from sqlalchemy.ext.asyncio import AsyncSession
@ -42,6 +43,43 @@ class RoleDal(DalBase):
def __init__(self, db: AsyncSession):
super(RoleDal, self).__init__(db, models.VadminRole, schemas.RoleSimpleOut)
async def create_data(self, data: schemas.RoleIn, return_obj: bool = False, options: list = None, schema=None):
"""创建数据"""
obj = self.model(**data.dict(exclude={'menu_ids'}))
for data_id in data.menu_ids:
obj.menus.append(await MenuDal(db=self.db).get_data(data_id=data_id))
self.db.add(obj)
await self.db.flush()
await self.db.refresh(obj)
if options:
obj = await self.get_data(obj.id, options=options)
if return_obj:
return obj
if schema:
return schema.from_orm(obj).dict()
return self.out_dict(await self.get_data(obj.id))
async def put_data(self, data_id: int, data: schemas.RoleIn, return_obj: bool = False, options: list = None,
schema=None):
"""更新单个数据"""
obj = await self.get_data(data_id, options=[self.model.menus])
obj_dict = jsonable_encoder(data)
for key, value in obj_dict.items():
if key == "menu_ids":
if obj.menus:
obj.menus.clear()
for data_id in value:
obj.menus.append(await MenuDal(db=self.db).get_data(data_id=data_id))
continue
setattr(obj, key, value)
await self.db.flush()
await self.db.refresh(obj)
if return_obj:
return obj
if schema:
return schema.from_orm(obj).dict()
return self.out_dict(obj)
async def get_role_menu_tree(self, role_id: int):
role = await self.get_data(role_id, options=[self.model.menus])
return [i.id for i in role.menus]
@ -106,10 +144,12 @@ class MenuDal(DalBase):
return self.generate_router_tree(menus, roots)
async def get_treeselect(self):
"""获取菜单树列表信息"""
sql = select(self.model)
"""获取菜单树列表,角色添加菜单权限时使用"""
sql = select(self.model).where(self.model.disabled == False, self.model.menu_type != "2")
queryset = await self.db.execute(sql)
return [schemas.TreeselectOut.from_orm(i).dict() for i in queryset.scalars().all()]
menus = queryset.scalars().all()
roots = filter(lambda i: not i.parent_id, menus)
return self.generate_tree_options(menus, roots)
def generate_router_tree(self, menus: List[models.VadminMenu], nodes: filter) -> list:
"""

View File

@ -18,8 +18,8 @@ class VadminRole(BaseModel):
name = Column(String(50), index=True, nullable=False, comment="名称")
role_key = Column(String(50), index=True, nullable=False, comment="权限字符")
is_active = Column(Boolean, default=True, comment="是否可")
index = Column(Integer, comment="排序")
disabled = Column(Boolean, default=False, comment="是否禁")
order = Column(Integer, comment="排序")
desc = Column(String(255), comment="描述")
is_admin = Column(Boolean, comment="是否为超级角色", default=False)

View File

@ -1,3 +1,3 @@
from .user import UserOut, UserUpdate, User, UserIn, UserSimpleOut, ResetPwd
from .role import Role, RoleOut, RoleIn, RoleSelectOut, RoleSimpleOut
from .menu import Menu, MenuSimpleOut, RouterOut, Meta, TreeselectOut, TreeListOut
from .menu import Menu, MenuSimpleOut, RouterOut, Meta, TreeListOut

View File

@ -63,17 +63,6 @@ class RouterOut(BaseModel):
RouterOut.update_forward_refs()
# 采单树列表,选用权限展示使用
class TreeselectOut(BaseModel):
id: int
label: str = Field(alias='title')
order: Optional[int] = 1
parent_id: Optional[int] = None
class Config:
orm_mode = True
class TreeListOut(MenuSimpleOut):
children: List['TreeListOut'] = []

View File

@ -17,8 +17,8 @@ from .menu import MenuSimpleOut
class Role(BaseModel):
name: str
is_active: bool = True
index: Optional[int] = None
disabled: bool = False
order: Optional[int] = None
desc: Optional[str] = None
role_key: str
is_admin: bool = False
@ -41,14 +41,13 @@ class RoleOut(RoleSimpleOut):
class RoleIn(Role):
menus: Optional[List[int]] = []
menu_ids: Optional[List[int]] = []
class RoleSelectOut(BaseModel):
id: int
name: str
is_active: bool
role_key: str
disabled: bool
class Config:
orm_mode = True

View File

@ -8,7 +8,7 @@
from typing import Optional
from fastapi import APIRouter, Depends, Query
from utils.response import SuccessResponse
from utils.response import SuccessResponse, ErrorResponse
from . import schemas, crud, models
from core.dependencies import paging, id_list, Params
from apps.vadmin.auth.utils.current import login_auth, Auth
@ -69,6 +69,11 @@ async def put_role(data_id: int, data: schemas.RoleIn, auth: Auth = Depends(logi
return SuccessResponse(await crud.RoleDal(auth.db).put_data(data_id, data))
@app.get("/roles/options/", summary="获取角色选择项")
async def get_role_options(auth: Auth = Depends(login_auth)):
return SuccessResponse(await crud.RoleDal(auth.db).get_select_datas())
@app.get("/roles/{data_id}/", summary="获取角色信息")
async def get_role(data_id: int, auth: Auth = Depends(login_auth)):
model = models.VadminRole