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:
sexygoat
2026-01-22 16:35:33 +08:00
commit 222e5bb11a
495 changed files with 145440 additions and 0 deletions

View 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>

View File

@@ -0,0 +1,5 @@
<template>
<div class="page-content">
<h1>菜单-1</h1>
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<div class="page-content">
<h1>菜单-2-1</h1>
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<div class="page-content">
<h1>菜单-3-1</h1>
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<div class="page-content">
<h1>菜单-3-2-1</h1>
</div>
</template>

View 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>

View 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: '&#xe72b;',
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>

View 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">&#xe72e;</i>
<span>jdkjjfnndf@mall.com</span>
</div>
<div>
<i class="iconfont-sys">&#xe608;</i>
<span>交互专家</span>
</div>
<div>
<i class="iconfont-sys">&#xe736;</i>
<span>广东省深圳市</span>
</div>
<div>
<i class="iconfont-sys">&#xe811;</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>