diff --git a/.env.development b/.env.development index 43c546f..e412b41 100644 --- a/.env.development +++ b/.env.development @@ -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 # 调试相关 diff --git a/src/api/business.ts b/src/api/business.ts index 8118da2..79cd2c5 100644 --- a/src/api/business.ts +++ b/src/api/business.ts @@ -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; @@ -11,4 +11,18 @@ export interface ProjectRecord { export interface ProjectParams extends Partial { current: number; pageSize: number; -} \ No newline at end of file +} + +// 全部项目列表 +export function getAllProjectList() { + return axios.get('/project/getAllProjectList'); +} + +// 项目信息 +export function getProjectDetail(id: string) { + return axios.get(`/project/getProject?id=${id}`); +} +// 项目列表(分页) +export function queryPolicyList(params: ProjectParams) { + return axios.post('/project/getProjectList', { params }); +} diff --git a/src/api/setting.ts b/src/api/setting.ts new file mode 100644 index 0000000..efb65df --- /dev/null +++ b/src/api/setting.ts @@ -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; + +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; + +// 创建部门 +export function createDepartment( + params: DepartmentFormParams +): Promise { + return axios.post('/department/createDepartment', params); +} +// 获取部门列表 +export function getDepartmentList(): Promise { + return axios.get('/department/getDepartmentList'); +} +// 更新(编辑)部门 +export function updateDepartment( + params: DepartmentFormParams +): Promise { + return axios.post('/department/updateDepartment', params); +} +export function getDepartmentDetail(id: number): Promise { + 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; + +// 后台-用户列表(除主管外所有用户) +export function getAllUserList(params): Promise { + return axios.post('/user/getAllUserList', params); +} +// 部门-向上移动 +export function departmentSetUp(id: number): Promise { + return axios.get(`/department/setUp?id=${id}`); +} +// 部门-向下移动 +export function departmentSetDown(id: number): Promise { + return axios.get(`/department/setDown?id=${id}`); +} +// 删除部门 +export function delDepartment(id: number): Promise { + return axios.get(`/department/delDepartment?id=${id}`); +} +// // 项目信息 +// export function getProjectDetail(id: string) { +// return axios.get(`/project/getProject?id=${id}`); +// } +// // 项目列表(分页) +// export function queryPolicyList(params: ProjectParams) { +// return axios.post('/project/getProjectList', { params }); +// } diff --git a/src/assets/style/global.less b/src/assets/style/global.less index de914e9..73802ac 100644 --- a/src/assets/style/global.less +++ b/src/assets/style/global.less @@ -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); +} diff --git a/src/layout/default-layout.vue b/src/layout/default-layout.vue index 7bbef6f..55f432f 100644 --- a/src/layout/default-layout.vue +++ b/src/layout/default-layout.vue @@ -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 }; diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts index f678420..496dc3d 100644 --- a/src/locale/zh-CN.ts +++ b/src/locale/zh-CN.ts @@ -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, }; diff --git a/src/router/routes/modules/setting.ts b/src/router/routes/modules/setting.ts index c93fd97..b4e33f0 100644 --- a/src/router/routes/modules/setting.ts +++ b/src/router/routes/modules/setting.ts @@ -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, diff --git a/src/utils/index.ts b/src/utils/index.ts index 197bab3..cdfe720 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -22,4 +22,17 @@ export const regexUrl = new RegExp( 'i' ); +export function debounce any>( + fn: T, + delay = 300 +) { + let timer: ReturnType | null = null; + return function (this: any, ...args: Parameters) { + if (timer) clearTimeout(timer); + timer = setTimeout(() => { + fn.apply(this, args); + }, delay); + }; +} + export default null; diff --git a/src/views/business/locale/zh-CN.ts b/src/views/business/locale/zh-CN.ts deleted file mode 100644 index 6be826e..0000000 --- a/src/views/business/locale/zh-CN.ts +++ /dev/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': '新建项目', -}; diff --git a/src/views/business/project/index.vue b/src/views/business/project/index.vue index 0a84476..6daf797 100644 --- a/src/views/business/project/index.vue +++ b/src/views/business/project/index.vue @@ -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([ @@ -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() diff --git a/src/views/setting/composables/useDepartmentList.ts b/src/views/setting/composables/useDepartmentList.ts new file mode 100644 index 0000000..c02d738 --- /dev/null +++ b/src/views/setting/composables/useDepartmentList.ts @@ -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([]); + const allDepartmentKeys = ref([]); + 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 => { + 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, + }; +} diff --git a/src/views/setting/locale/zh-CN.ts b/src/views/setting/locale/zh-CN.ts deleted file mode 100644 index e7dc8ee..0000000 --- a/src/views/setting/locale/zh-CN.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default { - 'menu.setting.user': '用户设置', - 'menu.setting.user-list': '用户列表', - 'menu.setting.role': '角色权限', - 'menu.setting.system': '系统配置', -}; diff --git a/src/views/setting/system/components/department-form-modal.vue b/src/views/setting/system/components/department-form-modal.vue new file mode 100644 index 0000000..6a8d6e4 --- /dev/null +++ b/src/views/setting/system/components/department-form-modal.vue @@ -0,0 +1,148 @@ + + + diff --git a/src/views/setting/system/index.vue b/src/views/setting/system/index.vue index 0752538..147e18f 100644 --- a/src/views/setting/system/index.vue +++ b/src/views/setting/system/index.vue @@ -1,3 +1,82 @@ + + + + diff --git a/src/views/setting/system/organization.vue b/src/views/setting/system/organization.vue new file mode 100644 index 0000000..8c7772f --- /dev/null +++ b/src/views/setting/system/organization.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/src/views/setting/user/list.vue b/src/views/setting/user/list.vue index 0752538..2aa198f 100644 --- a/src/views/setting/user/list.vue +++ b/src/views/setting/user/list.vue @@ -1,3 +1,57 @@ + +