feat: 对接系统配置中的组织架构90%
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
# 开发环境配置
|
||||
VITE_APP_TITLE=租赁管理系统
|
||||
VITE_API_BASE_URL=http://localhost:9500/api/
|
||||
VITE_API_BASE_URL=https://go.kola.work/api
|
||||
VITE_APP_MODE=development
|
||||
|
||||
# 调试相关
|
||||
|
||||
+16
-2
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import type { RouteRecordNormalized } from 'vue-router';
|
||||
import { UserState } from '@/store/modules/user/types';
|
||||
// import type { RouteRecordNormalized } from 'vue-router';
|
||||
// import { UserState } from '@/store/modules/user/types';
|
||||
|
||||
export interface ProjectRecord {
|
||||
id: string;
|
||||
@@ -12,3 +12,17 @@ export interface ProjectParams extends Partial<ProjectRecord> {
|
||||
current: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
// 全部项目列表
|
||||
export function getAllProjectList() {
|
||||
return axios.get<any>('/project/getAllProjectList');
|
||||
}
|
||||
|
||||
// 项目信息
|
||||
export function getProjectDetail(id: string) {
|
||||
return axios.get<any>(`/project/getProject?id=${id}`);
|
||||
}
|
||||
// 项目列表(分页)
|
||||
export function queryPolicyList(params: ProjectParams) {
|
||||
return axios.post<any>('/project/getProjectList', { params });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import axios from 'axios';
|
||||
import { HttpResponse } from './interceptor';
|
||||
|
||||
export interface DepartmentFormParams {
|
||||
parent_id: number | null;
|
||||
name: string;
|
||||
leaderId?: number;
|
||||
remark?: string;
|
||||
id?: number;
|
||||
}
|
||||
|
||||
export interface DepartmentIdData {
|
||||
departmentId: number | string;
|
||||
}
|
||||
type DepartmentFormRes = HttpResponse<DepartmentIdData>;
|
||||
|
||||
export interface DepartmentItem {
|
||||
id: number;
|
||||
systemTenantId: number;
|
||||
name: string;
|
||||
parentId: number;
|
||||
UserId: number;
|
||||
level: number;
|
||||
path: string;
|
||||
remark: string;
|
||||
isDelete: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
sort: number;
|
||||
children?: DepartmentItem[]; // 递归结构
|
||||
isShowEditIcon?: boolean; // 是否展示编辑icon
|
||||
username?: string;
|
||||
parentName?: string;
|
||||
}
|
||||
export type DepartmentList = DepartmentItem[];
|
||||
export type DepartmentListRes = HttpResponse<{
|
||||
departmentList: DepartmentItem[];
|
||||
}>;
|
||||
export interface DepartmentDetailData {
|
||||
department: DepartmentItem;
|
||||
}
|
||||
export type DepartmentDetailRes = HttpResponse<DepartmentDetailData>;
|
||||
|
||||
// 创建部门
|
||||
export function createDepartment(
|
||||
params: DepartmentFormParams
|
||||
): Promise<DepartmentFormRes> {
|
||||
return axios.post('/department/createDepartment', params);
|
||||
}
|
||||
// 获取部门列表
|
||||
export function getDepartmentList(): Promise<DepartmentListRes> {
|
||||
return axios.get('/department/getDepartmentList');
|
||||
}
|
||||
// 更新(编辑)部门
|
||||
export function updateDepartment(
|
||||
params: DepartmentFormParams
|
||||
): Promise<DepartmentFormRes> {
|
||||
return axios.post('/department/updateDepartment', params);
|
||||
}
|
||||
export function getDepartmentDetail(id: number): Promise<DepartmentDetailRes> {
|
||||
return axios.get(`/department/getDepartment?id=${id}`);
|
||||
}
|
||||
|
||||
export interface UserItem {
|
||||
id: number; // 用户id
|
||||
username: string; // 用户名称
|
||||
realname: string; // 姓名
|
||||
email: string; // 邮箱
|
||||
tel: string; // 手机号
|
||||
status: 1 | 2; // 状态:1 启用,2 禁用
|
||||
role_id: number; // 角色id
|
||||
department_id: number; // 部门id
|
||||
role_name: string; // 角色名称
|
||||
dept_name: string; // 部门名称
|
||||
lastLoginTime: string; // 最后登录时间
|
||||
}
|
||||
export type UserList = UserItem[];
|
||||
export type UserListRes = HttpResponse<UserList>;
|
||||
|
||||
// 后台-用户列表(除主管外所有用户)
|
||||
export function getAllUserList(params): Promise<UserListRes> {
|
||||
return axios.post('/user/getAllUserList', params);
|
||||
}
|
||||
// 部门-向上移动
|
||||
export function departmentSetUp(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/setUp?id=${id}`);
|
||||
}
|
||||
// 部门-向下移动
|
||||
export function departmentSetDown(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/setDown?id=${id}`);
|
||||
}
|
||||
// 删除部门
|
||||
export function delDepartment(id: number): Promise<HttpResponse> {
|
||||
return axios.get(`/department/delDepartment?id=${id}`);
|
||||
}
|
||||
// // 项目信息
|
||||
// export function getProjectDetail(id: string) {
|
||||
// return axios.get<any>(`/project/getProject?id=${id}`);
|
||||
// }
|
||||
// // 项目列表(分页)
|
||||
// export function queryPolicyList(params: ProjectParams) {
|
||||
// return axios.post<any>('/project/getProjectList', { params });
|
||||
// }
|
||||
@@ -13,36 +13,131 @@ body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
// reset.less - 通用 CSS Reset 初始化样式
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
main,
|
||||
menu,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
text-size-adjust: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
input[type='search'] {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
// 常用隐藏类
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// 通用字体加粗
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.echarts-tooltip-diy {
|
||||
background: linear-gradient(
|
||||
304.17deg,
|
||||
rgba(253, 254, 255, 0.6) -6.04%,
|
||||
rgba(244, 247, 252, 0.6) 85.2%
|
||||
rgb(253 254 255 / 60%) -6.04%,
|
||||
rgb(244 247 252 / 60%) 85.2%
|
||||
) !important;
|
||||
border: none !important;
|
||||
backdrop-filter: blur(10px) !important;
|
||||
|
||||
/* Note: backdrop-filter has minimal browser support */
|
||||
|
||||
border-radius: 6px !important;
|
||||
|
||||
.content-panel {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 9px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
background: rgb(255 255 255 / 80%);
|
||||
width: 164px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
box-shadow: 6px 0px 20px rgba(34, 87, 188, 0.1);
|
||||
box-shadow: 6px 0 20px rgb(34 87 188 / 10%);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tooltip-title {
|
||||
margin: 0 0 10px 0;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tooltip-title,
|
||||
.tooltip-value {
|
||||
font-size: 13px;
|
||||
@@ -53,6 +148,7 @@ body {
|
||||
color: #1d2129;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tooltip-item-icon {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
@@ -65,13 +161,15 @@ body {
|
||||
.general-card {
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
|
||||
& > .arco-card-header {
|
||||
height: auto;
|
||||
padding: 20px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
& > .arco-card-body {
|
||||
padding: 0 20px 20px 20px;
|
||||
padding: 0 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,18 +185,26 @@ body {
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: rgb(var(--blue-6));
|
||||
|
||||
&.pass {
|
||||
background-color: rgb(var(--green-6));
|
||||
}
|
||||
}
|
||||
}
|
||||
.flex_center_between{
|
||||
|
||||
.flex_center_between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.card-title_between{
|
||||
|
||||
.card-title_between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.full_height {
|
||||
/* 56为面包屑高度 58为头部layout高度 20为内容padding */
|
||||
min-height: calc(100vh - 56px - 58px - 20px);
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
const paddingStyle = computed(() => {
|
||||
const paddingLeft =
|
||||
renderMenu.value && !hideMenu.value
|
||||
? { paddingLeft: `${menuWidth.value + 10}px` }
|
||||
? { paddingLeft: `${menuWidth.value + 18}px` }
|
||||
: {};
|
||||
const paddingTop = navbar.value ? { paddingTop: navbarHeight } : {};
|
||||
return { ...paddingLeft, ...paddingTop };
|
||||
|
||||
+40
-12
@@ -5,18 +5,13 @@ import localeWorkplace from '@/views/dashboard/workplace/locale/zh-CN';
|
||||
|
||||
import localeMonitor from '@/views/dashboard/monitor/locale/zh-CN';
|
||||
|
||||
|
||||
import locale403 from '@/views/exception/403/locale/zh-CN';
|
||||
import locale404 from '@/views/exception/404/locale/zh-CN';
|
||||
import locale500 from '@/views/exception/500/locale/zh-CN';
|
||||
|
||||
|
||||
import localeSettings from './zh-CN/settings';
|
||||
|
||||
import localeBusiness from '@/views/business/locale/zh-CN';
|
||||
import localeFinance from '@/views/finance/locale/zh-CN';
|
||||
import localeProperty from '@/views/property/locale/zh-CN';
|
||||
import localeSetting from '@/views/setting/locale/zh-CN';
|
||||
import localeSettings from './zh-CN/settings';
|
||||
|
||||
export default {
|
||||
'menu.dashboard': '仪表盘',
|
||||
@@ -35,14 +30,49 @@ export default {
|
||||
'navbar.docs': '文档中心',
|
||||
'navbar.action.locale': '切换为中文',
|
||||
|
||||
// 翻译
|
||||
'menu.business': '业务',
|
||||
'menu.finance': '财务',
|
||||
'menu.property': '物业',
|
||||
'menu.setting': '设置',
|
||||
|
||||
'search.placeholder':'请输入内容',
|
||||
'table.filter':'筛选',
|
||||
'table.export':'导出',
|
||||
'search.placeholder': '请输入内容',
|
||||
'table.filter': '筛选',
|
||||
'table.export': '导出',
|
||||
'watch-detail': '查看详情',
|
||||
'menu.business.project': '项目管理',
|
||||
'menu.business.space': '空间管理',
|
||||
'menu.business.house-view': '房源剖视',
|
||||
'menu.business.house-list': '房源列表',
|
||||
'menu.business.contract': '合同管理',
|
||||
'menu.business.contract-list': '合同列表',
|
||||
'menu.business.contract-file': '合同归档',
|
||||
'menu.business.contract-template': '合同模板',
|
||||
'menu.business.tenant': '租客管理',
|
||||
'menu.business.tenant-list': '租客列表',
|
||||
'menu.business.tenant.contacts': '租客联系人',
|
||||
'add-project': '新建项目',
|
||||
'menu.setting.user': '用户设置',
|
||||
'menu.setting.user-list': '用户列表',
|
||||
'menu.setting.role': '角色权限',
|
||||
'menu.setting.system': '系统配置',
|
||||
'organization': '组织架构',
|
||||
'general-setting': '通用设置',
|
||||
'security-setting': '安全设置',
|
||||
'company-info': '公司信息',
|
||||
'appearance-setting': '外观设置',
|
||||
'add-department': '新建部门',
|
||||
'department-name': '部门名称',
|
||||
'belongto-department': '所属部门',
|
||||
'department-head': '部门主管',
|
||||
'department-desc': '部门描述',
|
||||
'remark': '备注',
|
||||
'confirm.delete': '确认删除',
|
||||
'delete.some': '删除{name}',
|
||||
'please.confirm.delete.some': '请确认是否要删除此{name}?',
|
||||
'department': '部门',
|
||||
'delete': '删除',
|
||||
'confirm': '确定',
|
||||
'cancel': '取消',
|
||||
|
||||
...localeSettings,
|
||||
...localeMessageBox,
|
||||
@@ -54,8 +84,6 @@ export default {
|
||||
...locale404,
|
||||
...locale500,
|
||||
|
||||
...localeBusiness,
|
||||
...localeFinance,
|
||||
...localeProperty,
|
||||
...localeSetting,
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import PageLayout from '@/layout/page-layout.vue';
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import { AppRouteRecordRaw } from '../types';
|
||||
import PageLayout from '@/layout/page-layout.vue';
|
||||
// 设置
|
||||
const SETTING: AppRouteRecordRaw = {
|
||||
path: '/setting',
|
||||
name: 'setting',
|
||||
component: DEFAULT_LAYOUT,
|
||||
redirect: '/setting/basic',
|
||||
redirect: '/setting/user',
|
||||
meta: {
|
||||
locale: 'menu.setting',
|
||||
requiresAuth: true,
|
||||
@@ -20,6 +20,7 @@ const SETTING: AppRouteRecordRaw = {
|
||||
path: 'user',
|
||||
name: 'user-setting',
|
||||
component: PageLayout,
|
||||
redirect: '/setting/user/list',
|
||||
meta: {
|
||||
locale: 'menu.setting.user',
|
||||
requiresAuth: true,
|
||||
|
||||
@@ -22,4 +22,17 @@ export const regexUrl = new RegExp(
|
||||
'i'
|
||||
);
|
||||
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
fn: T,
|
||||
delay = 300
|
||||
) {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
return function (this: any, ...args: Parameters<T>) {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
fn.apply(this, args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
||||
export default null;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
export default {
|
||||
'menu.business.project': '项目管理',
|
||||
'menu.business.space': '空间管理',
|
||||
'menu.business.house-view': '房源剖视',
|
||||
'menu.business.house-list': '房源列表',
|
||||
'menu.business.contract': '合同管理',
|
||||
'menu.business.contract-list': '合同列表',
|
||||
'menu.business.contract-file': '合同归档',
|
||||
'menu.business.contract-template': '合同模板',
|
||||
'menu.business.tenant': '租客管理',
|
||||
'menu.business.tenant-list': '租客列表',
|
||||
'menu.business.tenant.contacts': '租客联系人',
|
||||
'add-project': '新建项目',
|
||||
};
|
||||
@@ -77,6 +77,7 @@
|
||||
PROJECT_TYPE_COLORS,
|
||||
PROJECT_TYPE_LABELS,
|
||||
} from '@/types/business';
|
||||
import { ProjectParams, getAllProjectList } from '@/api/business';
|
||||
|
||||
const { loading, setLoading } = useLoading(false);
|
||||
const renderData = ref<ProjectRecord[]>([
|
||||
@@ -138,4 +139,17 @@
|
||||
pagination.current = current;
|
||||
// TODO: 这里应该调用API获取对应页码的数据
|
||||
};
|
||||
|
||||
const getAllProjectListData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await getAllProjectList();
|
||||
console.log(data, 'getAllProjectList');
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// getAllProjectListData()
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ref, onMounted } from 'vue';
|
||||
import {
|
||||
getDepartmentList,
|
||||
DepartmentItem,
|
||||
getDepartmentDetail,
|
||||
} from '@/api/setting';
|
||||
|
||||
export default function useDepartmentList(autoFetch = true) {
|
||||
const departmentListData = ref<DepartmentItem[]>([]);
|
||||
const allDepartmentKeys = ref<number[]>([]);
|
||||
function markNonLevel1Departments(list: DepartmentItem[]): DepartmentItem[] {
|
||||
const traverse = (items: DepartmentItem[]) => {
|
||||
return items.map((item) => {
|
||||
const newItem: DepartmentItem = {
|
||||
...item,
|
||||
isShowEditIcon: false,
|
||||
children: [],
|
||||
};
|
||||
|
||||
// 收集 ID
|
||||
allDepartmentKeys.value.push(item.id);
|
||||
|
||||
if (item.children && item.children.length > 0) {
|
||||
newItem.children = traverse(item.children);
|
||||
}
|
||||
|
||||
return newItem;
|
||||
});
|
||||
};
|
||||
|
||||
return traverse(list);
|
||||
}
|
||||
|
||||
const getDepartmentListData = async () => {
|
||||
try {
|
||||
const { code, data } = await getDepartmentList();
|
||||
if (code === 200) {
|
||||
if (data?.departmentList?.length === 0) return;
|
||||
allDepartmentKeys.value = []; // 清空再收集
|
||||
|
||||
const fetchDepartmentList = markNonLevel1Departments(
|
||||
data?.departmentList
|
||||
);
|
||||
departmentListData.value = fetchDepartmentList;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取部门列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getDepartmentDetailData = async (
|
||||
id: number
|
||||
): Promise<DepartmentItem | null> => {
|
||||
try {
|
||||
const { code, data } = await getDepartmentDetail(id);
|
||||
if (code === 200 && data?.department) {
|
||||
return data.department;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('获取部门详情失败:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
if (autoFetch) {
|
||||
onMounted(() => {
|
||||
getDepartmentListData();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
departmentListData,
|
||||
allDepartmentKeys,
|
||||
getDepartmentListData,
|
||||
getDepartmentDetailData,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
'menu.setting.user': '用户设置',
|
||||
'menu.setting.user-list': '用户列表',
|
||||
'menu.setting.role': '角色权限',
|
||||
'menu.setting.system': '系统配置',
|
||||
};
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
:title="isEdit ? '编辑部门' : '新增部门'"
|
||||
unmount-on-close
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
auto-label-width
|
||||
layout="vertical"
|
||||
>
|
||||
<!-- 部门名称 -->
|
||||
<a-form-item :label="t('department-name')" field="name" required>
|
||||
<a-input v-model="form.name" placeholder="请输入部门名称" />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 所属部门 -->
|
||||
<a-form-item :label="t('belongto-department')" field="parent_id" required>
|
||||
<a-tree-select
|
||||
v-model="form.parent_id"
|
||||
:data="departmentListData"
|
||||
:field-names="{ key: 'id', title: 'name', children: 'children' }"
|
||||
placeholder="请选择所属部门"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 部门主管 -->
|
||||
<a-form-item :label="t('department-head')" field="user_id">
|
||||
<a-select
|
||||
v-model="form.user_id"
|
||||
:options="props.userListData"
|
||||
placeholder="请选择部门主管"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 备注 -->
|
||||
<a-form-item :label="t('remark')" field="remark">
|
||||
<a-textarea
|
||||
v-model="form.remark"
|
||||
placeholder="请输入备注"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FormInstance } from '@arco-design/web-vue';
|
||||
import {
|
||||
createDepartment,
|
||||
updateDepartment,
|
||||
DepartmentItem,
|
||||
UserList,
|
||||
} from '@/api/setting';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
// Props & Emits
|
||||
// interface LeaderOption {
|
||||
// name: string;
|
||||
// id: number;
|
||||
// }
|
||||
|
||||
interface DepartmentForm {
|
||||
id?: number;
|
||||
name: string;
|
||||
parent_id: number | null;
|
||||
user_id?: number;
|
||||
remark?: string;
|
||||
}
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
isEdit: boolean;
|
||||
record?: DepartmentItem | null;
|
||||
departmentListData: any[]; // 新增
|
||||
userListData: UserList;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
(e: 'submitted'): void;
|
||||
}>();
|
||||
// 表单数据
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = reactive<DepartmentForm>({
|
||||
name: '',
|
||||
parent_id: null,
|
||||
user_id: undefined,
|
||||
remark: '',
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
// 校验规则
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入部门名称' }],
|
||||
parent_id: [{ required: true, message: '请选择所属部门' }],
|
||||
};
|
||||
|
||||
// Watch 弹窗控制 & 初始化
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
if (props.record) {
|
||||
form.id = props.record?.id;
|
||||
form.name = props.record?.name;
|
||||
form.parent_id = props.record?.parentId ?? '';
|
||||
form.user_id = props.record?.UserId ?? '';
|
||||
form.remark = props.record?.remark ?? '';
|
||||
// Object.assign(form, props.record);
|
||||
} else {
|
||||
Object.assign(form, {
|
||||
id: undefined,
|
||||
name: '',
|
||||
parent_id: null,
|
||||
user_id: undefined,
|
||||
remark: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 表单事件
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate();
|
||||
const { code, message } = props.isEdit
|
||||
? await updateDepartment(form)
|
||||
: await createDepartment(form);
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
handleCancel();
|
||||
// 通知父组件更新部门列表数据
|
||||
emit('submitted');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,3 +1,82 @@
|
||||
<template>
|
||||
<div>帅哥kilo</div>
|
||||
<div class="setting_user_list">
|
||||
<Breadcrumb :items="['menu.setting', 'menu.setting.system']" />
|
||||
<a-row :gutter="12">
|
||||
<a-col flex="310px">
|
||||
<a-card
|
||||
class="full_height"
|
||||
:bordered="false"
|
||||
:title="$t('menu.setting.system')"
|
||||
>
|
||||
<ul class="system_setting_list">
|
||||
<li
|
||||
v-for="item in systemSettingList"
|
||||
:key="item.id"
|
||||
:class="[
|
||||
'system_setting_item',
|
||||
{ active: currentActive === item.id },
|
||||
]"
|
||||
@click="handleChooseSystemItem(item.id)"
|
||||
>
|
||||
<component :is="item.icon" class="mr-2" />
|
||||
<p class="name">{{ item.name }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<organization v-if="currentActive === 4" />
|
||||
<div v-else>开发中{{ currentActive }}</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import {
|
||||
IconSettings,
|
||||
IconSafe,
|
||||
IconPaste,
|
||||
IconFullscreen,
|
||||
IconTiktokColor,
|
||||
} from '@arco-design/web-vue/es/icon';
|
||||
import organization from './organization.vue';
|
||||
// import { CreateDepartmentParams, createDepartment, getDepartmentList, DepartmentItem } from '@/api/setting'
|
||||
const { t } = useI18n();
|
||||
const currentActive = ref(4);
|
||||
const systemSettingList = [
|
||||
{ id: 1, name: t('general-setting'), icon: IconSettings },
|
||||
{ id: 2, name: t('security-setting'), icon: IconSafe },
|
||||
{ id: 3, name: t('company-info'), icon: IconPaste },
|
||||
{ id: 4, name: t('organization'), icon: IconFullscreen },
|
||||
{ id: 5, name: t('appearance-setting'), icon: IconTiktokColor },
|
||||
];
|
||||
const handleChooseSystemItem = (id: number) => {
|
||||
currentActive.value = id;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.setting_user_list {
|
||||
.system_setting_list {
|
||||
.system_setting_item {
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
|
||||
&.active {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.name {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<a-card class="full_height" :bordered="false">
|
||||
<template #title>
|
||||
<div class="card-title_between">
|
||||
<p>
|
||||
<icon-drag-arrow />
|
||||
<span class="title" style="margin-left: 15px">{{
|
||||
$t('organization')
|
||||
}}</span>
|
||||
</p>
|
||||
<a-button type="primary" @click="handleAdd">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
{{ $t('add-department') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<a-row :gutter="12">
|
||||
<a-col class="border" :span="12">
|
||||
<a-tree
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
:field-names="{ key: 'id', title: 'name' }"
|
||||
:data="departmentListData"
|
||||
@select="onSelect"
|
||||
>
|
||||
<template #icon>
|
||||
<IconStar />
|
||||
</template>
|
||||
<template #extra="nodeData">
|
||||
<a-row
|
||||
v-if="nodeData.isShowEditIcon"
|
||||
style="position: absolute; right: 0"
|
||||
:gutter="24"
|
||||
>
|
||||
<a-col :span="4" @click="handleEdit"><icon-edit /></a-col>
|
||||
<a-col :span="4">
|
||||
<a-popconfirm
|
||||
:ok-text="t('cancel')"
|
||||
:cancel-text="t('delete')"
|
||||
:ok-button-props="{ type: 'outline' }"
|
||||
:cancel-button-props="{ type: 'primary', status: 'danger' }"
|
||||
:content="
|
||||
t('please.confirm.delete.some', { name: t('department') })
|
||||
"
|
||||
type="error"
|
||||
@cancel="handleDelete(nodeData)"
|
||||
>
|
||||
<icon-delete />
|
||||
</a-popconfirm>
|
||||
</a-col>
|
||||
<a-col :span="4" @click="handleMoveUp(nodeData)"
|
||||
><icon-up
|
||||
/></a-col>
|
||||
<a-col :span="4" @click="handleMoveDown(nodeData)"
|
||||
><icon-down
|
||||
/></a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-descriptions
|
||||
v-if="departmentDetailInfo"
|
||||
:column="1"
|
||||
layout="horizontal"
|
||||
bordered
|
||||
>
|
||||
<a-descriptions-item :label="t('department-name')">
|
||||
{{ departmentDetailInfo.name }}</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="t('belongto-department')">
|
||||
{{ departmentDetailInfo.parentName || '-' }}</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="t('department-head')">
|
||||
{{ departmentDetailInfo.username || '-' }}</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="t('department-desc')">
|
||||
{{ departmentDetailInfo.remark }}</a-descriptions-item
|
||||
>
|
||||
</a-descriptions>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<departmentFormModal
|
||||
v-model:visible="showModal"
|
||||
:department-list-data="departmentListData"
|
||||
:record="departmentDetailInfo"
|
||||
:user-list-data="allUserList"
|
||||
:is-edit="isEdit"
|
||||
@submitted="getDepartmentListData"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import useDepartmentList from '@/views/setting/composables/useDepartmentList';
|
||||
import {
|
||||
DepartmentItem,
|
||||
getAllUserList,
|
||||
UserList,
|
||||
departmentSetUp,
|
||||
departmentSetDown,
|
||||
delDepartment,
|
||||
} from '@/api/setting';
|
||||
import { debounce } from '@/utils';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import departmentFormModal from './components/department-form-modal.vue';
|
||||
|
||||
const {
|
||||
departmentListData,
|
||||
allDepartmentKeys,
|
||||
getDepartmentListData,
|
||||
getDepartmentDetailData,
|
||||
} = useDepartmentList();
|
||||
const emit = defineEmits(['onSelectTree']);
|
||||
const expandedKeys = ref<(string | number)[]>([]);
|
||||
const { t } = useI18n();
|
||||
watch(
|
||||
allDepartmentKeys,
|
||||
(val) => {
|
||||
expandedKeys.value = val;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const showModal = ref<boolean>(false);
|
||||
const isEdit = ref<boolean>(false);
|
||||
const departmentDetailInfo = ref<DepartmentItem | null>(null);
|
||||
|
||||
const resetEditIcon = (nodes: any[]) => {
|
||||
nodes.forEach((node) => {
|
||||
node.isShowEditIcon = false;
|
||||
if (node.children && node.children.length > 0) {
|
||||
resetEditIcon(node.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
const currentNode = ref<any>(null);
|
||||
const onSelect = debounce(async (_: (string | number)[], event: any) => {
|
||||
const { node } = event;
|
||||
if (node.id === currentNode.value?.id) return;
|
||||
currentNode.value = node;
|
||||
// 递归重置所有节点的 isShowEditIcon
|
||||
resetEditIcon(departmentListData.value);
|
||||
// 只设置当前节点
|
||||
if (currentNode.value.level !== 1) {
|
||||
node.isShowEditIcon = true;
|
||||
}
|
||||
departmentDetailInfo.value = await getDepartmentDetailData(
|
||||
currentNode.value.id
|
||||
);
|
||||
}, 300);
|
||||
|
||||
// 所有用户数据
|
||||
const allUserList = ref<UserList>([]);
|
||||
const userKeyword = ref<string>('');
|
||||
|
||||
const getAllUserListData = async () => {
|
||||
try {
|
||||
const { code, data } = await getAllUserList({
|
||||
keyword: userKeyword.value,
|
||||
});
|
||||
if (code === 200 && data?.length) {
|
||||
allUserList.value = data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取所以用户数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
showModal.value = true;
|
||||
isEdit.value = false;
|
||||
};
|
||||
const handleEdit = () => {
|
||||
showModal.value = true;
|
||||
isEdit.value = true;
|
||||
};
|
||||
const handleDelete = async (item) => {
|
||||
const { id } = item;
|
||||
try {
|
||||
const { code, message } = await delDepartment(id);
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
getDepartmentListData();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('删除失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMoveUp = async (item) => {
|
||||
const { id } = item;
|
||||
try {
|
||||
const { code, message } = await departmentSetUp(id);
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
getDepartmentListData();
|
||||
} else {
|
||||
Message.info(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('向上移动失败:', error);
|
||||
}
|
||||
};
|
||||
const handleMoveDown = async (item) => {
|
||||
const { id } = item;
|
||||
try {
|
||||
const { code, message } = await departmentSetDown(id);
|
||||
if (code === 200) {
|
||||
Message.success(message);
|
||||
getDepartmentListData();
|
||||
} else {
|
||||
Message.info(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('向下移动失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getAllUserListData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.border {
|
||||
border: 1px solid #f4f4f4;
|
||||
height: 100%;
|
||||
padding: 15px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +1,57 @@
|
||||
<template>
|
||||
<div>帅哥kilo</div>
|
||||
<div class="setting_user_list">
|
||||
<Breadcrumb
|
||||
:items="['menu.setting', 'menu.setting.user', 'menu.setting.user-list']"
|
||||
/>
|
||||
<a-row class="" :gutter="12">
|
||||
<a-col flex="310px">
|
||||
<a-card :bordered="false">
|
||||
<template #title>
|
||||
<div class="card-title_between">
|
||||
<p class="title">{{ $t('organization') }}</p>
|
||||
<a-button @click="onClick"> {{ $t('watch-detail') }} </a-button>
|
||||
</div>
|
||||
</template>
|
||||
<a-tree
|
||||
:field-names="{ key: 'ID', title: 'Name' }"
|
||||
:data="departmentListData"
|
||||
>
|
||||
<template #icon>
|
||||
<IconStar />
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<a-card title="Arco Card" :bordered="false">
|
||||
<template #extra>
|
||||
<a-link>More</a-link>
|
||||
</template>
|
||||
Card content
|
||||
<br />
|
||||
Card content
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DepartmentFormParams, createDepartment } from '@/api/setting';
|
||||
import useDepartmentList from '@/views/setting/composables/useDepartmentList';
|
||||
// import { ref } from 'vue'
|
||||
const onClick = async () => {
|
||||
const params: DepartmentFormParams = {
|
||||
name: '冰一下很开心', // 部门名称
|
||||
parent_id: 34, // 上一级id
|
||||
};
|
||||
try {
|
||||
const res = await createDepartment(params);
|
||||
console.log(res, 'res');
|
||||
} catch (error) {
|
||||
console.log(error, 'res');
|
||||
}
|
||||
};
|
||||
const { departmentListData, getDepartmentListData } = useDepartmentList();
|
||||
getDepartmentListData();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user