Initial commit: One Pipe System
完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
661
src/views/system/menu/index.vue
Normal file
661
src/views/system/menu/index.vue
Normal file
@@ -0,0 +1,661 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="menu-page" id="table-full-screen">
|
||||
<!-- 搜索栏 -->
|
||||
<ArtSearchBar
|
||||
v-model:filter="formFilters"
|
||||
:items="formItems"
|
||||
:showExpand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader
|
||||
:columnList="columnOptions"
|
||||
:showZebra="false"
|
||||
v-model:columns="columnChecks"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<!-- 按钮权限:后端控制模式,使用自定义指令 -->
|
||||
<ElButton v-auth="'add'" @click="showModel('menu', null, true)" v-ripple>
|
||||
添加菜单
|
||||
</ElButton>
|
||||
<ElButton @click="toggleExpand" v-ripple>
|
||||
{{ isExpanded ? '收起' : '展开' }}
|
||||
</ElButton>
|
||||
<!-- 按钮权限:前端控制模式,使用 hasAuth 方法 -->
|
||||
<!-- <ElButton v-if="hasAuth('B_CODE1')" @click="showModel('menu', null, true)" v-ripple>
|
||||
添加菜单
|
||||
</ElButton> -->
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
rowKey="path"
|
||||
ref="tableRef"
|
||||
:loading="loading"
|
||||
:data="filteredTableData"
|
||||
:marginTop="10"
|
||||
:stripe="false"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<ElDialog :title="dialogTitle" v-model="dialogVisible" width="700px" align-center>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="85px">
|
||||
<ElFormItem label="菜单类型">
|
||||
<ElRadioGroup v-model="labelPosition" :disabled="disableMenuType">
|
||||
<ElRadioButton value="menu" label="menu">菜单</ElRadioButton>
|
||||
<ElRadioButton value="button" label="button">权限</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
|
||||
<template v-if="labelPosition === 'menu'">
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="菜单名称" prop="name">
|
||||
<ElInput v-model="form.name" placeholder="菜单名称"></ElInput>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="路由地址" prop="path">
|
||||
<ElInput v-model="form.path" placeholder="路由地址"></ElInput>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="权限标识" prop="label">
|
||||
<ElInput v-model="form.label" placeholder="权限标识"></ElInput>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="图标" prop="icon">
|
||||
<ArtIconSelector :iconType="iconType" :defaultIcon="form.icon" width="229px" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="菜单排序" prop="sort" style="width: 100%">
|
||||
<ElInputNumber
|
||||
v-model="form.sort"
|
||||
style="width: 100%"
|
||||
@change="handleChange"
|
||||
:min="1"
|
||||
controls-position="right"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="外部链接" prop="link">
|
||||
<ElInput
|
||||
v-model="form.link"
|
||||
placeholder="外部链接/内嵌地址(https://www.baidu.com)"
|
||||
></ElInput>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="5">
|
||||
<ElFormItem label="是否启用" prop="isEnable">
|
||||
<ElSwitch v-model="form.isEnable"></ElSwitch>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="5">
|
||||
<ElFormItem label="页面缓存" prop="keepAlive">
|
||||
<ElSwitch v-model="form.keepAlive"></ElSwitch>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="5">
|
||||
<ElFormItem label="是否显示" prop="isHidden">
|
||||
<ElSwitch v-model="form.isHidden"></ElSwitch>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="5">
|
||||
<ElFormItem label="是否内嵌" prop="isMenu">
|
||||
<ElSwitch v-model="form.isIframe"></ElSwitch>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</template>
|
||||
|
||||
<template v-if="labelPosition === 'button'">
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="权限名称" prop="authName">
|
||||
<ElInput v-model="form.authName" placeholder="权限名称"></ElInput>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="权限标识" prop="authLabel">
|
||||
<ElInput v-model="form.authLabel" placeholder="权限标识"></ElInput>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="权限排序" prop="authSort" style="width: 100%">
|
||||
<ElInputNumber
|
||||
v-model="form.authSort"
|
||||
style="width: 100%"
|
||||
@change="handleChange"
|
||||
:min="1"
|
||||
controls-position="right"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</template>
|
||||
</ElForm>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取 消</ElButton>
|
||||
<ElButton type="primary" @click="submitForm()">确 定</ElButton>
|
||||
</span>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMenuStore } from '@/store/modules/menu'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { IconTypeEnum } from '@/enums/appEnum'
|
||||
import { formatMenuTitle } from '@/router/utils/utils'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { ElPopover, ElButton } from 'element-plus'
|
||||
import { AppRouteRecord } from '@/types/router'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { SearchFormItem } from '@/types'
|
||||
|
||||
defineOptions({ name: 'Menus' })
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
|
||||
const { menuList } = storeToRefs(useMenuStore())
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
// 定义表单搜索初始值
|
||||
const initialSearchState = {
|
||||
name: '',
|
||||
route: ''
|
||||
}
|
||||
|
||||
// 响应式表单数据
|
||||
const formFilters = reactive({ ...initialSearchState })
|
||||
|
||||
// 增加实际应用的搜索条件状态
|
||||
const appliedFilters = reactive({ ...initialSearchState })
|
||||
|
||||
// 重置表单
|
||||
const handleReset = () => {
|
||||
Object.assign(formFilters, { ...initialSearchState })
|
||||
Object.assign(appliedFilters, { ...initialSearchState })
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
// 将当前输入的筛选条件应用到实际搜索
|
||||
Object.assign(appliedFilters, { ...formFilters })
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 表单配置项
|
||||
const formItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '菜单名称',
|
||||
prop: 'name',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '路由地址',
|
||||
prop: 'route',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: '勾选', type: 'selection' },
|
||||
{ label: '用户名', prop: 'avatar' },
|
||||
{ label: '手机号', prop: 'mobile' },
|
||||
{ label: '性别', prop: 'sex' },
|
||||
{ label: '部门', prop: 'dep' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '创建日期', prop: 'create_time' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
|
||||
// 构建菜单类型标签
|
||||
const buildMenuTypeTag = (row: AppRouteRecord) => {
|
||||
if (row.children && row.children.length > 0) {
|
||||
return 'info'
|
||||
} else if (row.meta?.link && row.meta?.isIframe) {
|
||||
return 'success'
|
||||
} else if (row.path) {
|
||||
return 'primary'
|
||||
} else if (row.meta?.link) {
|
||||
return 'warning'
|
||||
}
|
||||
}
|
||||
|
||||
// 构建菜单类型文本
|
||||
const buildMenuTypeText = (row: AppRouteRecord) => {
|
||||
if (row.children && row.children.length > 0) {
|
||||
return '目录'
|
||||
} else if (row.meta?.link && row.meta?.isIframe) {
|
||||
return '内嵌'
|
||||
} else if (row.path) {
|
||||
return '菜单'
|
||||
} else if (row.meta?.link) {
|
||||
return '外链'
|
||||
}
|
||||
}
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'meta.title',
|
||||
label: '菜单名称',
|
||||
minWidth: 120,
|
||||
formatter: (row: AppRouteRecord) => {
|
||||
return formatMenuTitle(row.meta?.title)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'type',
|
||||
label: '菜单类型',
|
||||
formatter: (row: AppRouteRecord) => {
|
||||
return h(ElTag, { type: buildMenuTypeTag(row) }, () => buildMenuTypeText(row))
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'path',
|
||||
label: '路由',
|
||||
formatter: (row: AppRouteRecord) => {
|
||||
return row.meta?.link || row.path || ''
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'meta.authList',
|
||||
label: '可操作权限',
|
||||
formatter: (row: AppRouteRecord) => {
|
||||
return h(
|
||||
'div',
|
||||
{},
|
||||
row.meta.authList?.map((item: { title: string; auth_mark: string }, index: number) => {
|
||||
return h(
|
||||
ElPopover,
|
||||
{
|
||||
placement: 'top-start',
|
||||
title: '操作',
|
||||
width: 200,
|
||||
trigger: 'click',
|
||||
key: index
|
||||
},
|
||||
{
|
||||
default: () =>
|
||||
h('div', { style: 'margin: 0; text-align: right' }, [
|
||||
h(
|
||||
ElButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'primary',
|
||||
onClick: () => showModel('button', item)
|
||||
},
|
||||
{ default: () => '编辑' }
|
||||
),
|
||||
h(
|
||||
ElButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'danger',
|
||||
onClick: () => deleteAuth()
|
||||
},
|
||||
{ default: () => '删除' }
|
||||
)
|
||||
]),
|
||||
reference: () => h(ElButton, { class: 'small-btn' }, { default: () => item.title })
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'date',
|
||||
label: '编辑时间',
|
||||
formatter: () => '2022-3-12 12:00:00'
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '隐藏菜单',
|
||||
formatter: (row) => {
|
||||
return h(ElTag, { type: row.meta.isHide ? 'danger' : 'info' }, () =>
|
||||
row.meta.isHide ? '是' : '否'
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 180,
|
||||
formatter: (row: AppRouteRecord) => {
|
||||
return h('div', [
|
||||
hasAuth('B_CODE1') &&
|
||||
h(ArtButtonTable, {
|
||||
type: 'add',
|
||||
onClick: () => showModel('menu')
|
||||
}),
|
||||
hasAuth('B_CODE2') &&
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
onClick: () => showDialog('edit', row)
|
||||
}),
|
||||
hasAuth('B_CODE3') &&
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
onClick: () => deleteMenu()
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
const handleRefresh = () => {
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const form = reactive({
|
||||
// 菜单
|
||||
name: '',
|
||||
path: '',
|
||||
label: '',
|
||||
icon: '',
|
||||
isEnable: true,
|
||||
sort: 1,
|
||||
isMenu: true,
|
||||
keepAlive: true,
|
||||
isHidden: true,
|
||||
link: '',
|
||||
isIframe: false,
|
||||
// 权限 (修改这部分)
|
||||
authName: '',
|
||||
authLabel: '',
|
||||
authIcon: '',
|
||||
authSort: 1
|
||||
})
|
||||
const iconType = ref(IconTypeEnum.UNICODE)
|
||||
|
||||
const labelPosition = ref('menu')
|
||||
const rules = reactive<FormRules>({
|
||||
name: [
|
||||
{ required: true, message: '请输入菜单名称', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
path: [{ required: true, message: '请输入路由地址', trigger: 'blur' }],
|
||||
label: [{ required: true, message: '输入权限标识', trigger: 'blur' }],
|
||||
// 修改这部分
|
||||
authName: [{ required: true, message: '请输入权限名称', trigger: 'blur' }],
|
||||
authLabel: [{ required: true, message: '请输入权限权限标识', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const tableData = ref<AppRouteRecord[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
})
|
||||
|
||||
const getTableData = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
tableData.value = menuList.value
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 过滤后的表格数据
|
||||
const filteredTableData = computed(() => {
|
||||
// 递归搜索函数
|
||||
const searchMenu = (items: AppRouteRecord[]): AppRouteRecord[] => {
|
||||
return items.filter((item) => {
|
||||
// 获取搜索关键词,转换为小写并去除首尾空格
|
||||
const searchName = appliedFilters.name?.toLowerCase().trim() || ''
|
||||
const searchRoute = appliedFilters.route?.toLowerCase().trim() || ''
|
||||
|
||||
// 获取菜单标题和路径,确保它们存在
|
||||
const menuTitle = formatMenuTitle(item.meta?.title || '').toLowerCase()
|
||||
const menuPath = (item.path || '').toLowerCase()
|
||||
|
||||
// 使用 includes 进行模糊匹配
|
||||
const nameMatch = !searchName || menuTitle.includes(searchName)
|
||||
const routeMatch = !searchRoute || menuPath.includes(searchRoute)
|
||||
|
||||
// 如果有子菜单,递归搜索
|
||||
if (item.children && item.children.length > 0) {
|
||||
const matchedChildren = searchMenu(item.children)
|
||||
// 如果子菜单有匹配项,保留当前菜单
|
||||
if (matchedChildren.length > 0) {
|
||||
item.children = matchedChildren
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return nameMatch && routeMatch
|
||||
})
|
||||
}
|
||||
|
||||
return searchMenu(tableData.value)
|
||||
})
|
||||
|
||||
const isEdit = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const dialogTitle = computed(() => {
|
||||
const type = labelPosition.value === 'menu' ? '菜单' : '权限'
|
||||
return isEdit.value ? `编辑${type}` : `新建${type}`
|
||||
})
|
||||
|
||||
const showDialog = (type: string, row: AppRouteRecord) => {
|
||||
showModel('menu', row, true)
|
||||
}
|
||||
|
||||
const handleChange = () => {}
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
// const menuStore = useMenuStore()
|
||||
// const params =
|
||||
// labelPosition.value === 'menu'
|
||||
// ? {
|
||||
// title: form.name,
|
||||
// path: form.path,
|
||||
// name: form.label,
|
||||
// icon: form.icon,
|
||||
// sort: form.sort,
|
||||
// isEnable: form.isEnable,
|
||||
// isMenu: form.isMenu,
|
||||
// keepAlive: form.keepAlive,
|
||||
// isHidden: form.isHidden,
|
||||
// link: form.link
|
||||
// }
|
||||
// : {
|
||||
// title: form.authName,
|
||||
// name: form.authLabel,
|
||||
// icon: form.authIcon,
|
||||
// sort: form.authSort
|
||||
// }
|
||||
|
||||
if (isEdit.value) {
|
||||
// await menuStore.updateMenu(params)
|
||||
} else {
|
||||
// await menuStore.addMenu(params)
|
||||
}
|
||||
|
||||
ElMessage.success(`${isEdit.value ? '编辑' : '新增'}成功`)
|
||||
dialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const showModel = (type: string, row?: any, lock: boolean = false) => {
|
||||
dialogVisible.value = true
|
||||
labelPosition.value = type
|
||||
isEdit.value = false
|
||||
lockMenuType.value = lock
|
||||
resetForm()
|
||||
|
||||
if (row) {
|
||||
isEdit.value = true
|
||||
nextTick(() => {
|
||||
// 回显数据
|
||||
if (type === 'menu') {
|
||||
// 菜单数据回显
|
||||
form.name = formatMenuTitle(row.meta.title)
|
||||
form.path = row.path
|
||||
form.label = row.name
|
||||
form.icon = row.meta.icon
|
||||
form.sort = row.meta.sort || 1
|
||||
form.isMenu = row.meta.isMenu
|
||||
form.keepAlive = row.meta.keepAlive
|
||||
form.isHidden = row.meta.isHidden || true
|
||||
form.isEnable = row.meta.isEnable || true
|
||||
form.link = row.meta.link
|
||||
form.isIframe = row.meta.isIframe || false
|
||||
} else {
|
||||
// 权限按钮数据回显
|
||||
form.authName = row.title
|
||||
form.authLabel = row.auth_mark
|
||||
form.authIcon = row.icon || ''
|
||||
form.authSort = row.sort || 1
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
formRef.value?.resetFields()
|
||||
Object.assign(form, {
|
||||
// 菜单
|
||||
name: '',
|
||||
path: '',
|
||||
label: '',
|
||||
icon: '',
|
||||
sort: 1,
|
||||
isMenu: true,
|
||||
keepAlive: true,
|
||||
isHidden: true,
|
||||
link: '',
|
||||
isIframe: false,
|
||||
// 权限
|
||||
authName: '',
|
||||
authLabel: '',
|
||||
authIcon: '',
|
||||
authSort: 1
|
||||
})
|
||||
}
|
||||
|
||||
const deleteMenu = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该菜单吗?删除后无法恢复', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
ElMessage.success('删除成功')
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAuth = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该权限吗?删除后无法恢复', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
ElMessage.success('删除成功')
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修改计算属性,增加锁定控制参数
|
||||
const disableMenuType = computed(() => {
|
||||
// 编辑权限时锁定为权限类型
|
||||
if (isEdit.value && labelPosition.value === 'button') return true
|
||||
// 编辑菜单时锁定为菜单类型
|
||||
if (isEdit.value && labelPosition.value === 'menu') return true
|
||||
// 顶部添加菜单按钮时锁定为菜单类型
|
||||
if (!isEdit.value && labelPosition.value === 'menu' && lockMenuType.value) return true
|
||||
return false
|
||||
})
|
||||
|
||||
// 添加一个控制变量
|
||||
const lockMenuType = ref(false)
|
||||
|
||||
const isExpanded = ref(false)
|
||||
const tableRef = ref()
|
||||
|
||||
const toggleExpand = () => {
|
||||
isExpanded.value = !isExpanded.value
|
||||
nextTick(() => {
|
||||
if (tableRef.value) {
|
||||
tableRef.value[isExpanded.value ? 'expandAll' : 'collapseAll']()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-page {
|
||||
.svg-icon {
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
overflow: hidden;
|
||||
vertical-align: -8px;
|
||||
fill: currentcolor;
|
||||
}
|
||||
|
||||
:deep(.small-btn) {
|
||||
height: 30px !important;
|
||||
padding: 0 10px !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
5
src/views/system/nested/menu1/index.vue
Normal file
5
src/views/system/nested/menu1/index.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<h1>菜单-1</h1>
|
||||
</div>
|
||||
</template>
|
||||
5
src/views/system/nested/menu2/index.vue
Normal file
5
src/views/system/nested/menu2/index.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<h1>菜单-2-1</h1>
|
||||
</div>
|
||||
</template>
|
||||
5
src/views/system/nested/menu3/index.vue
Normal file
5
src/views/system/nested/menu3/index.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<h1>菜单-3-1</h1>
|
||||
</div>
|
||||
</template>
|
||||
5
src/views/system/nested/menu3/menu3-2/index.vue
Normal file
5
src/views/system/nested/menu3/menu3-2/index.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<h1>菜单-3-2-1</h1>
|
||||
</div>
|
||||
</template>
|
||||
489
src/views/system/permission/index.vue
Normal file
489
src/views/system/permission/index.vue
Normal file
@@ -0,0 +1,489 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="permission-page" id="table-full-screen">
|
||||
<!-- 搜索栏 -->
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader
|
||||
:columnList="columnOptions"
|
||||
v-model:columns="columnChecks"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton @click="showDialog('add')">新增权限</ElButton>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="ID"
|
||||
:data="permissionList"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:default-expand-all="false"
|
||||
:marginTop="10"
|
||||
:show-pagination="false"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增权限' : '编辑权限'"
|
||||
width="600px"
|
||||
align-center
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<ElFormItem label="权限名称" prop="perm_name">
|
||||
<ElInput v-model="form.perm_name" placeholder="请输入权限名称" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="权限标识" prop="perm_code">
|
||||
<ElInput v-model="form.perm_code" placeholder="请输入权限标识,如:user:add" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="权限类型" prop="perm_type">
|
||||
<ElSelect v-model="form.perm_type" placeholder="请选择权限类型" style="width: 100%">
|
||||
<ElOption
|
||||
v-for="option in PERMISSION_TYPE_OPTIONS"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="父级权限" prop="parent_id">
|
||||
<ElTreeSelect
|
||||
v-model="form.parent_id"
|
||||
:data="permissionTreeOptions"
|
||||
placeholder="请选择父级权限(可选)"
|
||||
clearable
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="form.perm_type === PermissionType.MENU" label="菜单路径" prop="url">
|
||||
<ElInput v-model="form.url" placeholder="请输入菜单路径,如:/system/user" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="适用端口" prop="platform">
|
||||
<ElSelect v-model="form.platform" placeholder="请选择适用端口" style="width: 100%">
|
||||
<ElOption label="全部" value="all" />
|
||||
<ElOption label="Web后台" value="web" />
|
||||
<ElOption label="H5端" value="h5" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="排序序号" prop="sort">
|
||||
<ElInputNumber v-model="form.sort" :min="0" :max="9999" style="width: 100%" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit" :loading="submitLoading"
|
||||
>提交</ElButton
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import {
|
||||
ElMessage,
|
||||
ElMessageBox,
|
||||
ElTag,
|
||||
ElSwitch,
|
||||
type FormInstance,
|
||||
type FormRules
|
||||
} from 'element-plus'
|
||||
import { PermissionService } from '@/api/modules'
|
||||
import type { Permission, CreatePermissionParams } from '@/types/api'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import {
|
||||
CommonStatus,
|
||||
getStatusText,
|
||||
PermissionType,
|
||||
PERMISSION_TYPE_OPTIONS,
|
||||
PERMISSION_TYPE_SELECT_OPTIONS,
|
||||
getPermissionTypeText,
|
||||
getPermissionTypeTag,
|
||||
STATUS_SELECT_OPTIONS
|
||||
} from '@/config/constants'
|
||||
|
||||
defineOptions({ name: 'Permission' })
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
perm_name: '',
|
||||
perm_code: '',
|
||||
perm_type: undefined as number | undefined,
|
||||
status: undefined as number | undefined
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({ ...initialSearchState })
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '权限名称',
|
||||
prop: 'perm_name',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入权限名称'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '权限标识',
|
||||
prop: 'perm_code',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入权限标识'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '权限类型',
|
||||
prop: 'perm_type',
|
||||
type: 'select',
|
||||
options: PERMISSION_TYPE_SELECT_OPTIONS,
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
options: STATUS_SELECT_OPTIONS,
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: '权限名称', prop: 'perm_name' },
|
||||
{ label: '权限标识', prop: 'perm_code' },
|
||||
{ label: '权限类型', prop: 'perm_type' },
|
||||
{ label: '菜单路径', prop: 'url' },
|
||||
{ label: '排序', prop: 'sort' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '创建时间', prop: 'CreatedAt' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
|
||||
// 权限列表
|
||||
const permissionList = ref<Permission[]>([])
|
||||
|
||||
const tableRef = ref()
|
||||
const dialogVisible = ref(false)
|
||||
const detailDialogVisible = ref(false)
|
||||
const dialogType = ref('add')
|
||||
const currentRow = ref<Permission | null>(null)
|
||||
const currentPermissionId = ref<number>(0)
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 表单引用和数据
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive<CreatePermissionParams>({
|
||||
perm_name: '',
|
||||
perm_code: '',
|
||||
perm_type: PermissionType.MENU,
|
||||
parent_id: undefined,
|
||||
url: '',
|
||||
platform: 'all',
|
||||
sort: 0
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<FormRules>({
|
||||
perm_name: [{ required: true, message: '请输入权限名称', trigger: 'blur' }],
|
||||
perm_code: [
|
||||
{ required: true, message: '请输入权限标识', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9:_-]+$/,
|
||||
message: '权限标识只能包含字母、数字、冒号、下划线和连字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
perm_type: [{ required: true, message: '请选择权限类型', trigger: 'change' }]
|
||||
})
|
||||
|
||||
// 权限树选项(用于选择父级权限)
|
||||
const permissionTreeOptions = ref<any[]>([])
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'perm_name',
|
||||
label: '权限名称',
|
||||
minWidth: 200
|
||||
},
|
||||
{
|
||||
prop: 'perm_code',
|
||||
label: '权限标识',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
prop: 'perm_type',
|
||||
label: '权限类型',
|
||||
width: 120,
|
||||
formatter: (row: any) => {
|
||||
return h(ElTag, { type: getPermissionTypeTag(row.perm_type) }, () =>
|
||||
getPermissionTypeText(row.perm_type)
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'url',
|
||||
label: '菜单路径',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
prop: 'sort',
|
||||
label: '排序',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
formatter: (row: any) => {
|
||||
return h(ElSwitch, {
|
||||
modelValue: row.status,
|
||||
activeValue: CommonStatus.ENABLED,
|
||||
inactiveValue: CommonStatus.DISABLED,
|
||||
activeText: getStatusText(CommonStatus.ENABLED),
|
||||
inactiveText: getStatusText(CommonStatus.DISABLED),
|
||||
inlinePrompt: true,
|
||||
'onUpdate:modelValue': (val: number) => handleStatusChange(row, val)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'CreatedAt',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: any) => formatDateTime(row.CreatedAt)
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 160,
|
||||
fixed: 'right',
|
||||
formatter: (row: any) => {
|
||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
onClick: () => showDialog('edit', row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
onClick: () => deletePermission(row)
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// 获取权限列表
|
||||
const getPermissionList = async () => {
|
||||
try {
|
||||
const response = await PermissionService.getPermissions(searchForm)
|
||||
if (response.code === 0) {
|
||||
permissionList.value = response.data.items || []
|
||||
// 构建权限树选项
|
||||
buildPermissionTreeOptions()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 构建权限树选项
|
||||
const buildPermissionTreeOptions = () => {
|
||||
const buildTree = (list: Permission[]): any[] => {
|
||||
return list.map((item) => ({
|
||||
value: item.ID,
|
||||
label: item.perm_name,
|
||||
children: item.children ? buildTree(item.children) : undefined
|
||||
}))
|
||||
}
|
||||
permissionTreeOptions.value = buildTree(permissionList.value)
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
getPermissionList()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, { ...initialSearchState })
|
||||
getPermissionList()
|
||||
}
|
||||
|
||||
// 刷新表格
|
||||
const handleRefresh = () => {
|
||||
getPermissionList()
|
||||
}
|
||||
|
||||
// 显示对话框
|
||||
const showDialog = (type: string, row?: any) => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = type
|
||||
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields()
|
||||
}
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
currentRow.value = row
|
||||
currentPermissionId.value = row.ID
|
||||
Object.assign(form, {
|
||||
perm_name: row.perm_name,
|
||||
perm_code: row.perm_code,
|
||||
perm_type: row.perm_type,
|
||||
parent_id: row.parent_id,
|
||||
url: row.url || '',
|
||||
platform: row.platform || 'all',
|
||||
sort: row.sort || 0
|
||||
})
|
||||
} else {
|
||||
currentPermissionId.value = 0
|
||||
resetForm()
|
||||
}
|
||||
}
|
||||
|
||||
// 删除权限
|
||||
const deletePermission = (row: Permission) => {
|
||||
// 检查是否有子权限
|
||||
if (row.children && row.children.length > 0) {
|
||||
ElMessage.warning('该权限下存在子权限,请先删除子权限')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(`确定删除权限 ${row.perm_name} 吗?`, '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await PermissionService.deletePermission(row.ID)
|
||||
ElMessage.success('删除成功')
|
||||
getPermissionList()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (dialogType.value === 'add') {
|
||||
await PermissionService.createPermission(form)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
await PermissionService.updatePermission(currentPermissionId.value, form)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
formRef.value.resetFields()
|
||||
getPermissionList()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(form, {
|
||||
perm_name: '',
|
||||
perm_code: '',
|
||||
perm_type: PermissionType.MENU,
|
||||
parent_id: undefined,
|
||||
url: '',
|
||||
platform: 'all',
|
||||
sort: 0
|
||||
})
|
||||
}
|
||||
|
||||
// 获取父级权限名称
|
||||
const getParentPermissionName = (parentId?: number | null) => {
|
||||
if (!parentId) return ''
|
||||
|
||||
const findPermission = (list: Permission[], id: number): string | null => {
|
||||
for (const item of list) {
|
||||
if (item.ID === id) return item.perm_name
|
||||
if (item.children) {
|
||||
const found = findPermission(item.children, id)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return findPermission(permissionList.value, parentId) || ''
|
||||
}
|
||||
|
||||
// 状态切换
|
||||
const handleStatusChange = async (row: any, newStatus: number) => {
|
||||
const oldStatus = row.status
|
||||
// 先更新UI
|
||||
row.status = newStatus
|
||||
try {
|
||||
await PermissionService.updatePermission(row.ID, { status: newStatus })
|
||||
ElMessage.success('状态切换成功')
|
||||
} catch (error) {
|
||||
// 切换失败,恢复原状态
|
||||
row.status = oldStatus
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取权限列表
|
||||
onMounted(() => {
|
||||
getPermissionList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.permission-page {
|
||||
// 权限管理页面样式
|
||||
}
|
||||
</style>
|
||||
514
src/views/system/role/index.vue
Normal file
514
src/views/system/role/index.vue
Normal file
@@ -0,0 +1,514 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="role-page" id="table-full-screen">
|
||||
<!-- 搜索栏 -->
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
:show-expand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
|
||||
<ElCard shadow="never" class="art-table-card">
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader
|
||||
:columnList="columnOptions"
|
||||
v-model:columns="columnChecks"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton @click="showDialog('add')">新增角色</ElButton>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<!-- 表格 -->
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
row-key="ID"
|
||||
:loading="loading"
|
||||
:data="roleList"
|
||||
:currentPage="pagination.page"
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增角色' : '编辑角色'"
|
||||
width="30%"
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<ElFormItem label="角色名称" prop="role_name">
|
||||
<ElInput v-model="form.role_name" placeholder="请输入角色名称" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="角色描述" prop="role_desc">
|
||||
<ElInput v-model="form.role_desc" type="textarea" :rows="3" placeholder="请输入角色描述" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="角色类型" prop="role_type">
|
||||
<ElSelect v-model="form.role_type" placeholder="请选择角色类型" style="width: 100%">
|
||||
<ElOption label="平台角色" :value="1" />
|
||||
<ElOption label="客户角色" :value="2" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="状态">
|
||||
<ElSwitch
|
||||
v-model="form.status"
|
||||
:active-value="CommonStatus.ENABLED"
|
||||
:inactive-value="CommonStatus.DISABLED"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit(formRef)" :loading="submitLoading">
|
||||
提交
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 分配权限对话框 -->
|
||||
<ElDialog
|
||||
v-model="permissionDialogVisible"
|
||||
title="分配权限"
|
||||
width="500px"
|
||||
>
|
||||
<ElCheckboxGroup v-model="selectedPermissions">
|
||||
<div v-for="permission in allPermissions" :key="permission.ID" style="margin-bottom: 12px;">
|
||||
<ElCheckbox :label="permission.ID">
|
||||
{{ permission.perm_name }}
|
||||
<ElTag :type="permission.perm_type === 1 ? '' : 'success'" size="small" style="margin-left: 8px;">
|
||||
{{ permission.perm_type === 1 ? '菜单' : '按钮' }}
|
||||
</ElTag>
|
||||
</ElCheckbox>
|
||||
</div>
|
||||
</ElCheckboxGroup>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="permissionDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleAssignPermissions" :loading="permissionSubmitLoading">
|
||||
提交
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { RoleService, PermissionService } from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElTag, ElCheckbox, ElCheckboxGroup, ElSwitch } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { PlatformRole, Permission } from '@/types/api'
|
||||
import type { SearchFormItem, SearchChangeParams } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { CommonStatus, getStatusText } from '@/config/constants'
|
||||
|
||||
defineOptions({ name: 'Role' })
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const permissionDialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const permissionSubmitLoading = ref(false)
|
||||
const tableRef = ref()
|
||||
const currentRoleId = ref<number>(0)
|
||||
const selectedPermissions = ref<number[]>([])
|
||||
const originalPermissions = ref<number[]>([]) // 保存原始权限,用于对比
|
||||
const allPermissions = ref<Permission[]>([])
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
role_name: ''
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({ ...initialSearchState })
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '角色名称',
|
||||
prop: 'role_name',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入角色名称'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 分页
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: 'ID', prop: 'ID' },
|
||||
{ label: '角色名称', prop: 'role_name' },
|
||||
{ label: '角色描述', prop: 'role_desc' },
|
||||
{ label: '角色类型', prop: 'role_type' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '创建时间', prop: 'CreatedAt' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
role_name: [
|
||||
{ required: true, message: '请输入角色名称', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
|
||||
],
|
||||
role_desc: [{ max: 200, message: '描述不能超过200个字符', trigger: 'blur' }],
|
||||
role_type: [{ required: true, message: '请选择角色类型', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const form = reactive<any>({
|
||||
id: 0,
|
||||
role_name: '',
|
||||
role_desc: '',
|
||||
role_type: 1,
|
||||
status: CommonStatus.ENABLED
|
||||
})
|
||||
|
||||
const roleList = ref<PlatformRole[]>([])
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'ID',
|
||||
label: 'ID',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: 'role_name',
|
||||
label: '角色名称',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'role_desc',
|
||||
label: '角色描述',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'role_type',
|
||||
label: '角色类型',
|
||||
width: 100,
|
||||
formatter: (row: any) => {
|
||||
return h(
|
||||
ElTag,
|
||||
{ type: row.role_type === 1 ? 'primary' : 'success' },
|
||||
() => (row.role_type === 1 ? '平台角色' : '客户角色')
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
formatter: (row: any) => {
|
||||
return h(ElSwitch, {
|
||||
modelValue: row.status,
|
||||
activeValue: CommonStatus.ENABLED,
|
||||
inactiveValue: CommonStatus.DISABLED,
|
||||
activeText: getStatusText(CommonStatus.ENABLED),
|
||||
inactiveText: getStatusText(CommonStatus.DISABLED),
|
||||
inlinePrompt: true,
|
||||
'onUpdate:modelValue': (val: number) => handleStatusChange(row, val)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'CreatedAt',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: any) => formatDateTime(row.CreatedAt)
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
formatter: (row: any) => {
|
||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||
h(ArtButtonTable, {
|
||||
icon: '',
|
||||
onClick: () => showPermissionDialog(row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
onClick: () => showDialog('edit', row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
onClick: () => deleteRole(row)
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
loadAllPermissions()
|
||||
})
|
||||
|
||||
// 加载所有权限列表
|
||||
const loadAllPermissions = async () => {
|
||||
try {
|
||||
const res = await PermissionService.getPermissions({ page: 1, page_size: 1000 })
|
||||
if (res.code === 0) {
|
||||
allPermissions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取权限列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示分配权限对话框
|
||||
const showPermissionDialog = async (row: PlatformRole) => {
|
||||
currentRoleId.value = row.ID
|
||||
selectedPermissions.value = []
|
||||
originalPermissions.value = []
|
||||
|
||||
try {
|
||||
// 每次打开对话框时重新加载最新的权限列表
|
||||
await loadAllPermissions()
|
||||
|
||||
// 加载当前角色的权限
|
||||
const res = await RoleService.getRolePermissions(row.ID)
|
||||
if (res.code === 0 || Array.isArray(res.data)) {
|
||||
// 提取权限ID数组
|
||||
const permissions = res.data || []
|
||||
if (Array.isArray(permissions) && permissions.length > 0) {
|
||||
// 如果返回的是权限对象数组,提取ID
|
||||
if (typeof permissions[0] === 'object' && 'ID' in permissions[0]) {
|
||||
selectedPermissions.value = permissions.map((perm: any) => perm.ID)
|
||||
} else {
|
||||
// 如果返回的是ID数组
|
||||
selectedPermissions.value = permissions
|
||||
}
|
||||
}
|
||||
// 保存原始权限,用于后续对比
|
||||
originalPermissions.value = [...selectedPermissions.value]
|
||||
// 数据加载完成后再打开对话框
|
||||
permissionDialogVisible.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取角色权限失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交分配权限
|
||||
const handleAssignPermissions = async () => {
|
||||
permissionSubmitLoading.value = true
|
||||
try {
|
||||
// 对比原始权限和当前选中的权限,找出需要新增和移除的权限
|
||||
const addedPermissions = selectedPermissions.value.filter(
|
||||
id => !originalPermissions.value.includes(id)
|
||||
)
|
||||
const removedPermissions = originalPermissions.value.filter(
|
||||
id => !selectedPermissions.value.includes(id)
|
||||
)
|
||||
|
||||
// 使用 Promise.all 并发执行新增和移除操作
|
||||
const promises: Promise<any>[] = []
|
||||
|
||||
// 如果有新增的权限,调用分配接口
|
||||
if (addedPermissions.length > 0) {
|
||||
promises.push(RoleService.assignPermissions(currentRoleId.value, addedPermissions))
|
||||
}
|
||||
|
||||
// 如果有移除的权限,调用移除接口
|
||||
if (removedPermissions.length > 0) {
|
||||
removedPermissions.forEach(permId => {
|
||||
promises.push(RoleService.removePermission(currentRoleId.value, permId))
|
||||
})
|
||||
}
|
||||
|
||||
// 等待所有操作完成
|
||||
if (promises.length > 0) {
|
||||
await Promise.all(promises)
|
||||
ElMessage.success('权限更新成功')
|
||||
} else {
|
||||
ElMessage.info('权限未发生变化')
|
||||
}
|
||||
|
||||
permissionDialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
permissionSubmitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取角色列表
|
||||
const getTableData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
role_name: searchForm.role_name || undefined
|
||||
}
|
||||
const res = await RoleService.getRoles(params)
|
||||
if (res.code === 0) {
|
||||
roleList.value = res.data.items || []
|
||||
pagination.total = res.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, { ...initialSearchState })
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 刷新表格
|
||||
const handleRefresh = () => {
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 处理表格分页变化
|
||||
const handleSizeChange = (newPageSize: number) => {
|
||||
pagination.pageSize = newPageSize
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (newCurrentPage: number) => {
|
||||
pagination.page = newCurrentPage
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const dialogType = ref('add')
|
||||
|
||||
// 显示新增/编辑对话框
|
||||
const showDialog = (type: string, row?: any) => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = type
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.ID
|
||||
form.role_name = row.role_name
|
||||
form.role_desc = row.role_desc
|
||||
form.role_type = row.role_type
|
||||
form.status = row.status
|
||||
} else {
|
||||
form.id = 0
|
||||
form.role_name = ''
|
||||
form.role_desc = ''
|
||||
form.role_type = 1
|
||||
form.status = CommonStatus.ENABLED
|
||||
}
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
const deleteRole = (row: any) => {
|
||||
ElMessageBox.confirm(`确定删除角色 ${row.role_name} 吗?`, '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await RoleService.deleteRole(row.ID)
|
||||
ElMessage.success('删除成功')
|
||||
getTableData()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
})
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
role_name: form.role_name,
|
||||
role_desc: form.role_desc,
|
||||
role_type: form.role_type,
|
||||
status: form.status
|
||||
}
|
||||
|
||||
if (dialogType.value === 'add') {
|
||||
await RoleService.createRole(data)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
await RoleService.updateRole(form.id, data)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
|
||||
dialogVisible.value = false
|
||||
formEl.resetFields()
|
||||
getTableData()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 状态切换
|
||||
const handleStatusChange = async (row: any, newStatus: number) => {
|
||||
const oldStatus = row.status
|
||||
// 先更新UI
|
||||
row.status = newStatus
|
||||
try {
|
||||
await RoleService.updateRole(row.ID, { status: newStatus })
|
||||
ElMessage.success('状态切换成功')
|
||||
} catch (error) {
|
||||
// 切换失败,恢复原状态
|
||||
row.status = oldStatus
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.role-page {
|
||||
// 可以在这里添加角色页面特定样式
|
||||
}
|
||||
</style>
|
||||
429
src/views/system/user-center/index.vue
Normal file
429
src/views/system/user-center/index.vue
Normal file
@@ -0,0 +1,429 @@
|
||||
<template>
|
||||
<div class="page-content user">
|
||||
<div class="content">
|
||||
<div class="left-wrap">
|
||||
<div class="user-wrap box-style">
|
||||
<img class="bg" src="@imgs/user/bg.webp" />
|
||||
<img class="avatar" src="@imgs/user/avatar.webp" />
|
||||
<h2 class="name">{{ userInfo.userName }}</h2>
|
||||
<p class="des">Art Design Pro 是一款漂亮的后台管理系统模版.</p>
|
||||
|
||||
<div class="outer-info">
|
||||
<div>
|
||||
<i class="iconfont-sys"></i>
|
||||
<span>jdkjjfnndf@mall.com</span>
|
||||
</div>
|
||||
<div>
|
||||
<i class="iconfont-sys"></i>
|
||||
<span>交互专家</span>
|
||||
</div>
|
||||
<div>
|
||||
<i class="iconfont-sys"></i>
|
||||
<span>广东省深圳市</span>
|
||||
</div>
|
||||
<div>
|
||||
<i class="iconfont-sys"></i>
|
||||
<span>字节跳动-某某平台部-UED</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lables">
|
||||
<h3>标签</h3>
|
||||
<div>
|
||||
<div v-for="item in lableList" :key="item">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <el-carousel class="gallery" height="160px"
|
||||
:interval="5000"
|
||||
indicator-position="none"
|
||||
>
|
||||
<el-carousel-item class="item" v-for="item in galleryList" :key="item">
|
||||
<img :src="item"/>
|
||||
</el-carousel-item>
|
||||
</el-carousel> -->
|
||||
</div>
|
||||
<div class="right-wrap">
|
||||
<div class="info box-style">
|
||||
<h1 class="title">基本设置</h1>
|
||||
|
||||
<ElForm
|
||||
:model="form"
|
||||
class="form"
|
||||
ref="ruleFormRef"
|
||||
:rules="rules"
|
||||
label-width="86px"
|
||||
label-position="top"
|
||||
>
|
||||
<ElRow>
|
||||
<ElFormItem label="姓名" prop="realName">
|
||||
<el-input v-model="form.realName" :disabled="!isEdit" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="性别" prop="sex" class="right-input">
|
||||
<ElSelect v-model="form.sex" placeholder="Select" :disabled="!isEdit">
|
||||
<ElOption
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElRow>
|
||||
|
||||
<ElRow>
|
||||
<ElFormItem label="昵称" prop="nikeName">
|
||||
<ElInput v-model="form.nikeName" :disabled="!isEdit" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="邮箱" prop="email" class="right-input">
|
||||
<ElInput v-model="form.email" :disabled="!isEdit" />
|
||||
</ElFormItem>
|
||||
</ElRow>
|
||||
|
||||
<ElRow>
|
||||
<ElFormItem label="手机" prop="mobile">
|
||||
<ElInput v-model="form.mobile" :disabled="!isEdit" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="地址" prop="address" class="right-input">
|
||||
<ElInput v-model="form.address" :disabled="!isEdit" />
|
||||
</ElFormItem>
|
||||
</ElRow>
|
||||
|
||||
<ElFormItem label="个人介绍" prop="des" :style="{ height: '130px' }">
|
||||
<ElInput type="textarea" :rows="4" v-model="form.des" :disabled="!isEdit" />
|
||||
</ElFormItem>
|
||||
|
||||
<div class="el-form-item-right">
|
||||
<ElButton type="primary" style="width: 90px" v-ripple @click="edit">
|
||||
{{ isEdit ? '保存' : '编辑' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElForm>
|
||||
</div>
|
||||
|
||||
<div class="info box-style" style="margin-top: 20px">
|
||||
<h1 class="title">更改密码</h1>
|
||||
|
||||
<ElForm :model="pwdForm" class="form" label-width="86px" label-position="top">
|
||||
<ElFormItem label="当前密码" prop="password">
|
||||
<ElInput v-model="pwdForm.password" type="password" :disabled="!isEditPwd" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="新密码" prop="newPassword">
|
||||
<ElInput v-model="pwdForm.newPassword" type="password" :disabled="!isEditPwd" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="确认新密码" prop="confirmPassword">
|
||||
<ElInput v-model="pwdForm.confirmPassword" type="password" :disabled="!isEditPwd" />
|
||||
</ElFormItem>
|
||||
|
||||
<div class="el-form-item-right">
|
||||
<ElButton type="primary" style="width: 90px" v-ripple @click="editPwd">
|
||||
{{ isEditPwd ? '保存' : '编辑' }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { ElForm, FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'UserCenter' })
|
||||
|
||||
const userStore = useUserStore()
|
||||
const userInfo = computed(() => userStore.getUserInfo)
|
||||
|
||||
const isEdit = ref(false)
|
||||
const isEditPwd = ref(false)
|
||||
const date = ref('')
|
||||
const form = reactive({
|
||||
realName: 'John Snow',
|
||||
nikeName: '皮卡丘',
|
||||
email: '59301283@mall.com',
|
||||
mobile: '18888888888',
|
||||
address: '广东省深圳市宝安区西乡街道101栋201',
|
||||
sex: '2',
|
||||
des: 'Art Design Pro 是一款漂亮的后台管理系统模版.'
|
||||
})
|
||||
|
||||
const pwdForm = reactive({
|
||||
password: '123456',
|
||||
newPassword: '123456',
|
||||
confirmPassword: '123456'
|
||||
})
|
||||
|
||||
const ruleFormRef = ref<FormInstance>()
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
realName: [
|
||||
{ required: true, message: '请输入昵称', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '长度在 2 到 30 个字符', trigger: 'blur' }
|
||||
],
|
||||
nikeName: [
|
||||
{ required: true, message: '请输入昵称', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '长度在 2 到 30 个字符', trigger: 'blur' }
|
||||
],
|
||||
email: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
|
||||
mobile: [{ required: true, message: '请输入手机号码', trigger: 'blur' }],
|
||||
address: [{ required: true, message: '请输入地址', trigger: 'blur' }],
|
||||
sex: [{ type: 'array', required: true, message: '请选择性别', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: '1',
|
||||
label: '男'
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
label: '女'
|
||||
}
|
||||
]
|
||||
|
||||
const lableList: Array<string> = ['专注设计', '很有想法', '辣~', '大长腿', '川妹子', '海纳百川']
|
||||
|
||||
onMounted(() => {
|
||||
getDate()
|
||||
})
|
||||
|
||||
const getDate = () => {
|
||||
const d = new Date()
|
||||
const h = d.getHours()
|
||||
let text = ''
|
||||
|
||||
if (h >= 6 && h < 9) {
|
||||
text = '早上好'
|
||||
} else if (h >= 9 && h < 11) {
|
||||
text = '上午好'
|
||||
} else if (h >= 11 && h < 13) {
|
||||
text = '中午好'
|
||||
} else if (h >= 13 && h < 18) {
|
||||
text = '下午好'
|
||||
} else if (h >= 18 && h < 24) {
|
||||
text = '晚上好'
|
||||
} else if (h >= 0 && h < 6) {
|
||||
text = '很晚了,早点睡'
|
||||
}
|
||||
|
||||
date.value = text
|
||||
}
|
||||
|
||||
const edit = () => {
|
||||
isEdit.value = !isEdit.value
|
||||
}
|
||||
|
||||
const editPwd = () => {
|
||||
isEditPwd.value = !isEditPwd.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.user {
|
||||
.icon {
|
||||
width: 1.4em;
|
||||
height: 1.4em;
|
||||
overflow: hidden;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentcolor;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
$box-radius: calc(var(--custom-radius) + 4px);
|
||||
|
||||
.box-style {
|
||||
border: 1px solid var(--art-border-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
|
||||
.left-wrap {
|
||||
width: 450px;
|
||||
margin-right: 25px;
|
||||
|
||||
.user-wrap {
|
||||
position: relative;
|
||||
height: 600px;
|
||||
padding: 35px 40px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
background: var(--art-main-bg-color);
|
||||
border-radius: $box-radius;
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-top: 120px;
|
||||
object-fit: cover;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-top: 20px;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.des {
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.outer-info {
|
||||
width: 300px;
|
||||
margin: auto;
|
||||
margin-top: 30px;
|
||||
text-align: left;
|
||||
|
||||
> div {
|
||||
margin-top: 10px;
|
||||
|
||||
span {
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lables {
|
||||
margin-top: 40px;
|
||||
|
||||
h3 {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 15px;
|
||||
|
||||
> div {
|
||||
padding: 3px 6px;
|
||||
margin: 0 10px 10px 0;
|
||||
font-size: 12px;
|
||||
background: var(--art-main-bg-color);
|
||||
border: 1px solid var(--art-border-color);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gallery {
|
||||
margin-top: 25px;
|
||||
border-radius: 10px;
|
||||
|
||||
.item {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-wrap {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
border-radius: $box-radius;
|
||||
|
||||
.info {
|
||||
background: var(--art-main-bg-color);
|
||||
border-radius: $box-radius;
|
||||
|
||||
.title {
|
||||
padding: 15px 25px;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
color: var(--art-text-gray-800);
|
||||
border-bottom: 1px solid var(--art-border-color);
|
||||
}
|
||||
|
||||
.form {
|
||||
box-sizing: border-box;
|
||||
padding: 30px 25px;
|
||||
|
||||
> .el-row {
|
||||
.el-form-item {
|
||||
width: calc(50% - 10px);
|
||||
}
|
||||
|
||||
.el-input,
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.right-input {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.el-form-item-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
|
||||
.el-button {
|
||||
width: 110px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $device-ipad-vertical) {
|
||||
.page-content {
|
||||
.content {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
|
||||
.left-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.right-wrap {
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user