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 request from '@/config/axios'
import { List } from 'echarts'
export const getMenuListApi = (params: any): Promise<IResponse> => { export const getMenuListApi = (params: any): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/menus/', params }) return request.get({ url: '/vadmin/auth/menus/', params })
} }
export const delMenuListApi = (params: any): Promise<IResponse> => { export const delMenuListApi = (data: List): Promise<IResponse> => {
return request.delete({ url: '/vadmin/auth/menus/', params }) 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> => { export const getMenuTreeOptionsApi = (): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/menus/tree/options/' }) 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> => { export const getRoleListApi = (params: any): Promise<IResponse> => {
return request.get({ url: '/vadmin/auth/roles/', params }) 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"> <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 { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
import { componentMap } from './componentMap' import { componentMap } from './componentMap'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
@ -150,9 +150,23 @@ export default defineComponent({
const renderFormItemWrap = () => { const renderFormItemWrap = () => {
// hidden // hidden
const { schema = [], isCol } = unref(getProps) const { schema = [], isCol } = unref(getProps)
return schema 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) => { .map((item) => {
// Divider // Divider
const isDivider = item.component === '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 ( return (
<ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}> <ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>
{{ {{

View File

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

View File

@ -16,7 +16,7 @@ export const setTextPlaceholder = (schema: FormSchema): PlaceholderMoel => {
const selectMap = ['Select', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect'] const selectMap = ['Select', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect']
if (textMap.includes(schema?.component as string)) { if (textMap.includes(schema?.component as string)) {
return { return {
placeholder: t('common.inputText') placeholder: `请输入${schema.label}`
} }
} }
if (selectMap.includes(schema?.component as string)) { if (selectMap.includes(schema?.component as string)) {
@ -34,7 +34,7 @@ export const setTextPlaceholder = (schema: FormSchema): PlaceholderMoel => {
} }
} else { } else {
return { 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') { } else if (v.component && v.component !== 'Divider') {
const hasField = Reflect.has(model, v.field) 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 return model

View File

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

View File

@ -6,6 +6,7 @@ import { TableData } from '@/api/table/types'
import { useValidator } from '@/hooks/web/useValidator' import { useValidator } from '@/hooks/web/useValidator'
import { getMenuTreeOptionsApi } from '@/api/vadmin/auth/menu' import { getMenuTreeOptionsApi } from '@/api/vadmin/auth/menu'
import { ElButton, ElInput } from 'element-plus' import { ElButton, ElInput } from 'element-plus'
import { schema } from './menu.data'
const { required } = useValidator() 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({ const rules = reactive({
title: [required()], title: [required()],
disabled: [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"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' 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 { TableData } from '@/api/table/types'
import { useTable } from '@/hooks/web/useTable' import { useTable } from '@/hooks/web/useTable'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { ElButton, ElTag } from 'element-plus' import { ElButton } from 'element-plus'
import { h, ref, unref, reactive } from 'vue' import { ref, unref } from 'vue'
import { Dialog } from '@/components/Dialog' import { Dialog } from '@/components/Dialog'
import Write from './components/Write.vue' import Write from './components/Write.vue'
import { columns } from './components/menu.data'
const { t } = useI18n() 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>({ const { register, tableObject, methods } = useTable<TableData>({
getListApi: getMenuListApi, getListApi: getMenuListApi,
delListApi: delMenuListApi, delListApi: delMenuListApi,
@ -60,25 +31,31 @@ const { register, tableObject, methods } = useTable<TableData>({
const dialogVisible = ref(false) const dialogVisible = ref(false)
const dialogTitle = ref('') const dialogTitle = ref('')
const actionType = ref('')
const delLoading = ref(false) const delLoading = ref(false)
const actionType = ref('')
//
const AddAction = () => { const AddAction = () => {
dialogTitle.value = t('exampleDemo.add') dialogTitle.value = t('exampleDemo.add')
tableObject.currentRow = null tableObject.currentRow = null
dialogVisible.value = true 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 tableObject.currentRow = row
const { delListApi, getSelections } = methods dialogVisible.value = true
const selections = await getSelections() actionType.value = 'edit'
}
//
const delData = async (row: any) => {
tableObject.currentRow = row
const { delListApi } = methods
delLoading.value = true delLoading.value = true
await delListApi( await delListApi([row.id], false).finally(() => {
multiple ? selections.map((v) => v.id) : [tableObject.currentRow?.id as string],
multiple
).finally(() => {
delLoading.value = false delLoading.value = false
}) })
} }
@ -92,8 +69,17 @@ const save = async () => {
await write?.elFormRef?.validate(async (isValid) => { await write?.elFormRef?.validate(async (isValid) => {
if (isValid) { if (isValid) {
loading.value = true loading.value = true
const data = (await write?.getFormData()) as TableData const data = await write?.getFormData()
console.log('a', data) 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 loading.value = false
} }
}) })
@ -117,11 +103,19 @@ getList()
row-key="id" row-key="id"
@register="register" @register="register"
> >
<template #action="{}"> <template #title="{ row }">
<ElButton type="primary" text> {{ 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') }} {{ t('exampleDemo.edit') }}
</ElButton> </ElButton>
<ElButton type="danger" text> <ElButton type="danger" text size="small" @click="delData(row)">
{{ t('exampleDemo.del') }} {{ t('exampleDemo.del') }}
</ElButton> </ElButton>
</template> </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"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' import { Table } from '@/components/Table'
import { getRoleListApi } from '@/api/vadmin/auth/role' import {
import { TableData } from '@/api/table/types' getRoleListApi,
import { reactive } from 'vue' addRoleListApi,
delRoleListApi,
putRoleListApi,
getRoleApi
} from '@/api/vadmin/auth/role'
import { useTable } from '@/hooks/web/useTable' 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 { useI18n } from '@/hooks/web/useI18n'
import { ElButton } from 'element-plus'
const { t } = useI18n() const { t } = useI18n()
const columns = reactive<TableColumn[]>([ const { register, tableObject, methods } = useTable({
{
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>({
getListApi: getRoleListApi, getListApi: getRoleListApi,
delListApi: delRoleListApi,
response: { response: {
data: 'data', data: 'data',
count: 'count' 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 const { getList } = methods
getList() getList()
@ -70,6 +100,10 @@ getList()
<template> <template>
<ContentWrap> <ContentWrap>
<div class="mb-10px">
<ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
</div>
<Table <Table
v-model:limit="tableObject.limit" v-model:limit="tableObject.limit"
v-model:page="tableObject.page" v-model:page="tableObject.page"
@ -81,17 +115,29 @@ getList()
}" }"
@register="register" @register="register"
> >
<template #action="{}"> <template #action="{ row }">
<ElButton type="primary" text> <ElButton type="primary" text size="small" @click="updateAction(row)">
{{ t('exampleDemo.edit') }} {{ t('exampleDemo.edit') }}
</ElButton> </ElButton>
<ElButton type="success" text> <ElButton type="danger" text size="small" @click="delData(row)">
{{ t('exampleDemo.detail') }}
</ElButton>
<ElButton type="danger" text>
{{ t('exampleDemo.del') }} {{ t('exampleDemo.del') }}
</ElButton> </ElButton>
</template> </template>
</Table> </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> </ContentWrap>
</template> </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"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' import { Table } from '@/components/Table'
import { getTableListApi } from '@/api/table' import {
import { TableData } from '@/api/table/types' getUserListApi,
import { reactive } from 'vue' addUserListApi,
delUserListApi,
putUserListApi,
getUserApi
} from '@/api/vadmin/auth/user'
import { useTable } from '@/hooks/web/useTable' 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[]>([ const { t } = useI18n()
{
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 { register, tableObject, methods } = useTable<TableData>({ const { register, tableObject, methods } = useTable({
getListApi: getTableListApi, getListApi: getUserListApi,
delListApi: delUserListApi,
response: { response: {
list: 'list', data: 'data',
total: 'total' count: 'count'
}, },
props: { props: {
columns 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 const { getList } = methods
getList() getList()
@ -52,15 +96,40 @@ getList()
<template> <template>
<ContentWrap> <ContentWrap>
<div class="mb-10px">
<ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
</div>
<Table <Table
v-model:pageSize="tableObject.pageSize" v-model:limit="tableObject.limit"
v-model:currentPage="tableObject.currentPage" v-model:page="tableObject.page"
:data="tableObject.tableList" :data="tableObject.tableData"
:loading="tableObject.loading" :loading="tableObject.loading"
:selection="false"
:pagination="{ :pagination="{
total: tableObject.total total: tableObject.count
}" }"
@register="register" @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> </ContentWrap>
</template> </template>

View File

@ -24,6 +24,7 @@ declare global {
| 'InputPassword' | 'InputPassword'
| 'Editor' | 'Editor'
| 'TreeSelect' | 'TreeSelect'
| 'Tree'
declare type ColProps = { declare type ColProps = {
span?: number span?: number
@ -86,6 +87,8 @@ declare global {
value?: FormValueType value?: FormValueType
// 是否隐藏 // 是否隐藏
hidden?: boolean hidden?: boolean
// 是否显示
ifshow?: (values: Recordable) => boolean
// 远程加载下拉项 // 远程加载下拉项
api?: <T = any>() => AxiosPromise<T> 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 : 增删改查 # @desc : 增删改查
from typing import List from typing import List
from fastapi.encoders import jsonable_encoder
from sqlalchemy import select from sqlalchemy import select
from core.crud import DalBase from core.crud import DalBase
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -42,6 +43,43 @@ class RoleDal(DalBase):
def __init__(self, db: AsyncSession): def __init__(self, db: AsyncSession):
super(RoleDal, self).__init__(db, models.VadminRole, schemas.RoleSimpleOut) 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): async def get_role_menu_tree(self, role_id: int):
role = await self.get_data(role_id, options=[self.model.menus]) role = await self.get_data(role_id, options=[self.model.menus])
return [i.id for i in role.menus] return [i.id for i in role.menus]
@ -106,10 +144,12 @@ class MenuDal(DalBase):
return self.generate_router_tree(menus, roots) return self.generate_router_tree(menus, roots)
async def get_treeselect(self): 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) 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: 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="名称") name = Column(String(50), index=True, nullable=False, comment="名称")
role_key = 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="是否可") disabled = Column(Boolean, default=False, comment="是否禁")
index = Column(Integer, comment="排序") order = Column(Integer, comment="排序")
desc = Column(String(255), comment="描述") desc = Column(String(255), comment="描述")
is_admin = Column(Boolean, comment="是否为超级角色", default=False) is_admin = Column(Boolean, comment="是否为超级角色", default=False)

View File

@ -1,3 +1,3 @@
from .user import UserOut, UserUpdate, User, UserIn, UserSimpleOut, ResetPwd from .user import UserOut, UserUpdate, User, UserIn, UserSimpleOut, ResetPwd
from .role import Role, RoleOut, RoleIn, RoleSelectOut, RoleSimpleOut 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() 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): class TreeListOut(MenuSimpleOut):
children: List['TreeListOut'] = [] children: List['TreeListOut'] = []

View File

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

View File

@ -8,7 +8,7 @@
from typing import Optional from typing import Optional
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from utils.response import SuccessResponse from utils.response import SuccessResponse, ErrorResponse
from . import schemas, crud, models from . import schemas, crud, models
from core.dependencies import paging, id_list, Params from core.dependencies import paging, id_list, Params
from apps.vadmin.auth.utils.current import login_auth, Auth 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)) 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="获取角色信息") @app.get("/roles/{data_id}/", summary="获取角色信息")
async def get_role(data_id: int, auth: Auth = Depends(login_auth)): async def get_role(data_id: int, auth: Auth = Depends(login_auth)):
model = models.VadminRole model = models.VadminRole