6 Commits

Author SHA1 Message Date
Zy
ae1f9f6ee2 同步 2026-06-01 18:12:58 +08:00
Zy
38274ab612 房屋详情,单元变更 2026-05-21 14:13:28 +08:00
Zy
9645412534 房屋详情待完善 2026-05-14 17:57:09 +08:00
Zy
0d3bb25e86 快捷入口开发中 2026-05-13 18:07:03 +08:00
Zy
a1750bc67b 修正ts类型 2026-05-13 15:28:25 +08:00
Zy
1b738c0f4d 物业主开发 2026-05-08 11:30:28 +08:00
246 changed files with 9900 additions and 3283 deletions

View File

@@ -11,7 +11,7 @@ VITE_APP_BASE_API = '/dev-api'
# # 开发环境 接口代理地址 # # 开发环境 接口代理地址
VITE_APP_PROXY_API = 'http://192.168.1.222:8080' VITE_APP_PROXY_API = 'http://192.168.1.222:8080'
# 开发环境 接口代理地址 # 开发环境 接口代理地址
# VITE_APP_PROXY_API = 'http://192.168.1.215:8080' # VITE_APP_PROXY_API = 'http://192.168.0.239:8080'
# 图片基础地址 # 图片基础地址
VITE_IMG_URL = 'http://192.168.1.222:8080' VITE_IMG_URL = 'http://192.168.1.222:8080'
@@ -27,6 +27,29 @@ VITE_APP_SNAILJOB_ADMIN = 'http://192.168.1.215/snail-job'
VITE_APP_PORT = 5173 VITE_APP_PORT = 5173
# 登录成功跳转地址
# VITE_LOGIN_BACK_URL = '/equipment/WaterDevice'
VITE_LOGIN_BACK_URL = '/index'
# 工作台正式内容:false 假内容true
VITE_LOGIN_BACK_URL_WORKBENCH_TRUE= true
# 是否展示工作台
VITE_APP_SHOW_WORK_PLATFORM = true
# 地图类型 天地图 or 高德地图 天地图 WGS84 高德地图 GCJ02, 高德地图 需要在高德地图官网申请key
# 高德地图 GCJ02
# 天地图 WGS84
VITE_APP_MAP_TYPE = 'GCJ02'
# 高德地图key 建议放入环境变量中,避免泄露
VITE_GCJ02_KEY_GD = '8e302fe1be5f4db62bc734db23dca149'
VITE_GCJ02_JS_CODE_GD = '09534b3534d8da53968529a9ce164118'
# 天地图key 建议放入环境变量中,避免泄露
VITE_TIANDITU_KEY = 'f71689f9e22158947ebf64ad95e0a595'
VITE_TIANDITU_JS_CODE = ''
# 接口加密功能开关(如需关闭 后端也必须对应关闭) # 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT = true VITE_APP_ENCRYPT = true
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换 # 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
@@ -41,4 +64,5 @@ VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
VITE_APP_WEBSOCKET = false VITE_APP_WEBSOCKET = false
# sse 开关 # sse 开关
VITE_APP_SSE = false VITE_APP_SSE = true

View File

@@ -18,10 +18,28 @@ VITE_APP_SNAILJOB_ADMIN = '/snail-job'
VITE_APP_BASE_API = 'https://wuyeapi.bugtc.com' VITE_APP_BASE_API = 'https://wuyeapi.bugtc.com'
# VITE_APP_BASE_API = 'http://192.168.0.222:8080' # VITE_APP_BASE_API = 'http://192.168.0.222:8080'
# 登录成功跳转地址
# VITE_LOGIN_BACK_URL = '/equipment/WaterDevice'
VITE_LOGIN_BACK_URL = '/index'
# 工作台正式内容:false 假内容true
VITE_LOGIN_BACK_URL_WORKBENCH_TRUE= true
# 是否展示工作台
VITE_APP_SHOW_WORK_PLATFORM = true
# 地图类型 天地图 or 高德地图 天地图 WGS84 高德地图 GCJ02, 高德地图 需要在高德地图官网申请key
# 高德地图 GCJ02
# 天地图 WGS84
VITE_APP_MAP_TYPE = 'GCJ02'
# 高德地图key 建议放入环境变量中,避免泄露
VITE_GCJ02_KEY_GD = '8e302fe1be5f4db62bc734db23dca149'
VITE_GCJ02_JS_CODE_GD = '09534b3534d8da53968529a9ce164118'
# 是否在打包时开启压缩,支持 gzip 和 brotli # 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip # VITE_BUILD_COMPRESS = gzip
VITE_APP_PORT = 80 VITE_APP_PORT = 80
@@ -39,4 +57,4 @@ VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
VITE_APP_WEBSOCKET = false VITE_APP_WEBSOCKET = false
# sse 开关 # sse 开关
VITE_APP_SSE = false VITE_APP_SSE = true

19
.gitignore vendored
View File

@@ -2,6 +2,23 @@
.history .history
node_modules/ node_modules/
dist/ dist/
.env
.git
.idea
.vscode
.gitignore
.npmrc
.yarn
.yarnrc
.pnp.js
.pnp.lock.json
.pnp
.yarn.lock
.bat
dist.zip
.zip
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
@@ -27,3 +44,5 @@ pnpm-lock.yaml
# 编译生成的文件 # 编译生成的文件
auto-imports.d.ts auto-imports.d.ts
components.d.ts components.d.ts

View File

@@ -1,12 +0,0 @@
@echo off
echo.
echo [信息] 打包Web工程生成dist文件。
echo.
%~d0
cd %~dp0
cd ..
yarn build:prod
pause

View File

@@ -1,12 +0,0 @@
@echo off
echo.
echo [信息] 安装Web工程生成node_modules文件。
echo.
%~d0
cd %~dp0
cd ..
yarn --registry=https://registry.npmmirror.com
pause

View File

@@ -1,12 +0,0 @@
@echo off
echo.
echo [信息] 使用 Vite 命令运行 Web 工程。
echo.
%~d0
cd %~dp0
cd ..
yarn dev
pause

View File

@@ -35,11 +35,12 @@
"echarts": "6.0.0", "echarts": "6.0.0",
"element-china-area-data": "^6.1.0", "element-china-area-data": "^6.1.0",
"element-plus": "2.13.5", "element-plus": "2.13.5",
"file-saver": "2.0.5", "file-saver": "^2.0.5",
"highlight.js": "11.11.1", "highlight.js": "11.11.1",
"image-conversion": "2.1.1", "image-conversion": "2.1.1",
"js-cookie": "3.0.5", "js-cookie": "3.0.5",
"jsencrypt": "3.5.4", "jsencrypt": "3.5.4",
"jszip": "^3.10.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "3.0.4", "pinia": "3.0.4",
"pinia-plugin-persistedstate": "^4.7.1", "pinia-plugin-persistedstate": "^4.7.1",
@@ -56,7 +57,7 @@
"devDependencies": { "devDependencies": {
"@iconify/json": "^2.2.448", "@iconify/json": "^2.2.448",
"@types/crypto-js": "4.2.2", "@types/crypto-js": "4.2.2",
"@types/file-saver": "2.0.7", "@types/file-saver": "^2.0.7",
"@types/js-cookie": "3.0.6", "@types/js-cookie": "3.0.6",
"@types/node": "^25.4.0", "@types/node": "^25.4.0",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
@@ -85,6 +86,7 @@
"vite": "7.3.1", "vite": "7.3.1",
"vite-plugin-svg-icons-ng": "^1.5.2", "vite-plugin-svg-icons-ng": "^1.5.2",
"vite-plugin-vue-devtools": "8.0.7", "vite-plugin-vue-devtools": "8.0.7",
"vite-plugin-zip-pack": "^1.2.4",
"vitest": "4.0.18", "vitest": "4.0.18",
"vue-tsc": "^3.2.5" "vue-tsc": "^3.2.5"
}, },

View File

@@ -1,6 +1,6 @@
export default { export default {
plugins: { plugins: {
autoprefixer: {}, // autoprefixer: {},
'postcss-pxtorem': { 'postcss-pxtorem': {
rootValue: 16, // 基准根节点字体大小 rootValue: 16, // 基准根节点字体大小
propList: ['*'], // 所有属性都转换 propList: ['*'], // 所有属性都转换

View File

@@ -61,3 +61,18 @@ export const delBanner = (id: string | number | Array<string | number>) => {
method: 'delete' method: 'delete'
}); });
}; };
/**
* 改变Banner图管理 状态
* @param id
*/
export const switchBanner = (id: string, status: string) => {
return request({
url: '/banner/changeStatus',
method: 'GET',
params: {
id,
status
}
});
};

View File

@@ -17,9 +17,9 @@ export interface BannerVO {
/** /**
* 排序 * 排序
*/ */
order: number;
url: string; url: string;
jump_link: string; byOrder: number;
link: string;
/** /**
* 小区id * 小区id
*/ */
@@ -45,7 +45,8 @@ export interface BannerForm extends BaseEntity {
/** /**
* 排序 * 排序
*/ */
order?: number; byOrder?: number;
link: string;
/** /**
* 小区id * 小区id
@@ -67,8 +68,8 @@ export interface BannerQuery extends PageQuery {
/** /**
* 排序 * 排序
*/ */
order?: number; byOrder: number;
link: string;
/** /**
* 小区id * 小区id
*/ */

View File

@@ -60,12 +60,12 @@ export interface DecorationForm extends BaseEntity {
* 所属小区id * 所属小区id
*/ */
villageId?: string; villageId?: string;
villageName?: string;
/** /**
* 所属房屋id * 所属房屋id
*/ */
houseId?: string | number; houseId?: string | number;
houseName?: string;
/** /**
* 申请人姓名 * 申请人姓名
*/ */

View File

@@ -61,3 +61,15 @@ export const delPlan = (id: string | number | Array<string | number>) => {
method: 'delete' method: 'delete'
}); });
}; };
/**
* 改变巡检计划状态
* @param id
* @param status
*/
export const changePlanStatus = (id: string, status: string) => {
return request({
url: '/inspection/plan/changeStatus',
method: 'put',
data: { id, status }
});
};

View File

@@ -8,7 +8,7 @@ import { PointVO, PointForm, PointQuery } from '@/api/system/InspectionPoint/typ
* @returns {*} * @returns {*}
*/ */
export const listPoint = (query?: PointQuery): AxiosPromise<PointVO[]> => { export const listPoint = (query?: PointQuery | {}): AxiosPromise<PointVO[]> => {
return request({ return request({
url: '/inspection/point/list', url: '/inspection/point/list',
method: 'get', method: 'get',
@@ -61,3 +61,13 @@ export const delPoint = (id: string | number | Array<string | number>) => {
method: 'delete' method: 'delete'
}); });
}; };
/**
* 修改巡检点状态
* @param id
*/
export const changePointStatus = (id: string, status: string) => {
return request({
url: '/inspection/point/changeStatus?id=' + id + '&status=' + status,
method: 'PUT'
});
};

View File

@@ -1,15 +1,15 @@
export interface RecordVO { export interface RecordVO {
actualEndTime?: null; actualEndTime?: string;
actualStartTime?: null; actualStartTime?: string;
createByName?: null; createByName?: string;
createTime?: string; createTime?: string;
details?: null; details?: [];
id?: string; id?: string;
inspectorId?: number; inspectorId?: number;
inspectorName?: string; inspectorName?: string;
planId?: string; planId?: string;
planName?: string; planName?: string;
remark?: null; remark?: string;
routeId?: string; routeId?: string;
routeName?: string; routeName?: string;
status?: number; status?: number;

View File

@@ -61,3 +61,11 @@ export const delRoute = (id: string | number | Array<string | number>) => {
method: 'delete' method: 'delete'
}); });
}; };
export const changeStatusRoute = (id: string, status: string) => {
return request({
url: '/inspection/route/changeStatus',
method: 'put',
params: { id, status }
});
};

View File

@@ -26,6 +26,8 @@ export interface RouteVO {
*/ */
estimatedTime: number; estimatedTime: number;
items: { itemName: string }[];
PointsName: string;
/** /**
* 备注 * 备注
*/ */

View File

@@ -22,12 +22,12 @@ export interface NoticeVO {
/** /**
* 紧急程度 0=普通 1=紧急 2=非常紧急 * 紧急程度 0=普通 1=紧急 2=非常紧急
*/ */
urgencyLevel: string; urgencyLevel: number;
/** /**
* 发布状态 0=草稿 1=已发布 * 发布状态 0=草稿 1=已发布
*/ */
publishStatus: string; publishStatus: number;
/** /**
* 发布人 * 发布人
@@ -64,12 +64,12 @@ export interface NoticeForm extends BaseEntity {
/** /**
* 紧急程度 0=普通 1=紧急 2=非常紧急 * 紧急程度 0=普通 1=紧急 2=非常紧急
*/ */
urgencyLevel?: string; urgencyLevel?: number;
/** /**
* 发布状态 0=草稿 1=已发布 * 发布状态 0=草稿 1=已发布
*/ */
publishStatus?: string; publishStatus?: number;
/** /**
* 发布人 * 发布人
@@ -101,12 +101,12 @@ export interface NoticeQuery extends PageQuery {
/** /**
* 紧急程度 0=普通 1=紧急 2=非常紧急 * 紧急程度 0=普通 1=紧急 2=非常紧急
*/ */
urgencyLevel?: string; urgencyLevel?: number;
/** /**
* 发布状态 0=草稿 1=已发布 * 发布状态 0=草稿 1=已发布
*/ */
publishStatus?: string; publishStatus?: number;
/** /**
* 发布人 * 发布人

View File

@@ -0,0 +1,40 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
export interface NotificationVo {
'id': 'string';
'title': 'string';
'item': 'string';
'content': 'string';
'manageIsCheck': 'string';
'repairIsCheck': 'string';
}
/**
* 查询消息通知列表
* @param query
* @returns {*}
*/
export const getNotificationList = (query): AxiosPromise<NoticeVO[]> => {
return request({
url: '/notification/list',
method: 'get',
params: query
});
};
/**
* 消息通知 已读
* @param query
* @returns {*}
*/
export const ReadNotificationByID = (id: string) => {
return request({
url: '/notification/read',
method: 'POST',
data: {
id
}
});
};

View File

@@ -12,7 +12,7 @@ export interface ParkingCostVO {
/** /**
* 车位id * 车位id
*/ */
parkingId: string | number; parkingId: string;
/** /**
* 收费项目 0临时停车 * 收费项目 0临时停车
@@ -79,7 +79,7 @@ export interface ParkingCostForm extends BaseEntity {
/** /**
* 车位id * 车位id
*/ */
parkingId?: string | number; parkingId?: string;
/** /**
* 收费项目 0临时停车 * 收费项目 0临时停车
@@ -141,7 +141,7 @@ export interface ParkingCostQuery extends PageQuery {
/** /**
* 车位id * 车位id
*/ */
parkingId?: string | number; parkingId?: string;
/** /**
* 收费项目 0临时停车 * 收费项目 0临时停车

View File

@@ -0,0 +1,27 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 获取缴费方式,种类占比
* @param query
* @returns {*}
*/
export const income_statsApi = (
datetime?: string
): Promise<{
'totalResident': number;
'totalHouse': number;
'totalParking': number;
'totalDevice': number;
'totalMaterial': number;
'totalComplaint': number;
}> => {
return request({
url: '/api/analysis/income/stats',
method: 'get',
params: {
datetime: datetime
}
});
};

View File

@@ -0,0 +1,93 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
/**
* 查询入住情况
* @param query
* @returns {*}
*/
export const user_movein_list = (query?: {
datetime: string;
}): Promise<{
'totalResident': number;
'totalHouse': number;
'totalParking': number;
'totalDevice': number;
'totalMaterial': number;
'totalComplaint': number;
}> => {
return request({
url: '/workSpace/totalList',
method: 'get',
params: query
});
};
/**
* 查询居住人类型列表
* @param query
* @returns {*}
*/
export const get_movein_user_type = (query?: {
datetime: string;
}): Promise<{
'owner': string;
'ownerRate': string;
'tenant': string;
'tenantRate': string;
'relatives': string;
'relativeRate': string;
'other': string;
'otherRate': string;
'totalResident': string;
}> => {
return request({
url: '/residentType/selectTotal',
method: 'get',
params: query
});
};
/**
* 查询住户性别比例
* @param query
* @returns {*}
*/
export const get_movein_user_sex = (query?: {
datetime: string;
}): Promise<{
'man': number;
'woman': number;
'total': number;
'manRate': string;
'womanRate': string;
}> => {
return request({
url: '/gender/overview',
method: 'get',
params: query
});
};
/**
* 获取迁入迁出趋势图
* @param query
* @returns {*}
*/
export const get_movein_moveout_list = (query?: {
datetime: string;
}): Promise<
{
'date': string;
'inCount': number;
'outCount': number;
}[]
> => {
return request({
url: '/residentDailyStat/selectTotal',
method: 'get',
params: query
});
};

View File

@@ -22,7 +22,7 @@ export interface RepairVO {
/** /**
* 报修人id(住户表id) * 报修人id(住户表id)
*/ */
residentId: string | number; residentId: string;
/** /**
* 手机号码 * 手机号码
@@ -57,7 +57,7 @@ export interface RepairVO {
/** /**
* 维修人员id * 维修人员id
*/ */
repairId: string | number; repairId: string;
/** 名称(小区名-楼栋名-单元号-房间号) */ /** 名称(小区名-楼栋名-单元号-房间号) */
name?: string; name?: string;
} }
@@ -86,7 +86,7 @@ export interface RepairForm extends BaseEntity {
/** /**
* 报修人id(住户表id) * 报修人id(住户表id)
*/ */
residentId?: string | number; residentId?: string;
/** /**
* 手机号码 * 手机号码
@@ -163,7 +163,7 @@ export interface RepairQuery extends PageQuery {
/** /**
* 报修人id(住户表id) * 报修人id(住户表id)
*/ */
residentId?: string | number; residentId?: string;
/** /**
* 手机号码 * 手机号码

View File

@@ -2,7 +2,7 @@ export interface RepairProjectVO {
/** /**
* 维修项目表id * 维修项目表id
*/ */
id: string | number; id: string;
/** /**
* 维修项目名 * 维修项目名
@@ -12,7 +12,7 @@ export interface RepairProjectVO {
/** /**
* 父级项目id * 父级项目id
*/ */
parentId: string | number; parentId: string;
/** /**
* 项目类型 0公共报修 1个人报修 * 项目类型 0公共报修 1个人报修
@@ -23,7 +23,7 @@ export interface RepairProjectVO {
* 项目层级 01 * 项目层级 01
*/ */
projectLevel: number; projectLevel: number;
children?: [];
/** /**
* 排序 (数越小越靠前) * 排序 (数越小越靠前)
*/ */
@@ -39,7 +39,7 @@ export interface RepairProjectForm extends BaseEntity {
/** /**
* 维修项目表id * 维修项目表id
*/ */
id?: string | number; id?: string;
/** /**
* 维修项目名 * 维修项目名
@@ -49,7 +49,7 @@ export interface RepairProjectForm extends BaseEntity {
/** /**
* 父级项目id * 父级项目id
*/ */
parentId?: string | number; parentId?: string;
/** /**
* 项目类型 0公共报修 1个人报修 * 项目类型 0公共报修 1个人报修
@@ -101,7 +101,7 @@ export interface RepairProjectQuery extends PageQuery {
/** /**
* 父级项目id * 父级项目id
*/ */
parentId?: string | number; parentId?: string;
/** /**
* 项目类型 0公共报修 1个人报修 * 项目类型 0公共报修 1个人报修

View File

@@ -62,9 +62,10 @@ export const delBill = (id: string | number | Array<string | number>) => {
}); });
}; };
// ! =---------------=================================== 组件
export interface BillHouse { export interface BillHouse {
'buildingName': string; 'buildingName': string;
'houseId': number; 'houseId': string;
'unitNo': number; 'unitNo': number;
'houseNo': string; 'houseNo': string;
'floorNo': number; 'floorNo': number;
@@ -82,3 +83,39 @@ export const listBillHouse = (query: { pageNum: number; pageSize: number }): Axi
params: query params: query
}); });
}; };
// ! =---------------
export type BillUserlist = {
'gender': string;
'phone': string;
'house_name': string;
'residentName': string;
'houseNo': string;
'residentId': string;
'buildingId': string;
};
/**
* 查询业主列表
* @param data
* @returns {*}
*/
export const queryResidentList_holder = (query: { pageNum: number; pageSize: number }): AxiosPromise<BillUserlist[]> => {
return request({
url: '/bill/getResidentList',
method: 'GET',
params: query
});
};
/**
* 查询车位业主列表
* @param data
* @returns {*}
*/
export const queryResidentList_Cars = (query: { pageNum: number; pageSize: number }): AxiosPromise<BillUserlist[]> => {
return request({
url: '/bill/getParkingList',
method: 'GET',
params: query
});
};

View File

@@ -13,6 +13,7 @@ export interface BillVO {
* 收费项目id * 收费项目id
*/ */
chargeItemId: string; chargeItemId: string;
residentIds: string[];
/** /**
* 收费范围 0全体业主 1部分业主 * 收费范围 0全体业主 1部分业主
@@ -117,6 +118,8 @@ export interface BillQuery extends PageQuery {
*/ */
chargeItemId?: string | number; chargeItemId?: string | number;
// type: string;
/** /**
* 收费范围 0全体业主 1部分业主 * 收费范围 0全体业主 1部分业主
*/ */

View File

@@ -0,0 +1,61 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
export type cashlist = {
'residentId': string;
'residentName': string;
'houseName': string;
'phone': string;
};
/**
* 查询住户列表
* @param post
* @returns {*}
*/
export const cashQuery = (data: { residentName?: string; queryType: number }): AxiosPromise<cashlist[]> => {
return request({
url: '/cash/residentList',
method: 'post',
data: data
});
};
export type billlistType = {
'villageId': string;
'residentId': string;
'houseId': string;
'houseName': string;
'parkingId': string;
'parkingName': string;
'billId': string;
'chargeTypeName': string;
'chargeItemName': string;
'billingType': string;
'formula': string;
'fixedPrice': string;
'startDate': string;
'endDate': string;
'money': string;
'payStatus': string;
amount: string;
};
/**
* 查询账单列表
* @param post
* @returns {*}
*/
export const billQuery = (data: { residentId?: string; payStatus: '0' | '1' }): AxiosPromise<billlistType[]> => {
return request({
url: '/cash/billList',
method: 'post',
data: data
});
};
export const billEdit = (data: { 'detailsId': string[]; 'payType': string; 'payStatus': string }): AxiosPromise<billlistType[]> => {
return request({
url: '/cash/billEdit',
method: 'post',
data: data
});
};

View File

@@ -8,7 +8,7 @@ import { ChargeItemVO, ChargeItemForm, ChargeItemQuery } from './type';
* @returns {*} * @returns {*}
*/ */
export const listChargeItem = (query?: ChargeItemQuery): AxiosPromise<ChargeItemVO[]> => { export const listChargeItem = (query?: ChargeItemQuery | {}): AxiosPromise<ChargeItemVO[]> => {
return request({ return request({
url: '/chargeItem/list', url: '/chargeItem/list',
method: 'get', method: 'get',

View File

@@ -68,7 +68,7 @@ export const delElectricityDevice = (id: string | number | Array<string | number
*/ */
export const ElectricityDeviceMakerQrcode = (data: { deviceCode: string; id: string }[]) => { export const ElectricityDeviceMakerQrcode = (data: { deviceCode: string; id: string }[]) => {
return request({ return request({
url: '/electricityDevice/generateBarCodee', url: '/electricityDevice/generateBarCode',
method: 'POST', method: 'POST',
data: data data: data
}); });

View File

@@ -61,3 +61,39 @@ export const delTopupRecord = (id: string | number | Array<string | number>) =>
method: 'delete' method: 'delete'
}); });
}; };
export type TopupRecordList = {
'id': string;
'rechargeTime': string;
'villageName': string;
'residentName': string;
'houseName': string;
'fixedPriceUnit': string;
'fixedPrice': string;
'unit': string;
'deviceCode': string;
'orderId': string;
'rechargeAmount': string;
'topUpType': string;
};
/** 查询所有设备充值记录 */
export const getTopupRecordByDate = (query: {
startTime?: string;
endTime?: string;
pageSize: number;
pageNum: number;
}): AxiosPromise<TopupRecordList[]> => {
return request({
url: '/topupRecord/water/list',
method: 'get',
params: query
});
};
/** 导出 所有设备的 充值记录 */
export const exportTopupRecordByDate = (query: { startTime?: string; endTime?: string }): AxiosPromise<TopupRecordList[]> => {
return request({
url: '/topupRecord/water/export',
method: 'get',
params: query
});
};

View File

@@ -96,3 +96,14 @@ export const WaterDeviceUpdateBin = (data: { deviceCodes: string[]; urlA: string
data: data data: data
}); });
}; };
/**
* 获取设备最新的抄表记录
* @param deviceCodes
*/
export const latestRecord = (deviceCodes: string) => {
return request({
url: `/waterDevice/latestRecord/${deviceCodes}`,
method: 'GET'
});
};

View File

@@ -2,7 +2,7 @@ export interface WarehouseVO {
/** /**
* 仓库ID * 仓库ID
*/ */
id: string | number; id: string;
/** /**
* 仓库名称* * 仓库名称*
@@ -12,7 +12,7 @@ export interface WarehouseVO {
/** /**
* 仓库归属id*关联com_village * 仓库归属id*关联com_village
*/ */
villageId: number; villageId: string;
/** /**
* 联系人* * 联系人*
@@ -44,7 +44,7 @@ export interface WarehouseForm extends BaseEntity {
/** /**
* 仓库ID * 仓库ID
*/ */
id?: string | number; id?: string;
/** /**
* 仓库名称* * 仓库名称*

View File

@@ -8,7 +8,7 @@ import { AreaVO, AreaForm, AreaQuery } from '@/api/system/carArea/type';
* @returns {*} * @returns {*}
*/ */
export const listArea = (query?: AreaQuery): AxiosPromise<AreaVO[]> => { export const listArea = (query: AreaQuery | {} = {}): AxiosPromise<AreaVO[]> => {
return request({ return request({
url: '/area/list', url: '/area/list',
method: 'get', method: 'get',

View File

@@ -2,22 +2,22 @@ export interface CarVO {
/** /**
* 车辆管理表id * 车辆管理表id
*/ */
id: string | number; id: string;
/** /**
* 区域id * 区域id
*/ */
areaId: string | number; areaId: string;
/** /**
* 车位id * 车位id
*/ */
parkingId: string | number; parkingId: string;
/** /**
* 持有人id(住户表id) * 持有人id(住户表id)
*/ */
residentId: string | number; residentId: string;
/** /**
* 车牌号 * 车牌号

View File

@@ -12,7 +12,7 @@ export interface DeviceCategoryVO {
/** /**
* 状态 0正常 1停用 * 状态 0正常 1停用
*/ */
status: string; status: number;
/** /**
* 备注 * 备注
@@ -34,7 +34,7 @@ export interface DeviceCategoryForm extends BaseEntity {
/** /**
* 状态 0正常 1停用 * 状态 0正常 1停用
*/ */
status?: string; status?: number;
/** /**
* 备注 * 备注
@@ -51,7 +51,7 @@ export interface DeviceCategoryQuery extends PageQuery {
/** /**
* 状态 0正常 1停用 * 状态 0正常 1停用
*/ */
status?: string; status?: number;
/** /**
* 日期范围参数 * 日期范围参数

View File

@@ -1,4 +1,5 @@
export interface DeviceVO { export interface DeviceVO {
categoryName: string;
/** /**
* 主键ID * 主键ID
*/ */

View File

@@ -5,20 +5,19 @@ import { addBuildingType, addHouseType, BuildingUnit, BuildingVo } from './type'
// 楼栋--------------------------------------------------------------------------------------- // 楼栋---------------------------------------------------------------------------------------
/** 根据 小区id 和 类型 查询楼栋信息 */ /** 根据 小区id 和 类型 查询楼栋信息 */
export function getBuildinglistAPI(buildingUse: string): AxiosPromise<BuildingVo[]> { export function getBuildinglistAPI(): AxiosPromise<BuildingVo[]> {
return request({ return request({
url: '/building/byVillage', url: '/building/byVillage',
method: 'get', method: 'get',
params: { params: {}
buildingUse: buildingUse
}
}); });
} }
/** 获取所有楼栋 */ /** 获取所有楼栋 */
export function getBuildinglists(): AxiosPromise<BuildingVo[]> { export function getBuildinglists(query = {}): AxiosPromise<BuildingVo[]> {
return request({ return request({
url: '/building/list', url: '/building/list',
method: 'get' method: 'get',
params: query
}); });
} }
/** 获取单一楼栋 */ /** 获取单一楼栋 */
@@ -46,7 +45,7 @@ export function editBuildingApi(data: addBuildingType & { id: string }) {
}); });
} }
/** 删除楼栋 */ /** 删除楼栋 */
export function deleteBuildingApi(id: number) { export function deleteBuildingApi(id: string) {
return request({ return request({
url: `/building/${id}`, url: `/building/${id}`,
method: 'DELETE' method: 'DELETE'
@@ -55,9 +54,9 @@ export function deleteBuildingApi(id: number) {
// 房屋---------------------------------------------------------------------------------------------- // 房屋----------------------------------------------------------------------------------------------
/** 根据 楼栋id 查询房屋信息 */ /** 根据 楼栋id 查询房屋信息 */
export function getHouselistAPI(buildingId: string): AxiosPromise<BuildingUnit> { export function getHouselistAPI(buildingId: string, houseName: string = '', houseUseTypeId: string = ''): AxiosPromise<BuildingUnit> {
return request({ return request({
url: `/house/groupByUnitTree/${buildingId}`, url: `/house/groupByUnitTree/${buildingId}?houseName=${houseName}&houseUseTypeId=${houseUseTypeId}`,
method: 'get' method: 'get'
}); });
} }
@@ -100,3 +99,87 @@ export function getVillageTree(id: string) {
method: 'GET' method: 'GET'
}); });
} }
/** 根据小区id获取当前小区的树状结构 */
export function getHouseMain(id: string): Promise<Response> {
return request({
url: `/house/ByIdList?houseId=` + id,
method: 'GET'
});
}
export interface Response {
code: number;
data: Data;
msg: string;
}
export interface Data {
billResidentList: BillResidentList[];
chargeItemList: string[];
house: House;
parking: Parking;
residentList: ResidentList[];
storeroom: Storeroom;
}
export interface BillResidentList {
billCode: string;
billName: string;
chargeItemId: string;
chargeTypeName: string;
money: string;
payStatus: string;
useCount: string;
}
export interface House {
buildingName: string;
floorNo: number;
houseArea: string;
houseId: number;
houseNo: string;
houseType: string;
internalArea: string;
shareArea: string;
unitNoName: string;
villageName: string;
}
export interface ResidentList {
gender: string;
ownerRelationship: string;
phone: string;
residentId: string;
residentName: string;
residentStatus: string;
residentType: string;
}
export interface Storeroom {
buildingName: string;
houseArea: string;
internalArea: string;
shareArea: string;
storeroomId: number;
storeroomNo: string;
remark: string;
unitNoId: string;
unitNo: number;
}
export interface Parking {
'parkingArea': number;
'areaName': string;
'createTime': string;
'parkingId': string;
'parkingStatus': string;
'remark': string;
'parkingFloor': string;
'type': string;
'villageId': number;
'parkingCode': string;
'residentName': string;
'residentId': string;
'createName': string;
}

View File

@@ -47,14 +47,15 @@ export interface BuildingUnit {
buildingId: number; buildingId: number;
buildingName: string; buildingName: string;
floorCount: number; floorCount: number;
unitCount: number;
units: BuildingUnitlist[]; units: BuildingUnitlist[];
} }
// 单元 // 单元
export interface BuildingUnitlist { export interface BuildingUnitlist {
floors: FloorsType[]; floors: FloorsType[];
unitNo: number; unitNoName: string;
unitNoId: string;
} }
// 楼层 // 楼层
export interface FloorsType { export interface FloorsType {
floorNo: string; floorNo: string;
@@ -80,11 +81,14 @@ export interface addHouseType {
'houseId'?: string; 'houseId'?: string;
'buildingId'?: string; 'buildingId'?: string;
'villageId'?: string; 'villageId'?: string;
'unitNo': string; 'houseUseTypeId': string;
'unitNoId': string;
'floorNo': string; 'floorNo': string;
'houseNo': string; 'houseNo': string;
'houseArea': string; 'houseArea': string;
'internalArea': string; 'internalArea': string;
'shareArea': string; 'shareArea': string;
'houseType': string; 'houseType': string;
'storeroomId': string;
'parkingId': string;
} }

View File

@@ -81,3 +81,9 @@ export const uploadHouseImage = (formdata: FormData): AxiosPromise<uploadHouseIm
data: formdata data: formdata
}); });
}; };
export const getHouseOwnUser = (houseId: string): AxiosPromise<uploadHouseImagetype> => {
return request({
url: `/houseRental/ownerName/${houseId}`,
method: 'GET'
});
};

View File

@@ -2,7 +2,7 @@ export interface HouseRentalVO {
/** /**
* id 房屋租赁表id * id 房屋租赁表id
*/ */
id?: string | number; id: string | number;
/** /**
* 租户姓名 * 租户姓名
@@ -109,7 +109,7 @@ export interface HouseRentalForm extends BaseEntity {
/** /**
* 0 整租 1 合租 * 0 整租 1 合租
*/ */
rentalType?: number; rentalType?: string;
/** /**
* 房屋朝向 * 房屋朝向
@@ -171,7 +171,7 @@ export interface HouseRentalQuery extends PageQuery {
/** /**
* 房屋用途 * 房屋用途
*/ */
houseUse?: number; houseUse?: string;
/** /**
* 租户姓名 * 租户姓名
*/ */
@@ -185,7 +185,7 @@ export interface HouseRentalQuery extends PageQuery {
/** /**
* 0 整租 1 合租 * 0 整租 1 合租
*/ */
rentalType?: number; rentalType?: string;
/** /**
* 房屋朝向 * 房屋朝向

View File

@@ -0,0 +1,76 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { UnitNoVO, UnitNoForm, UnitNoQuery } from './type';
/**
* 查询单元列表
* @param query
* @returns {*}
*/
export const listUnitNo = (query?: UnitNoQuery): AxiosPromise<UnitNoVO[]> => {
return request({
url: '/unitNo/list',
method: 'get',
params: query
});
};
/**
* 查询单元详细
* @param id
*/
export const getUnitNo = (id: string | number): AxiosPromise<UnitNoVO> => {
return request({
url: '/unitNo/' + id,
method: 'get'
});
};
/**
* 新增单元
* @param data
*/
export const addUnitNo = (data: UnitNoForm) => {
return request({
url: '/unitNo',
method: 'post',
data: data
});
};
/**
* 修改单元
* @param data
*/
export const updateUnitNo = (data: UnitNoForm) => {
return request({
url: '/unitNo',
method: 'put',
data: data
});
};
/**
* 删除单元
* @param id
*/
export const delUnitNo = (id: string | number | Array<string | number>) => {
return request({
url: '/unitNo/' + id,
method: 'delete'
});
};
/**
* 根据 楼栋id 获取单元
* @param id
*/
export const getByBUildingidtoUnitNo = (id: string) => {
return request({
url: '/unitNo/byBuildingList',
method: 'GET',
params: {
buildingId: id
}
});
};

View File

@@ -0,0 +1,65 @@
export interface UnitNoVO {
/**
* 单元表id
*/
id: string | number;
/**
* 单元名称
*/
name: string;
/**
* 小区id
*/
villageId: string | number;
/**
* 楼栋id
*/
buildingId: string | number;
}
export interface UnitNoForm extends BaseEntity {
/**
* 单元表id
*/
id?: string | number;
/**
* 单元名称
*/
name?: string;
/**
* 小区id
*/
villageId?: string | number;
/**
* 楼栋id
*/
buildingId?: string | number;
}
export interface UnitNoQuery extends PageQuery {
/**
* 单元名称
*/
name?: string;
/**
* 小区id
*/
villageId?: string | number;
/**
* 楼栋id
*/
buildingId?: string | number;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,70 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { HouseUseTypeVO, HouseUseTypeForm, HouseUseTypeQuery } from './type';
/**
* 查询房屋类型列表
* @param query
* @returns {*}
*/
export const listHouseUseType = (query?: HouseUseTypeQuery): AxiosPromise<HouseUseTypeVO[]> => {
return request({
url: '/houseUseType/list',
method: 'get',
params: query
});
};
export const listHouseUseType_add_house = (query?: HouseUseTypeQuery): AxiosPromise<HouseUseTypeVO[]> => {
return request({
url: '/houseUseType/getList',
method: 'get',
params: query
});
};
/**
* 查询房屋类型详细
* @param id
*/
export const getHouseUseType = (id: string | number): AxiosPromise<HouseUseTypeVO> => {
return request({
url: '/houseUseType/' + id,
method: 'get'
});
};
/**
* 新增房屋类型
* @param data
*/
export const addHouseUseType = (data: HouseUseTypeForm) => {
return request({
url: '/houseUseType',
method: 'post',
data: data
});
};
/**
* 修改房屋类型
* @param data
*/
export const updateHouseUseType = (data: HouseUseTypeForm) => {
return request({
url: '/houseUseType',
method: 'put',
data: data
});
};
/**
* 删除房屋类型
* @param id
*/
export const delHouseUseType = (id: string | number | Array<string | number>) => {
return request({
url: '/houseUseType/' + id,
method: 'delete'
});
};

View File

@@ -0,0 +1,35 @@
export interface HouseUseTypeVO {
/**
* 房屋类型id
*/
id: string | number;
/**
* 用途名称
*/
name: string;
}
export interface HouseUseTypeForm extends BaseEntity {
/**
* 房屋类型id
*/
id?: string | number;
/**
* 用途名称
*/
name?: string;
}
export interface HouseUseTypeQuery extends PageQuery {
/**
* 用途名称
*/
name?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -61,3 +61,43 @@ export const delParking = (id: string | number | Array<string | number>) => {
method: 'delete' method: 'delete'
}); });
}; };
/**
* 改变车位审核装填
* @param id
*/
export const getParkingReview = (data: { id: string; propertyReviewFeedback: string; propertyStatus: number }) => {
return request({
url: '/parkingReview/checkStatus',
method: 'POST',
data
});
};
/**
* 获取车位审核信息
* @param id
*/
export const getParkingReviewInfo = (id: string) => {
return request({
url: '/parking/checkInfo',
method: 'POST',
data: {
id
}
});
};
/**
* 查询小区内车位列表
* @param { string } villageid
* @returns {*}
*/
export const GetVillageListParking = (villageid: string, houseId: string = undefined): AxiosPromise<ParkingVO[]> => {
return request({
url: `/parking/getList`,
method: 'POST',
data: {
villageId: villageid,
houseId: houseId
}
});
};

View File

@@ -32,7 +32,7 @@ export interface ParkingVO {
/** /**
* 住户id * 住户id
*/ */
residentId: string | number; residentId: string;
/** /**
* 添加时间 * 添加时间
@@ -88,7 +88,7 @@ export interface ParkingForm extends BaseEntity {
/** /**
* 住户id * 住户id
*/ */
residentId?: string | number; residentId?: string;
/** /**
* 添加时间 * 添加时间
@@ -160,7 +160,7 @@ export interface ParkingQuery extends PageQuery {
/** /**
* 住户id * 住户id
*/ */
residentId?: string | number; residentId?: string;
/** /**
* 添加时间 * 添加时间

View File

@@ -37,7 +37,9 @@ export interface QuestionnaireVO {
/** /**
* , * ,
*/ */
residents: string | number; residents: string;
startTime: string;
endTime: string;
/** /**
* *
@@ -69,7 +71,7 @@ export interface QuestionnaireForm extends BaseEntity {
/** /**
* JSON数组 * JSON数组
*/ */
content?: string; content?: string | QuestionComponentItem[];
/** /**
* 0-稿 1- 2- * 0-稿 1- 2-
@@ -158,6 +160,6 @@ export interface QuestionComponentItem {
export interface QuestionComponentnaireForm { export interface QuestionComponentnaireForm {
id?: string | number | null; id?: string | number | null;
title: string; title: string;
description: string; description?: string;
content: QuestionComponentItem[]; content: QuestionComponentItem[];
} }

View File

@@ -22,7 +22,7 @@ export interface ResidentVO {
/** /**
* 0 男性 1女性 * 0 男性 1女性
*/ */
gender: number; gender: string;
/** /**
* 身份证号 * 身份证号
@@ -90,14 +90,17 @@ export interface ResidentForm extends BaseEntity {
name?: string; name?: string;
/** /**
* 0业主 1租户 * 0业主
* 1亲属
* 2租户
* 3朋友
*/ */
type?: string; ownerRelationship?: string;
/** /**
* 0 男性 1女性 * 0 男性 1女性
*/ */
gender?: number; gender?: string;
/** /**
* 身份证号 * 身份证号
@@ -152,10 +155,12 @@ export interface ResidentQuery extends PageQuery {
date?: any; date?: any;
/** /**
* 住户类型 * 0业主
* 0业主 1租户 * 1亲属
* 2租户
* 3朋友
*/ */
type?: string; ownerRelationship?: string;
/** /**
* 住户状态 * 住户状态
* 0审核中 1 已认证 * 0审核中 1 已认证
@@ -165,5 +170,5 @@ export interface ResidentQuery extends PageQuery {
houseId?: number; houseId?: number;
villageId?: string; villageId?: string;
buildingId?: number; buildingId?: number;
unitNo?: number; unitNoId?: number;
} }

View File

@@ -61,3 +61,28 @@ export const delStoreroom = (id: string | number | Array<string | number>) => {
method: 'delete' method: 'delete'
}); });
}; };
/**
* 查询小区内储藏室管理列表
* @param query
* @returns {*}
*/
export const GetVillagelistStoreroom = (
data: { villageid: string; unitNoId: string; houseId?: string; buildingId: string } = {
villageid: null,
buildingId: null,
houseId: null,
unitNoId: null
}
): AxiosPromise<StoreroomVO[]> => {
return request({
url: `/storeroom/getList`,
method: 'POST',
data: {
villageId: data.villageid,
buildingId: data.buildingId,
unitNoId: data.unitNoId,
houseId: data.houseId
}
});
};

View File

@@ -2,17 +2,18 @@ export interface StoreroomVO {
/** /**
* 储藏室ID * 储藏室ID
*/ */
id: string | number; id: string;
/** /**
* 所属楼栋ID * 所属楼栋ID
*/ */
buildingId: string | number; buildingId: string;
/** /**
* 单元号 * 单元号
*/ */
unitNo: string | number; unitNoId: string | number;
unitName: string;
/** /**
* 储藏室号 * 储藏室号
@@ -54,12 +55,12 @@ export interface StoreroomForm extends BaseEntity {
/** /**
* 储藏室ID * 储藏室ID
*/ */
id?: string | number; id?: string;
/** /**
* 所属楼栋ID * 所属楼栋ID
*/ */
buildingId?: string | number; buildingId?: string;
/** /**
* 单元号 * 单元号
@@ -106,7 +107,7 @@ export interface StoreroomQuery extends PageQuery {
/** /**
* 所属楼栋ID * 所属楼栋ID
*/ */
buildingId?: string | number; buildingId?: string;
/** /**
* 单元号 * 单元号

View File

@@ -29,7 +29,7 @@ export interface UserQuery extends PageQuery {
* 用户返回对象 * 用户返回对象
*/ */
export interface UserVO extends BaseEntity { export interface UserVO extends BaseEntity {
userId: string | number; userId: string;
tenantId: string; tenantId: string;
deptId: number; deptId: number;
userName: string; userName: string;
@@ -70,8 +70,8 @@ export interface UserForm {
sex?: string; sex?: string;
status: string; status: string;
remark?: string; remark?: string;
postIds: string[]; postIds?: string[];
roleIds: string[]; roleIds?: string[];
} }
export interface UserInfoVO { export interface UserInfoVO {

View File

@@ -1,8 +1,9 @@
export interface CategoryTreeVO { export interface CategoryTreeVO {
id: number | string; id: string;
label: string; label: string;
parentId: number | string; parentId: number | string;
weight: number; weight: number;
categoryName: string;
children: CategoryTreeVO[]; children: CategoryTreeVO[];
} }
export interface CategoryVO { export interface CategoryVO {

View File

@@ -12,6 +12,8 @@ export const workSpaceTotalListApi = (
totalResident: number; totalResident: number;
totalHouse: number; totalHouse: number;
totalParking: number; totalParking: number;
totalDevice: number;
totalMaterial: number;
totalComplaint: number; totalComplaint: number;
}> => { }> => {
return request({ return request({
@@ -22,3 +24,300 @@ export const workSpaceTotalListApi = (
} }
}); });
}; };
/**
* 查询近一周报修趋势
* @param query
* @returns {*}
*/
export const workSpaceRepairTrendListApi = (
villageId: string
): Promise<
{
date: string;
count: number;
}[]
> => {
return request({
url: '/workSpace/last7DaysRepairCount',
method: 'GET',
params: {
villageId: villageId
}
});
};
/**
* 查询近一周收入趋势
* @param query
* @returns {*}
*/
export const workSpaceIncomeTrendListApi = (
villageId: string,
type: number
): Promise<
{
date: string;
income: number;
}[]
> => {
return request({
url: '/workSpace/last7DaysIncomeTrend',
method: 'GET',
params: {
villageId: villageId,
type: type
}
});
};
/**
* 查询当日天气
* @param query
* @returns {*}
*/
export const workSpaceWeatherApi = (city: string): Promise<reponseType> => {
const url = 'https://uapis.cn/api/v1/misc/weather?city=' + city + '&lang=zh';
return new Promise((resolve, reject) => {
fetch(url, {
method: 'GET'
})
.then((res) => res.json())
.then((res) => {
resolve(res);
})
.catch((err) => {
reject(err);
});
});
};
type reponseType = {
// 省份
'province': string;
// 城市名
'city': string;
// 区县或更细一级的行政区名称。自动按 IP 定位时更常见。
'district': string;
// 行政区划代码(部分数据源可能为空)
'adcode': string;
// 天气状况描述。默认返回中文,传 `lang=en` 时返回英文。非固定枚举。
'weather': string;
// 天气图标代码。请从[天气图标代码表](#enum-list)中查看所有可能的值。
'weather_icon': string;
// 当前温度 °C
'temperature': number;
// 风向
'wind_direction': string;
// 风力等级
'wind_power': string;
// 相对湿度 %
'humidity': number;
// 数据更新时间
'report_time': string;
// 体感温度 °Cextended=true 时返回)
'feels_like': number;
// 能见度 kmextended=true 时返回)
'visibility': number;
// 气压 hPaextended=true 时返回)
'pressure': number;
// 紫外线指数extended=true 时返回)
'uv': number;
// 当前降水量 mmextended=true 时返回)
'precipitation': number;
// 云量 %extended=true 时返回)
'cloud': number;
// 空气质量指数 0-500extended=true 时返回)
'aqi': number;
// AQI 等级 1-6extended=true 时返回)
'aqi_level': number;
// AQI 等级描述(优/良/轻度污染/中度污染/重度污染/严重污染extended=true 时返回)
'aqi_category': string;
// 主要污染物(如 PM2.5、PM10、O3 等extended=true 时返回)
'aqi_primary': string;
// 空气污染物分项数据extended=true 时返回,部分数据源可能不返回)
'air_pollutants': {
// PM2.5 μg/m³
'pm25': number;
// PM10 μg/m³
'pm10': number;
// 臭氧 μg/m³
'o3': number;
// 二氧化氮 μg/m³
'no2': number;
// 二氧化硫 μg/m³
'so2': number;
// 一氧化碳 mg/m³
'co': number;
};
// 官方气象预警列表(存在有效预警时返回)
'alerts': {
// 预警标题
'title': string;
// 预警类型,如雷电、暴雨
'type': string;
// 预警级别,如蓝色、黄色、橙色、红色
'level': string;
// 预警正文
'text': string;
// 预警发布时间
'publish_time': string;
// 发布单位
'publisher': string;
// 防御指引列表
'guidance': string[];
}[];
// 当天最高温 °Cforecast=true 时返回)
'temp_max': number;
// 当天最低温 °Cforecast=true 时返回)
'temp_min': number;
// 多天天气预报最多7天forecast=true 时返回)
'forecast': {
// 日期 YYYY-MM-DD
'date': string;
// 星期几(`lang=en` 时返回英文星期)
'week': string;
// 最高温度 °C
'temp_max': number;
// 最低温度 °C
'temp_min': number;
// 白天天气(`lang=en` 时返回英文)
'weather_day': string;
// 夜间天气(`lang=en` 时返回英文)
'weather_night': string;
// 白天风向(可选,`lang=en` 时返回英文)
'wind_dir_day': string;
// 夜间风向(可选,`lang=en` 时返回英文)
'wind_dir_night': string;
// 白天风力(可选,`lang=en` 时返回英文)
'wind_scale_day': string;
// 夜间风力(可选,`lang=en` 时返回英文)
'wind_scale_night': string;
// 白天风速 km/h可选
'wind_speed_day': number;
// 湿度 %(可选)
'humidity': number;
// 降水量 mm可选
'precip': number;
// 能见度 km可选
'visibility': number;
// 紫外线指数(可选)
'uv_index': number;
// 日出时间 HH:MM可选
'sunrise': string;
// 日落时间 HH:MM可选
'sunset': string;
}[];
// 逐小时预报最多24小时hourly=true 时返回)
'hourly_forecast': {
// 预报时间ISO8601 或 YYYY-MM-DD HH:MM
'time': string;
// 温度 °C
'temperature': number;
// 天气状况
'weather': string;
// 风向(可选)
'wind_direction': string;
// 风速 km/h可选
'wind_speed': number;
// 风力等级(可选)
'wind_scale': string;
// 湿度 %(可选)
'humidity': number;
// 降水量 mm可选
'precip': number;
// 体感温度 °C可选
'feels_like': number;
// 能见度 km可选
'visibility': number;
// 降水概率 %(可选)
'pop': number;
// 紫外线指数(可选,国内城市通常不返回)
'uv_index': number;
}[];
// 分钟级降水预报minutely=true 时返回仅国内城市可用精确到2分钟
'minutely_precip': {
// 降水描述
'summary': string;
// 更新时间
'update_time': string;
// 精确到2分钟的数据点
'data': {
// 预报时间 ISO8601
'time': string;
// 该时间点的降水量 mm
'precip': number;
// 降水类型rain / snow
'type': string;
}[];
};
// 18项生活指数indices=true 时返回),每项包含 level等级名称、brief简短描述、advice详细建议
'life_indices': LifeIndices;
};
// 1. 定义单个生活指数的基础结构
type LifeIndexItem = {
level: string;
brief: string;
advice: string;
};
// 2. 定义所有可能的键名
type LifeIndexKeys =
| 'clothing'
| 'uv'
| 'car_wash'
| 'drying'
| 'air_conditioner'
| 'cold_risk'
| 'exercise'
| 'comfort'
| 'travel'
| 'fishing'
| 'allergy'
| 'sunscreen'
| 'mood'
| 'beer'
| 'umbrella'
| 'traffic'
| 'air_purifier'
| 'pollen';
// 3. 使用映射类型自动生成完整结构
type LifeIndices = {
[K in LifeIndexKeys]: LifeIndexItem;
};
/**
* 查询 快捷入口
* @param query
* @returns {*}
*/
export const getQuicklyshortcut = () => {
return request({
url: '/workSpace/shortcut/list',
method: 'GET'
});
};
/**
* 添加 快捷入口
* @param query
* @returns {*}
*/
export const addQuicklyshortcut = (data) => {
return request({
url: '/workSpace/shortcut/add',
method: 'POST',
data
});
};
/**
* 删除 快捷入口
* @param query
* @returns {*}
*/
export const delQuicklyshortcut = (id) => {
return request({
url: '/workSpace/shortcut/remove/' + id,
method: 'DELETE'
});
};

View File

@@ -5,7 +5,7 @@ export interface FlowInstanceQuery extends PageQuery {
nodeName?: string; nodeName?: string;
flowCode?: string; flowCode?: string;
flowName?: string; flowName?: string;
createByIds?: string[] | number[]; createByIds?: string[];
businessId?: string; businessId?: string;
} }

View File

@@ -33,7 +33,7 @@ html.dark svg {
#app { #app {
height: 100%; height: 100%;
min-width: 1200px; /* 最小宽度,防止太小变形 */ min-width: 1024px; /* 最小宽度,防止太小变形 */
} }
*, *,
@@ -255,3 +255,35 @@ aside {
width: 960px; width: 960px;
} }
} }
.el-button {
border-radius: 5px;
}
.bgColor {
width: 850px;
height: 850px;
background: #fdf1ee;
filter: blur(200px);
display: block;
position: absolute;
top: 0;
right: 0;
transform: translateY(-50%) translateX(0%);
pointer-events: none;
z-index: 1;
border-radius: 50%;
@media (max-width: 1540px) {
width: 800px;
height: 400px;
}
@media (max-width: 1280px) {
width: 600px;
height: 300px;
}
@media (max-width: 1030px) {
width: 400px;
height: 200px;
}
}

View File

@@ -11,6 +11,7 @@
position: relative; position: relative;
overflow-x: hidden; overflow-x: hidden;
background: var(--el-bg-color-page); background: var(--el-bg-color-page);
background: #dfedff;
} }
.sidebarHide { .sidebarHide {

View File

@@ -28,7 +28,7 @@
// Modern rounded style + soft shadows // Modern rounded style + soft shadows
--app-radius-sm: 6px; --app-radius-sm: 6px;
--app-radius-md: 10px; --app-radius-md: 5px;
--app-radius-lg: 14px; --app-radius-lg: 14px;
--app-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08), 0 6px 16px rgba(15, 23, 42, 0.08); --app-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08), 0 6px 16px rgba(15, 23, 42, 0.08);
--app-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.12); --app-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.12);

View File

@@ -3,7 +3,7 @@
<transition-group name="breadcrumb"> <transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path"> <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
<span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta?.title }}</span> <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta?.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta?.title }}</a> <span class="no-redirect" v-else>{{ item.meta?.title }}</span>
</el-breadcrumb-item> </el-breadcrumb-item>
</transition-group> </transition-group>
</el-breadcrumb> </el-breadcrumb>

View File

@@ -0,0 +1,294 @@
<template>
<slot name="default" />
<el-dialog append-to-body v-model="dialog.dialogVisible" :title="dialog.dialogTitle" width="1050">
<el-table ref="multipleTableRef" :data="tableData" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" width="55" type="index" />
<el-table-column label="业主" align="center">
<template #default="scope">
<span>{{ scope.row.residentName ? scope.row.residentName : '---' }}</span>
</template>
</el-table-column>
<el-table-column label="性别" align="center">
<template #default="scope">
<dict-tag :options="sys_user_sex" :value="scope.row.gender"></dict-tag>
</template>
</el-table-column>
<el-table-column label="手机号" align="center" prop="phone" />
<el-table-column label="车位" align="center" prop="parkingName" />
</el-table>
<template #footer>
<div class="dialog-footer flex flex-items-center flex-justify-between">
<pagination
style="margin-top: 0"
layout="total, prev, pager, next, jumper"
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getlist"
/>
<div>
<el-button @click="conback">退出</el-button>
<el-button type="primary" @click="confirm"> 确定 </el-button>
</div>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { getBuildinglistAPI, getHouselistAPI } from '@/api/system/house';
import { BillUserlist, queryResidentList_Cars, queryResidentList_holder } from '@/api/system/SdbBill';
import { ref, nextTick, getCurrentInstance, ComponentInternalInstance, toRefs } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex } = toRefs<any>(proxy?.useDict('sys_user_sex'));
const props = defineProps({
returnvalue: {
type: String,
default: 'parkingId'
},
titleName: {
type: String,
default: '选择业主'
},
/** 是否多选 false 单选 */
isMultiple: {
type: Boolean,
default: false
}
});
const { titleName, returnvalue } = toRefs(props);
const userid = ref([]);
// ✅ 手动设置默认值(替代 withDefaults
const emit = defineEmits<{
change: [value: string | number | (string | number)[] | BillUserlist | BillUserlist[]]; // 支持返回数组
}>();
const dialog = ref({
dialogVisible: false,
dialogTitle: titleName.value
});
const queryParams = ref({
pageNum: 1,
pageSize: 10
});
const multipleTableRef = ref();
const tableData = ref<BillUserlist[]>([]);
const total = ref(0);
const loading = ref(false);
const isFetching = ref(false);
// 存储选中项
const multipleSelection = ref<BillUserlist[]>([]);
const isMultiple = computed(() => props.isMultiple ?? false);
// 优化 handleUserSelection避免重复查找
const handleUserSelection = () => {
isFetching.value = true;
if (!multipleTableRef.value || !tableData.value.length) {
isFetching.value = false;
return;
}
const rowsToSelect = tableData.value.filter((row) => {
return multipleSelection.value.some((selected) => selected.parkingId === row.parkingId);
});
multipleTableRef.value.clearSelection();
if (rowsToSelect.length > 0) {
nextTick(() => {
rowsToSelect.forEach((row) => {
multipleTableRef.value?.toggleRowSelection(row, true);
});
isFetching.value = false;
});
} else {
isFetching.value = false;
}
};
// ====================== 方法 ======================
// 获取列表
function getlist() {
isFetching.value = true; // 加锁
loading.value = true;
queryResidentList_Cars(queryParams.value)
.then((res) => {
tableData.value = res.rows;
total.value = res.total;
handleUserSelection();
})
.catch((error) => {
console.error('获取用户列表失败:', error);
})
.finally(() => {
loading.value = false;
isFetching.value = false; // 加锁
});
}
/** 楼栋列表 */
const buildinglist = ref([]);
/** 房屋列表 */
const unitlist = ref([]);
/** 筛选房屋类型 */
function handleChangeHouse(val: string) {
getBuildinglistAPI().then((res) => {
buildinglist.value = res.data;
});
}
/** 单元列表 */
function handleChangebuilding(val: string) {
getHouselistAPI(val).then((res) => {
unitlist.value = res.data.units;
});
}
// ==============================================
// ✅ 新增:处理 selection-change多选专用
// ==============================================
function handleSelectionChange(selection: BillUserlist[]) {
if (isFetching.value) return;
if (isMultiple.value) {
const currentSelectedIds = new Set(selection.map((item) => item.parkingId));
const otherPageSelections = multipleSelection.value.filter((item) => {
const isCurrentPageItem = tableData.value.some((row) => row.parkingId === item.parkingId);
if (isCurrentPageItem) {
return currentSelectedIds.has(item.parkingId);
} else {
return true;
}
});
// 3. 合并:其他页保留的 + 当前页新选中的
const finalSelection = [...otherPageSelections, ...selection];
// 4. 去重(以防万一)
const uniqueMap = new Map();
finalSelection.forEach((item) => uniqueMap.set(item.parkingId, item));
multipleSelection.value = Array.from(uniqueMap.values());
console.log(multipleSelection.value.map((item) => item.parkingId));
} else {
// --- 单选逻辑 ---
if (selection.length > 0) {
multipleSelection.value = [selection[selection.length - 1]];
} else {
multipleSelection.value = [];
}
}
}
// ==============================================
// 最佳单选方案:点击行选中,无报错、无循环、最稳定
// ==============================================
// 优化 handleRowClick避免重复
function handleRowClick(row: BillUserlist) {
if (!multipleTableRef.value) return;
if (isMultiple.value) {
const index = multipleSelection.value.findIndex((item) => item.parkingId === row.parkingId);
if (index > -1) {
// 取消选中
multipleSelection.value.splice(index, 1);
multipleTableRef.value?.toggleRowSelection(row, false);
} else {
// 选中
multipleSelection.value.push(row);
multipleTableRef.value?.toggleRowSelection(row, true);
}
} else {
// 【单选模式】先清空,再选中当前行
multipleSelection.value = [];
multipleTableRef.value.clearSelection();
multipleSelection.value = [row];
multipleTableRef.value.toggleRowSelection(row, true);
}
}
// 重置查询
function resetQuery() {
queryParams.value = {
pageNum: 1,
pageSize: 10
};
getlist();
}
// 退出
function conback() {
dialog.value.dialogVisible = false;
resetQuery();
}
// ==============================================
// ✅ 核心修改:根据 returnvalue 返回对应的值
// ==============================================
function confirm() {
dialog.value.dialogVisible = false;
if (isMultiple.value) {
// 多选
if (returnvalue.value === '*') {
// ✅ 返回整个对象数组
emit('change', multipleSelection.value);
} else {
// 返回指定字段数组
emit(
'change',
multipleSelection.value.map((item) => item[returnvalue.value as keyof BillUserlist])
);
}
} else {
// 单选
if (returnvalue.value === '*') {
// ✅ 返回整个对象
emit('change', multipleSelection.value[0]);
} else {
// 返回指定字段
emit('change', multipleSelection.value[0][returnvalue.value as keyof BillUserlist]);
}
}
multipleSelection.value = [];
multipleTableRef.value.clearSelection();
}
defineExpose({
open: (arr: string[] = []) => {
userid.value = arr;
if (arr && arr.length > 0) {
multipleSelection.value = arr.map((id) => ({
parkingId: id
}));
console.log(multipleSelection.value);
} else {
multipleSelection.value = [];
}
getlist();
dialog.value.dialogVisible = true;
}
});
</script>
<style scoped lang="scss">
.residentBox {
width: 385px;
height: 35px;
line-height: 35px;
border: 1px solid #ccc;
border-radius: 5px;
padding: 0 10px;
.defaultbox {
display: flex;
align-items: center;
justify-content: space-between;
}
cursor: pointer;
&:hover {
background-color: #f4f8ff;
}
}
</style>

View File

@@ -1,46 +1,68 @@
<template> <template>
<slot name="default" /> <slot name="default" />
<el-dialog append-to-body v-model="dialog.dialogVisible" :title="dialog.dialogTitle" width="1050"> <el-dialog append-to-body v-model="dialog.dialogVisible" :title="dialog.dialogTitle" width="1400">
<nav class="flex flex-items-center mb-8"> <nav class="flex flex-items-center mb-8 justify-between">
<div class="flex flex-items-center"> <!-- <div class="flex flex-items-center">
<div style="width: 70px; text-align: right; padding-right: 10px">房屋类型</div> <div style="width: 70px; text-align: right; padding-right: 10px">房屋类型</div>
<div class="flex-1"> <div class="flex-1">
<el-select @change="handleChangeHouse" style="width: 140px" v-model="queryParams.buildingUse"> <el-select @change="handleChangeHouse" style="width: 140px" v-model="queryParams.houseUseTypeId">
<el-option v-for="(item, index) in com_build_use" :key="index" :label="item.label" :value="item.value"></el-option> <el-option v-for="(item, index) in houseUseTypeList" :key="index" :label="item.name" :value="item.id"></el-option>
</el-select> </el-select>
</div> </div>
</div> </div> -->
<div class="flex flex-items-center"> <div class="flex flex-items-center">
<div style="width: 60px; text-align: right; padding-right: 10px">楼栋</div> <div class="flex flex-items-center">
<div style="width: 50px; text-align: right; padding-right: 10px">楼栋</div>
<div class="flex-1"></div> <div class="flex-1"></div>
<el-select @change="handleChangebuilding" style="width: 140px" v-model="queryParams.buildingId"> <el-select @change="handleChangebuilding" clearable style="width: 120px" v-model="queryParams.buildingId">
<el-option v-for="(item, index) in buildinglist" :key="index" :label="item.buildingName" :value="item.id"></el-option> <el-option v-for="(item, index) in buildinglist" :key="index" :label="item.buildingName" :value="item.id"></el-option>
</el-select> </el-select>
</div> </div>
<div class="flex flex-items-center mr-20px"> <div class="flex flex-items-center mr-20px">
<div style="width: 60px; text-align: right; padding-right: 10px">单元</div> <div style="width: 50px; text-align: right; padding-right: 10px">单元</div>
<div class="flex-1"> <div class="flex-1">
<el-select style="width: 140px" v-model="queryParams.unitNo"> <el-select @change="handleChangeUnit" clearable style="width: 120px" v-model="queryParams.unitNoId">
<el-option v-for="(item, index) in unitlist" :key="index" :label="`${item.unitNo}单元`" :value="item.unitNo"></el-option> <el-option v-for="(item, index) in unitlist" :key="index" :label="item.unitNoName" :value="item.unitNoId"></el-option>
</el-select> </el-select>
</div> </div>
</div> </div>
<div class="flex flex-items-center mr-20px">
<div style="width: 80px; text-align: right; padding-right: 10px">房屋类型</div>
<div class="flex-1">
<el-select clearable @change="handleHouseUseChange" style="width: 120px" v-model="queryParams.houseUseTypeId">
<el-option v-for="(item, index) in houseUseTypeList" :key="index" :label="item.name" :value="item.id"></el-option>
</el-select>
</div>
</div>
<div class="flex flex-items-center mr-20px">
<div style="width: 60px; text-align: right; padding-right: 10px">房屋号</div>
<div class="flex-1">
<el-input clearable @clear="handleClear" style="width: 120px" v-model="queryParams.houseNo" placeholder="请输入房屋号"></el-input>
</div>
</div>
<div class="flex flex-items-center mr-20px">
<div style="width: 80px; text-align: right; padding-right: 10px">业主名称</div>
<div class="flex-1">
<el-input clearable @clear="handleClear" style="width: 120px" v-model="queryParams.residentName" placeholder="请输入业主名称"></el-input>
</div>
</div>
</div>
<div> <div>
<el-button type="primary" @click="getlist">查询</el-button> <el-button type="primary" @click="getlist">查询</el-button>
<el-button @click="resetQuery">重置</el-button> <el-button @click="resetQuery">重置</el-button>
</div> </div>
</nav> </nav>
<el-table @row-click="handleRowClick" ref="multipleTableRef" :data="tableData" border @selection-change="handleSelectionChange"> <el-table row-key="houseId" v-loading="loading" ref="multipleTableRef" :data="tableData" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" width="55" type="index" /> <el-table-column label="序号" align="center" width="55" type="index" />
<el-table-column label="小区" align="center" prop="villageName" /> <el-table-column label="小区" align="center" prop="villageName" />
<el-table-column label="楼栋" align="center" prop="buildingName" /> <el-table-column label="楼栋" align="center" prop="buildingName" />
<el-table-column label="单元" align="center" prop="unitNo" /> <el-table-column label="单元" align="center" prop="unitNoName" />
<el-table-column label="房间" align="center" prop="houseNo" /> <el-table-column label="房间" align="center" prop="houseNo" />
<el-table-column label="业主" align="center"> <el-table-column label="业主" align="center">
<template #default="scope"> <template #default="scope">
<span>{{ scope.row.userName ? scope.row.userName : '---' }}</span> <span>{{ scope.row.residentName ? scope.row.residentName : '---' }}</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -66,18 +88,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { getBuildinglistAPI, getBuildingOnly, getHouselistAPI } from '@/api/system/house'; import { getBuildinglistAPI, getBuildingOnly, getHouselistAPI } from '@/api/system/house';
import { listHouseUseType, listHouseUseType_add_house } from '@/api/system/houseUse';
import { BillHouse, listBillHouse } from '@/api/system/SdbBill'; import { BillHouse, listBillHouse } from '@/api/system/SdbBill';
import { ref, watch, nextTick, getCurrentInstance, ComponentInternalInstance, toRefs } from 'vue'; import { ref, watch, nextTick, getCurrentInstance, ComponentInternalInstance, toRefs } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; // const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex } = toRefs<any>(proxy?.useDict('sys_user_sex')); // const { sys_user_sex } = toRefs<any>(proxy?.useDict('sys_user_sex'));
const { com_build_use } = toRefs<any>(proxy?.useDict('com_build_use')); // const { com_build_use } = toRefs<any>(proxy?.useDict('com_build_use'));
const props = defineProps({ const props = defineProps({
userid: {
type: String,
default: ''
},
returnvalue: { returnvalue: {
type: String, type: String,
default: 'houseId' default: 'houseId'
@@ -93,7 +112,9 @@ const props = defineProps({
} }
}); });
const { userid, titleName, returnvalue } = toRefs(props); const { titleName, returnvalue } = toRefs(props);
const showids = ref([]);
// ✅ 手动设置默认值(替代 withDefaults // ✅ 手动设置默认值(替代 withDefaults
const emit = defineEmits<{ const emit = defineEmits<{
@@ -108,9 +129,11 @@ const dialog = ref({
const queryParams = ref({ const queryParams = ref({
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
buildingUse: null, houseNo: '',
buildingId: null, buildingId: null,
unitNo: null unitNoId: null,
houseUseTypeId: null,
residentName: ''
}); });
const multipleTableRef = ref(); const multipleTableRef = ref();
@@ -125,30 +148,35 @@ const isMultiple = computed(() => props.isMultiple ?? false);
// 优化 handleUserSelection避免重复查找 // 优化 handleUserSelection避免重复查找
const handleUserSelection = () => { const handleUserSelection = () => {
if (!userid.value || !tableData.value.length) return; isFetching.value = true;
if (!multipleTableRef.value || !tableData.value.length) {
multipleSelection.value = []; isFetching.value = false;
if (userid.value.trim() !== '') { return;
const arr = userid.value.split(',');
arr.forEach((item) => {
const target = tableData.value.find((row) => row.userId == item);
target && multipleSelection.value.push(target);
});
nextTick(() => {
if (multipleTableRef.value) {
multipleTableRef.value.clearSelection();
multipleSelection.value.forEach((item) => {
multipleTableRef.value.toggleRowSelection(item, true);
});
} }
// 1. 获取当前页所有行的 houseId 集合,用于快速判断
// const currentPageIds = new Set(tableData.value.map((item) => item.houseId));
const rowsToSelect = tableData.value.filter((row) => {
return multipleSelection.value.some((selected) => selected.houseId === row.houseId);
}); });
multipleTableRef.value.clearSelection();
if (rowsToSelect.length > 0) {
nextTick(() => {
rowsToSelect.forEach((row) => {
multipleTableRef.value?.toggleRowSelection(row, true);
});
isFetching.value = false;
});
} else {
isFetching.value = false;
} }
}; };
// ====================== 方法 ====================== // ====================== 方法 ======================
const houseUseTypeList = ref([]);
const isFetching = ref(false);
// 获取列表 // 获取列表
function getlist() { function getlist() {
isFetching.value = true; // 🔒 加锁:禁止 selection-change 更新数据
loading.value = true; loading.value = true;
listBillHouse(queryParams.value) listBillHouse(queryParams.value)
.then((res) => { .then((res) => {
@@ -157,10 +185,11 @@ function getlist() {
handleUserSelection(); handleUserSelection();
}) })
.catch((error) => { .catch((error) => {
console.error('获取用户列表失败:', error); console.error('获取列表失败:', error);
}) })
.finally(() => { .finally(() => {
loading.value = false; loading.value = false;
isFetching.value = false;
}); });
} }
@@ -169,51 +198,72 @@ const buildinglist = ref([]);
/** 房屋列表 */ /** 房屋列表 */
const unitlist = ref([]); const unitlist = ref([]);
/** 筛选房屋类型 */ /** 获取楼栋 列表 */
function handleChangeHouse(val: string) { function handleChangeHouse() {
getBuildinglistAPI(val).then((res) => { getBuildinglistAPI().then((res) => {
buildinglist.value = res.data; buildinglist.value = res.data;
}); });
} }
function handleClear() {
queryParams.value.residentName = '';
getlist();
}
function handleHouseUseChange() {
getlist();
}
/** 单元列表 */ /** 单元列表 */
function handleChangebuilding(val: string) { function handleChangebuilding(val: string) {
queryParams.value.unitNoId = null;
unitlist.value = [];
if (queryParams.value.buildingId) {
getHouselistAPI(val).then((res) => { getHouselistAPI(val).then((res) => {
unitlist.value = res.data.units; unitlist.value = res.data.units;
getlist();
}); });
}
getlist();
}
function handleChangeUnit() {
getlist();
} }
// ============================================== // ==============================================
// ✅ 新增:处理 selection-change多选专用 // ✅ 新增:处理 selection-change多选专用
// ============================================== // ==============================================
function handleSelectionChange(selection: BillHouse[]) { function handleSelectionChange(selection: BillHouse[]) {
// 🛑 如果正在获取数据/恢复状态,直接返回,不要更新 multipleSelection
if (isFetching.value) return;
if (isMultiple.value) { if (isMultiple.value) {
multipleSelection.value = selection; // 1. 获取当前表格中所有选中行的 ID
} const currentSelectedIds = new Set(selection.map((item) => item.houseId));
} const otherPageSelections = multipleSelection.value.filter((item) => {
// ============================================== // 判断 item 是否属于当前页
// 最佳单选方案:点击行选中,无报错、无循环、最稳定 const isCurrentPageItem = tableData.value.some((row) => row.houseId === item.houseId);
// ============================================== if (isCurrentPageItem) {
// 优化 handleRowClick避免重复 // 如果是当前页的数据,检查是否在当前 selection 中
function handleRowClick(row: BillHouse) { return currentSelectedIds.has(item.houseId);
if (!multipleTableRef.value) return;
if (isMultiple.value) {
const index = multipleSelection.value.findIndex((item) => item.houseId === row.houseId);
if (index > -1) {
// 取消选中
multipleSelection.value.splice(index, 1);
multipleTableRef.value?.toggleRowSelection(row, false);
} else { } else {
// 选中 // 如果不是当前页的数据,保留(假设是之前页选的)
multipleSelection.value.push(row); return true;
multipleTableRef.value?.toggleRowSelection(row, true);
} }
});
// 去重合并
const finalSelection = [...otherPageSelections, ...selection];
// 再次去重以防万一
const uniqueMap = new Map();
finalSelection.forEach((item) => uniqueMap.set(item.houseId, item));
multipleSelection.value = Array.from(uniqueMap.values());
console.log(multipleSelection.value.map((item) => item.houseId));
} else {
// ✅ 补充单选模式下selection-change 触发时,直接取最新的一项
// 这样配合 handleRowClick 的 clearSelection + toggle 就能实现完美单选
if (selection.length > 0) {
multipleSelection.value = [selection[selection.length - 1]];
} else { } else {
// 【单选模式】先清空,再选中当前行
multipleSelection.value = []; multipleSelection.value = [];
multipleTableRef.value.clearSelection(); }
multipleSelection.value = [row];
multipleTableRef.value.toggleRowSelection(row, true);
} }
} }
// 重置查询 // 重置查询
@@ -221,9 +271,11 @@ function resetQuery() {
queryParams.value = { queryParams.value = {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
houseuse: null, houseUseTypeId: null,
buildingId: null, buildingId: null,
unitNo: null unitNoId: null,
houseNo: '',
residentName: ''
}; };
getlist(); getlist();
} }
@@ -267,9 +319,23 @@ function confirm() {
} }
defineExpose({ defineExpose({
open: () => { open: (showidArray: string[] = []) => {
multipleSelection.value = [];
showids.value = showidArray;
if (showidArray && showidArray.length > 0) {
multipleSelection.value = showidArray.map(
(id) =>
({
houseId: id
}) as BillHouse
);
}
getlist(); getlist();
dialog.value.dialogVisible = true; dialog.value.dialogVisible = true;
listHouseUseType_add_house().then((res) => {
houseUseTypeList.value = res.data;
});
handleChangeHouse();
} }
}); });
</script> </script>

View File

@@ -28,8 +28,7 @@
</div> </div>
</nav> </nav>
<!-- 关键去掉 @selection-change改用 row-click 实现单选永不报错 --> <el-table ref="multipleTableRef" row-key="id" :data="tableData" border @selection-change="handleSelectionChange">
<el-table ref="multipleTableRef" :data="tableData" border @row-click="handleRowClick" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" width="55" type="index" /> <el-table-column label="序号" align="center" width="55" type="index" />
<el-table-column label="姓名" align="center" prop="name" /> <el-table-column label="姓名" align="center" prop="name" />
@@ -38,6 +37,11 @@
<dict-tag :options="com_resident_type" :value="row.type" /> <dict-tag :options="com_resident_type" :value="row.type" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="与业主关系" align="center" width="95" prop="ownerRelationship">
<template #default="{ row }">
<dict-tag :options="com_resident_relationship" :value="row.ownerRelationship" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" width="85" prop="status"> <el-table-column label="状态" align="center" width="85" prop="status">
<template #default="{ row }"> <template #default="{ row }">
<dict-tag :options="com_certification_status" :value="row.status" /> <dict-tag :options="com_certification_status" :value="row.status" />
@@ -111,7 +115,7 @@ const props = defineProps<{
// ✅ 手动设置默认值(替代 withDefaults // ✅ 手动设置默认值(替代 withDefaults
const isMultiple = computed(() => props.isMultiple ?? false); const isMultiple = computed(() => props.isMultiple ?? false);
const returnvalue = computed(() => props.returnvalue ?? 'id'); // 👈 默认 userId const returnvalue = computed(() => props.returnvalue ?? '*'); // 👈 默认 userId
const { userid } = toRefs(props); const { userid } = toRefs(props);
@@ -140,50 +144,68 @@ const total = ref(0);
const loading = ref(false); const loading = ref(false);
// ============================================== // ==============================================
// ✅ 新增:处理 selection-change(多选专用) // ✅ 优化:处理 selection-change
// ============================================== // ==============================================
function handleSelectionChange(selection: tableType[]) { function handleSelectionChange(selection: tableType[]) {
if (isMultiple.value) { if (isMultiple.value) {
multipleSelection.value = selection; // 多选逻辑:合并当前页选中项和之前页选中项
} const currentSelectedIds = new Set(selection.map((item) => item.id));
}
// ============================================== // 过滤掉当前页未选中的旧数据
// 最佳单选方案:点击行选中,无报错、无循环、最稳定 const otherPageSelections = multipleSelection.value.filter((item) => {
// ============================================== const isCurrentPageItem = tableData.value.some((row) => row.id === item.id);
function handleRowClick(row) { if (isCurrentPageItem) {
if (isMultiple.value) { return currentSelectedIds.has(item.id);
// 多选:点击行切换选中状态
const isSelected = multipleSelection.value.some((item) => item.id === row.id);
if (isSelected) {
multipleSelection.value = multipleSelection.value.filter((item) => item.id !== row.id);
multipleTableRef.value?.toggleRowSelection(row, false);
} else {
multipleSelection.value.push(row);
multipleTableRef.value?.toggleRowSelection(row, true);
} }
return true; // 保留其他页的数据
});
// 合并当前页新选中的数据
const finalSelection = [...otherPageSelections, ...selection];
// 去重
const uniqueMap = new Map();
finalSelection.forEach((item) => uniqueMap.set(item.id, item));
multipleSelection.value = Array.from(uniqueMap.values());
} else { } else {
// 单选:原有逻辑保持不变 // 单选逻辑:始终只保留最后一个选中的项
if (selection.length > 0) {
// 获取最后选中的那一项
const lastSelected = selection[selection.length - 1];
multipleSelection.value = [lastSelected];
// 关键:如果 selection 长度大于1比如之前选了一个现在又选一个el-table可能暂时上报两个
// 我们需要手动清除表格中其他的勾选状态,只保留最后一个
nextTick(() => {
if (selection.length > 1) {
multipleTableRef.value?.clearSelection(); multipleTableRef.value?.clearSelection();
multipleTableRef.value?.toggleRowSelection(row, true); multipleTableRef.value?.toggleRowSelection(lastSelected, true);
multipleSelection.value = [row]; }
});
} else {
multipleSelection.value = [];
}
} }
} }
// ============================================== // ==============================================
// ✅ 修改:支持多选回显 // ✅ 优化:回显逻辑
// ============================================== // ==============================================
const handleUserSelection = () => { const handleUserSelection = () => {
if (!userid.value || !tableData.value.length) return; if (!multipleTableRef.value) return;
// 先清空所有选择
multipleTableRef.value.clearSelection();
multipleSelection.value = [];
if (!props.userid && props.userid !== 0) return;
nextTick(() => { nextTick(() => {
multipleTableRef.value?.clearSelection(); if (isMultiple.value && Array.isArray(props.userid)) {
if (isMultiple.value && Array.isArray(userid.value)) {
// 多选回显 // 多选回显
const selectedItems: tableType[] = []; const selectedItems: tableType[] = [];
userid.value.forEach((id) => { props.userid.forEach((id) => {
const findItem = tableData.value.find((item) => item.userId == id); const findItem = tableData.value.find((item) => item.id == id || item.userId == id);
if (findItem) { if (findItem) {
selectedItems.push(findItem); selectedItems.push(findItem);
multipleTableRef.value?.toggleRowSelection(findItem, true); multipleTableRef.value?.toggleRowSelection(findItem, true);
@@ -191,28 +213,32 @@ const handleUserSelection = () => {
}); });
multipleSelection.value = selectedItems; multipleSelection.value = selectedItems;
} else { } else {
// 单选回显(原有逻辑) // 单选回显
const findItem = tableData.value.find((item) => item.id == userid.value); // 注意props.userid 可能是单个 ID
const targetId = Array.isArray(props.userid) ? props.userid[0] : props.userid;
const findItem = tableData.value.find((item) => item.id == targetId || item.userId == targetId);
if (findItem) { if (findItem) {
multipleSelection.value = [findItem];
multipleTableRef.value?.toggleRowSelection(findItem, true); multipleTableRef.value?.toggleRowSelection(findItem, true);
multipleSelection.value = [findItem];
} }
} }
}); });
}; };
watch(userid, handleUserSelection, { immediate: true });
watch(tableData, handleUserSelection);
// 打开选择框 // 打开选择框
function choiceResidentFun() { function choiceResidentFun() {
// 初始化
getlist();
dialog.value.dialogVisible = true; dialog.value.dialogVisible = true;
} }
// 获取列表 // 获取列表
function getlist() { function getlist() {
console.log('getlist');
console.log(props.isMultiple);
loading.value = true; loading.value = true;
listResident(queryParams.value).then((res) => { listResident(queryParams.value)
.then((res) => {
tableData.value = res.rows; tableData.value = res.rows;
tableData.value.forEach((item) => { tableData.value.forEach((item) => {
if (item.houseId !== null) { if (item.houseId !== null) {
@@ -220,7 +246,10 @@ function getlist() {
item.housePath = houselist?.join('-') || ''; item.housePath = houselist?.join('-') || '';
} }
}); });
handleUserSelection();
total.value = res.total; total.value = res.total;
})
.finally(() => {
loading.value = false; loading.value = false;
}); });
} }
@@ -268,6 +297,7 @@ function confirm() {
} }
} else { } else {
// 单选 // 单选
console.log(multipleSelection.value[0], multipleSelection.value);
if (returnvalue.value === '*') { if (returnvalue.value === '*') {
// ✅ 返回整个对象 // ✅ 返回整个对象
emit('change', multipleSelection.value[0]); emit('change', multipleSelection.value[0]);
@@ -276,13 +306,22 @@ function confirm() {
emit('change', multipleSelection.value[0][returnvalue.value as keyof tableType]); emit('change', multipleSelection.value[0][returnvalue.value as keyof tableType]);
} }
} }
} else {
emit('change', null);
} }
} }
// 初始化
getlist();
defineExpose({ defineExpose({
open: () => { open: (arr) => {
if (arr && arr.length > 0) {
multipleSelection.value = arr.map((id) => ({
id: id
}));
console.log(multipleSelection.value);
} else {
multipleSelection.value = [];
}
getlist();
dialog.value.dialogVisible = true; dialog.value.dialogVisible = true;
} }
}); });

View File

@@ -2,12 +2,12 @@
<slot v-if="$slots.default" /> <slot v-if="$slots.default" />
<div v-else class="residentBox" @click="choiceResidentFun"> <div v-else class="residentBox" @click="choiceResidentFun">
<div class="defaultbox" v-if="multipleSelection.length === 0"> <div class="defaultbox" v-if="multipleSelection.length === 0 || props.username === ''">
<span>请选择</span> <span>请选择</span>
<el-icon><Search /></el-icon> <el-icon><Search /></el-icon>
</div> </div>
<div v-else class="overflow-auto overflow-hidden w-full text-ellipsis text-nowrap"> <div v-else class="overflow-auto overflow-hidden w-full text-ellipsis text-nowrap">
<span>姓名{{ multipleSelection[0]?.name }}</span> <span>姓名{{ multipleSelection[0]?.name ?? props.username }}</span>
</div> </div>
</div> </div>
@@ -80,6 +80,10 @@ const props = defineProps({
type: String, type: String,
default: '' default: ''
}, },
username: {
type: String,
default: ''
},
returnvalue: { returnvalue: {
type: String, type: String,
default: 'id' default: 'id'
@@ -160,6 +164,7 @@ const handleUserSelection = () => {
// 打开选择框 // 打开选择框
function choiceResidentFun() { function choiceResidentFun() {
dialog.value.dialogVisible = true; dialog.value.dialogVisible = true;
handleUserSelection();
} }
// 获取列表 // 获取列表

View File

@@ -0,0 +1,297 @@
<template>
<slot name="default" />
<el-dialog append-to-body v-model="dialog.dialogVisible" :title="dialog.dialogTitle" width="1050">
<el-table ref="multipleTableRef" :data="tableData" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" width="55" type="index" />
<el-table-column label="业主" align="center">
<template #default="scope">
<span>{{ scope.row.residentName ? scope.row.residentName : '---' }}</span>
</template>
</el-table-column>
<el-table-column label="性别" align="center">
<template #default="scope">
<dict-tag :options="sys_user_sex" :value="scope.row.gender"></dict-tag>
</template>
</el-table-column>
<el-table-column label="手机号" align="center" prop="phone" />
<el-table-column label="房屋" align="center" prop="house_name" />
</el-table>
<template #footer>
<div class="dialog-footer flex flex-items-center flex-justify-between">
<pagination
style="margin-top: 0"
layout="total, prev, pager, next, jumper"
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getlist"
/>
<div>
<el-button @click="conback">退出</el-button>
<el-button type="primary" @click="confirm"> 确定 </el-button>
</div>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { getBuildinglistAPI, getHouselistAPI } from '@/api/system/house';
import { BillUserlist, queryResidentList_holder } from '@/api/system/SdbBill';
import { ref, nextTick, getCurrentInstance, ComponentInternalInstance, toRefs } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex } = toRefs<any>(proxy?.useDict('sys_user_sex'));
const props = defineProps({
returnvalue: {
type: String,
default: 'residentId'
},
titleName: {
type: String,
default: '选择业主'
},
/** 是否多选 false 单选 */
isMultiple: {
type: Boolean,
default: false
}
});
const { titleName, returnvalue } = toRefs(props);
const userid = ref([]);
// ✅ 手动设置默认值(替代 withDefaults
const emit = defineEmits<{
change: [value: string | number | (string | number)[] | BillUserlist | BillUserlist[]]; // 支持返回数组
}>();
const dialog = ref({
dialogVisible: false,
dialogTitle: titleName.value
});
const queryParams = ref({
pageNum: 1,
pageSize: 10
});
const multipleTableRef = ref();
const tableData = ref<BillUserlist[]>([]);
const total = ref(0);
const loading = ref(false);
const isFetching = ref(false);
// 存储选中项
const multipleSelection = ref<BillUserlist[]>([]);
const isMultiple = computed(() => props.isMultiple ?? false);
// 优化 handleUserSelection避免重复查找
const handleUserSelection = () => {
isFetching.value = true;
if (!multipleTableRef.value || !tableData.value.length) {
isFetching.value = false;
return;
}
const rowsToSelect = tableData.value.filter((row) => {
return multipleSelection.value.some((selected) => selected.residentId === row.residentId);
});
multipleTableRef.value.clearSelection();
if (rowsToSelect.length > 0) {
nextTick(() => {
rowsToSelect.forEach((row) => {
multipleTableRef.value?.toggleRowSelection(row, true);
});
isFetching.value = false;
});
} else {
isFetching.value = false;
}
};
// ====================== 方法 ======================
// 获取列表
function getlist() {
isFetching.value = true; // 加锁
loading.value = true;
queryResidentList_holder(queryParams.value)
.then((res) => {
tableData.value = res.rows;
total.value = res.total;
handleUserSelection();
})
.catch((error) => {
console.error('获取用户列表失败:', error);
})
.finally(() => {
loading.value = false;
isFetching.value = false; // 加锁
});
}
/** 楼栋列表 */
const buildinglist = ref([]);
/** 房屋列表 */
const unitlist = ref([]);
/** 筛选房屋类型 */
function handleChangeHouse(val: string) {
getBuildinglistAPI().then((res) => {
buildinglist.value = res.data;
});
}
/** 单元列表 */
function handleChangebuilding(val: string) {
getHouselistAPI(val).then((res) => {
unitlist.value = res.data.units;
});
}
// ==============================================
// ✅ 新增:处理 selection-change多选专用
// ==============================================
function handleSelectionChange(selection: BillUserlist[]) {
if (isFetching.value) return;
if (isMultiple.value) {
const currentSelectedIds = new Set(selection.map((item) => item.residentId));
const otherPageSelections = multipleSelection.value.filter((item) => {
const isCurrentPageItem = tableData.value.some((row) => row.residentId === item.residentId);
if (isCurrentPageItem) {
return currentSelectedIds.has(item.residentId);
} else {
return true;
}
});
// 3. 合并:其他页保留的 + 当前页新选中的
const finalSelection = [...otherPageSelections, ...selection];
// 4. 去重(以防万一)
const uniqueMap = new Map();
finalSelection.forEach((item) => uniqueMap.set(item.residentId, item));
multipleSelection.value = Array.from(uniqueMap.values());
console.log(multipleSelection.value.map((item) => item.residentId));
} else {
// --- 单选逻辑 ---
if (selection.length > 0) {
multipleSelection.value = [selection[selection.length - 1]];
} else {
multipleSelection.value = [];
}
}
}
// ==============================================
// 最佳单选方案:点击行选中,无报错、无循环、最稳定
// ==============================================
// 优化 handleRowClick避免重复
function handleRowClick(row: BillUserlist) {
if (!multipleTableRef.value) return;
if (isMultiple.value) {
const index = multipleSelection.value.findIndex((item) => item.residentId === row.residentId);
if (index > -1) {
// 取消选中
multipleSelection.value.splice(index, 1);
multipleTableRef.value?.toggleRowSelection(row, false);
} else {
// 选中
multipleSelection.value.push(row);
multipleTableRef.value?.toggleRowSelection(row, true);
}
} else {
// 【单选模式】先清空,再选中当前行
multipleSelection.value = [];
multipleTableRef.value.clearSelection();
multipleSelection.value = [row];
multipleTableRef.value.toggleRowSelection(row, true);
}
}
// 重置查询
function resetQuery() {
queryParams.value = {
pageNum: 1,
pageSize: 10
};
getlist();
}
// 退出
function conback() {
dialog.value.dialogVisible = false;
resetQuery();
}
// ==============================================
// ✅ 核心修改:根据 returnvalue 返回对应的值
// ==============================================
function confirm() {
dialog.value.dialogVisible = false;
if (isMultiple.value) {
// 多选
if (returnvalue.value === '*') {
// ✅ 返回整个对象数组
emit('change', multipleSelection.value);
} else {
// 返回指定字段数组
emit(
'change',
multipleSelection.value.map((item) => item[returnvalue.value as keyof BillUserlist])
);
}
} else {
// 单选
if (returnvalue.value === '*') {
// ✅ 返回整个对象
emit('change', multipleSelection.value[0]);
} else {
// 返回指定字段
emit('change', multipleSelection.value[0][returnvalue.value as keyof BillUserlist]);
}
}
multipleSelection.value = [];
multipleTableRef.value.clearSelection();
}
defineExpose({
open: (arr: string[] = []) => {
userid.value = arr;
if (arr && arr.length > 0) {
multipleSelection.value = arr.map(
(id) =>
({
residentId: id
}) as BillUserlist
);
} else {
multipleSelection.value = [];
}
getlist();
dialog.value.dialogVisible = true;
}
});
</script>
<style scoped lang="scss">
.residentBox {
width: 385px;
height: 35px;
line-height: 35px;
border: 1px solid #ccc;
border-radius: 5px;
padding: 0 10px;
.defaultbox {
display: flex;
align-items: center;
justify-content: space-between;
}
cursor: pointer;
&:hover {
background-color: #f4f8ff;
}
}
</style>

View File

@@ -0,0 +1,287 @@
<template>
<div class="license-plate-input">
<div class="plate_line">
<div
v-for="(item, index) in chars"
:key="index"
class="plate-item"
:class="{
active: activeIndex === index,
disabled: disabled,
error: hasError
}"
>
<input
ref="inputRefs"
v-model="chars[index]"
type="text"
maxlength="1"
:disabled="disabled"
@input="onInput(index)"
@keydown="onKeydown($event, index)"
@paste="onPaste"
@focus="activeIndex = index"
@blur="onBlur"
/>
</div>
</div>
<!-- 错误提示 Element Plus 原生完全一致 -->
</div>
</template>
<script setup lang="ts">
import { LicensePlateValidator } from '@/utils/Reg';
import { ref, watch, nextTick } from 'vue';
const PROVINCES = [
'京',
'津',
'沪',
'渝',
'冀',
'豫',
'云',
'辽',
'黑',
'湘',
'皖',
'鲁',
'新',
'苏',
'浙',
'赣',
'鄂',
'桂',
'甘',
'晋',
'蒙',
'陕',
'吉',
'闽',
'贵',
'粤',
'青',
'藏',
'川',
'宁',
'琼'
];
const VALID_CHARS = /^[A-HJ-NP-Z0-9]$/i;
const props = defineProps<{
modelValue: string;
disabled?: boolean;
required?: boolean;
}>();
const emit = defineEmits<{
'update:modelValue': [value: string];
'change': [value: string];
}>();
const inputRefs = ref<HTMLInputElement[]>([]);
const activeIndex = ref(-1);
const chars = ref<string[]>(Array(8).fill(''));
const hasError = ref(false);
const errorMessage = ref('');
// 外部值同步
watch(
() => props.modelValue,
(val) => {
const currentValue = chars.value.join('').trim();
if (val !== currentValue) {
const newChars = Array(8).fill('');
if (val) {
val
.toUpperCase()
.split('')
.forEach((c, i) => {
if (i < 8) newChars[i] = c;
});
}
chars.value = newChars;
}
},
{ immediate: true }
);
// 内部值同步到外部
watch(
chars,
() => {
const value = chars.value.join('').trim();
emit('update:modelValue', value);
emit('change', value);
// 输入时清除错误
clearError();
},
{ deep: true }
);
function onInput(index: number) {
const char = chars.value[index].toUpperCase();
if (index === 0 && !PROVINCES.includes(char)) {
chars.value[index] = '';
return;
}
if (index > 0 && !VALID_CHARS.test(char)) {
chars.value[index] = '';
return;
}
chars.value[index] = char;
if (char && index < 7) {
nextTick(() => inputRefs.value[index + 1]?.focus());
}
}
function onKeydown(e: KeyboardEvent, index: number) {
if (e.key === 'Backspace' && !chars.value[index] && index > 0) {
e.preventDefault();
nextTick(() => {
inputRefs.value[index - 1]?.focus();
inputRefs.value[index - 1]?.select();
});
}
if (e.key === 'ArrowLeft' && index > 0) {
e.preventDefault();
inputRefs.value[index - 1]?.focus();
}
if (e.key === 'ArrowRight' && index < 7) {
e.preventDefault();
inputRefs.value[index + 1]?.focus();
}
}
function onPaste(e: ClipboardEvent) {
e.preventDefault();
const text = e.clipboardData?.getData('text')?.trim().toUpperCase() || '';
if (!text) return;
const newChars = Array(8).fill('');
text.split('').forEach((c, i) => {
if (i >= 8) return;
if (i === 0 && PROVINCES.includes(c)) newChars[i] = c;
if (i > 0 && VALID_CHARS.test(c)) newChars[i] = c;
});
chars.value = newChars;
const lastIndex = Math.min(text.length - 1, 6);
nextTick(() => inputRefs.value[lastIndex + 1]?.focus());
}
function onBlur() {
setTimeout(() => {
const hasFocus = inputRefs.value.some((input) => document.activeElement === input);
if (!hasFocus) {
activeIndex.value = -1;
}
}, 10);
}
// ✅ 手动验证方法(核心)
function validate(): { valid: boolean; message?: string } {
const value = chars.value.join('').trim();
// 必填验证
if (props.required && !value) {
hasError.value = true;
errorMessage.value = '请输入车牌号';
return { valid: false, message: errorMessage.value };
}
// 格式验证
if (value && !isValidPlate(value)) {
hasError.value = true;
errorMessage.value = '请输入正确的车牌号格式';
return { valid: false, message: errorMessage.value };
}
// 验证通过
clearError();
return { valid: true };
}
// 车牌格式验证(内置,不依赖外部工具)
function isValidPlate(plate: string): boolean {
return LicensePlateValidator.isValidPlate(plate);
}
// 清除错误
function clearError() {
hasError.value = false;
errorMessage.value = '';
}
// 暴露方法
defineExpose({
focus: () => nextTick(() => inputRefs.value[0]?.focus()),
clear: () => {
chars.value = Array(8).fill('');
},
validate,
clearError
});
</script>
<style scoped>
.license-plate-input {
display: flex;
flex-direction: column;
gap: 4px;
}
.plate_line {
display: flex;
align-items: center;
gap: 6px;
}
.plate-item {
width: 24px;
height: 24px;
line-height: 24px;
border: 1px solid #dcdfe6;
border-radius: 4px;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.plate-item.active {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
.plate-item.error {
border-color: #f56c6c;
}
.plate-item.disabled {
background-color: #f5f7fa;
cursor: not-allowed;
}
.plate-item input {
width: 100%;
height: 100%;
border: none;
text-align: center;
font-size: 15px;
font-weight: 500;
background: transparent;
outline: none;
}
.plate-error {
color: #f56c6c;
font-size: 12px;
line-height: 1.5;
margin-top: 2px;
}
</style>

View File

@@ -22,7 +22,7 @@ const props = defineProps({
total: propTypes.number, total: propTypes.number,
page: propTypes.number.def(1), page: propTypes.number.def(1),
limit: propTypes.number.def(20), limit: propTypes.number.def(20),
pageSizes: { type: Array<number>, default: () => [10, 20, 30, 50] }, pageSizes: { type: Array<number>, default: () => [10, 20, 30, 50, 100] },
// 移动端页码按钮的数量端默认值5 // 移动端页码按钮的数量端默认值5
pagerCount: propTypes.number.def(document.body.clientWidth < 992 ? 5 : 7), pagerCount: propTypes.number.def(document.body.clientWidth < 992 ? 5 : 7),
layout: propTypes.string.def('total, sizes, prev, pager, next, jumper'), layout: propTypes.string.def('total, sizes, prev, pager, next, jumper'),

View File

@@ -52,7 +52,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { propTypes } from '@/utils/propTypes'; import { propTypes } from '@/utils/propTypes';
import { FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types'; import { FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
import UserSelect from '@/components/UserSelect'; import UserSelect from '@/components/UserSelect/index.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { getTask, taskOperation, currentTaskAllUser, terminationTask } from '@/api/workflow/task'; import { getTask, taskOperation, currentTaskAllUser, terminationTask } from '@/api/workflow/task';
const props = defineProps({ const props = defineProps({
@@ -91,7 +91,9 @@ const task = ref<FlowTaskVO>({
nodeRatio: undefined, nodeRatio: undefined,
version: undefined, version: undefined,
applyNode: undefined, applyNode: undefined,
buttonList: [] buttonList: [],
businessCode: '',
businessTitle: ''
}); });
const open = (taskId: string) => { const open = (taskId: string) => {

View File

@@ -152,7 +152,7 @@ import {
currentTaskAllUser, currentTaskAllUser,
getNextNodeList getNextNodeList
} from '@/api/workflow/task'; } from '@/api/workflow/task';
import UserSelect from '@/components/UserSelect'; import UserSelect from '@/components/UserSelect/index.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { FlowCopyVo, FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types'; import { FlowCopyVo, FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';

View File

@@ -1,12 +1,28 @@
<template> <template>
<div class="top-right-btn" :style="style"> <div class="top-right-btn" :style="style">
<el-row> <div class="flex items-center">
<el-tooltip v-if="search" class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top"> <!-- 添加按钮 -->
<el-button circle icon="Search" @click="toggleSearch()" /> <el-button :disabled="props.disableAdd" v-if="props.showAdd" icon="Plus" type="primary" @click="toggleSearch()"> 新建 </el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="刷新" placement="top"> <!-- 批量操作下拉框 -->
<el-button circle icon="Refresh" @click="refresh()" /> <el-dropdown v-if="props.showDelete || props.showEdit || $slots.dropdown" trigger="click" style="margin-left: 10px">
</el-tooltip> <el-button @click.stop="refresh"> {{ props.moreTitle }} </el-button>
<!-- 👇 核心优先使用外部插槽没有则使用默认内容 -->
<template #dropdown>
<!-- 这里直接渲染不嵌套插槽 -->
<el-dropdown-menu v-if="!$slots.dropdown">
<el-dropdown-item :disabled="props.disableEdit" v-if="props.showEdit" @click="emits('update:edit')"> 编辑 </el-dropdown-item>
<el-dropdown-item :disabled="props.disableDelete" v-if="props.showDelete" @click="emits('update:delete')"> 批量删除 </el-dropdown-item>
</el-dropdown-menu>
<!-- 外部自定义内容直接渲染不包任何多余标签 -->
<template v-else>
<slot name="dropdown" />
</template>
</template>
</el-dropdown>
<!-- 显隐列保留你原有逻辑 -->
<el-tooltip v-if="columns" class="item" effect="dark" content="显示/隐藏列" placement="top"> <el-tooltip v-if="columns" class="item" effect="dark" content="显示/隐藏列" placement="top">
<div class="show-btn"> <div class="show-btn">
<el-popover placement="bottom" trigger="click"> <el-popover placement="bottom" trigger="click">
@@ -16,7 +32,7 @@
:data="columns" :data="columns"
show-checkbox show-checkbox
node-key="key" node-key="key"
:props="{ label: 'label', children: 'children' } as any" :props="{ label: 'label', children: 'children' }"
@check="columnChange" @check="columnChange"
></el-tree> ></el-tree>
<template #reference> <template #reference>
@@ -25,65 +41,77 @@
</el-popover> </el-popover>
</div> </div>
</el-tooltip> </el-tooltip>
</el-row> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { propTypes } from '@/utils/propTypes'; import { propTypes } from '@/utils/propTypes';
import { CSSProperties } from 'vue';
const props = defineProps({ const props = defineProps({
showSearch: propTypes.bool.def(true),
columns: propTypes.fieldOption, columns: propTypes.fieldOption,
search: propTypes.bool.def(true), gutter: propTypes.number.def(10),
gutter: propTypes.number.def(10) moreTitle: propTypes.string.def('批量操作'),
showAdd: propTypes.bool.def(true),
showDelete: propTypes.bool.def(true),
showEdit: propTypes.bool.def(true),
disableAdd: propTypes.bool.def(false),
disableDelete: propTypes.bool.def(false),
disableEdit: propTypes.bool.def(false)
}); });
const columnRef = ref<ElTreeInstance>(); const columnRef = ref();
const emits = defineEmits(['update:showSearch', 'queryTable']); const emits = defineEmits(['update:add', 'update:edit', 'update:delete']);
const style = computed(() => { const style = computed<CSSProperties>(() => {
const ret: any = {}; const ret: CSSProperties = {};
if (props.gutter) { if (props.gutter) {
ret.marginRight = `${props.gutter / 2}px`; ret.marginRight = `${props.gutter / 2}px`;
} }
return ret; return ret;
}); });
// 搜索 // 添加
function toggleSearch() { function toggleSearch() {
emits('update:showSearch', !props.showSearch); emits('update:add');
} }
// 刷新 const isExpent = ref(false);
// 批量操作展开
function refresh() { function refresh() {
emits('queryTable'); isExpent.value = !isExpent.value;
} }
// 更改数据列的显示隐藏 // 更改显示隐藏
function columnChange(...args: any[]) { function columnChange(...args) {
props.columns?.forEach((item) => { props.columns?.forEach((item) => {
item.visible = args[1].checkedKeys.includes(item.key); item.visible = args[1].checkedKeys.includes(item.key);
}); });
} }
// 显隐列初始默认隐藏列
onMounted(() => { onMounted(() => {
props.columns?.forEach((item) => { props.columns?.forEach((item) => {
if (item.visible) { if (item.visible) {
columnRef.value?.setChecked(item.key, true, false); columnRef.value?.setChecked(item.key, true, false);
// value.value.push(item.key);
} }
}); });
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.hiddenActions {
margin-left: 10px;
}
:deep(.el-transfer__button) { :deep(.el-transfer__button) {
border-radius: 50%; border-radius: 50%;
display: block; display: block;
margin-left: 0px; margin-left: 0px;
} }
:deep(.el-transfer__button:first-child) { :deep(.el-transfer__button:first-child) {
margin-bottom: 10px; margin-bottom: 10px;
} }
@@ -91,11 +119,13 @@ onMounted(() => {
.my-el-transfer { .my-el-transfer {
text-align: center; text-align: center;
} }
.tree-header { .tree-header {
width: 100%; width: 100%;
line-height: 24px; line-height: 24px;
text-align: center; text-align: center;
} }
.show-btn { .show-btn {
margin-left: 12px; margin-left: 12px;
} }

View File

@@ -4,7 +4,7 @@
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]"> <div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover"> <el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" @submit.prevent="handleQuery">
<el-form-item label="角色名称" prop="roleName"> <el-form-item label="角色名称" prop="roleName">
<el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@@ -13,8 +13,8 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>

View File

@@ -24,7 +24,7 @@
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]"> <div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover"> <el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <el-form ref="queryFormRef" @submit.prevent="handleQuery" :model="queryParams" :inline="true">
<el-form-item label="用户名称" prop="userName"> <el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@@ -32,8 +32,8 @@
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="() => resetQuery()">重置</el-button> <el-button @click="() => resetQuery()">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>

View File

@@ -1,5 +1,6 @@
<template> <template>
<section class="app-main"> <section class="app-main">
<div style="background-color: #f2f1f6; height: 100%">
<router-view v-slot="{ Component, route }" :key="$route.fullPath"> <router-view v-slot="{ Component, route }" :key="$route.fullPath">
<transition :enter-active-class="animate" mode="out-in"> <transition :enter-active-class="animate" mode="out-in">
<keep-alive :include="tagsViewStore.cachedViews"> <keep-alive :include="tagsViewStore.cachedViews">
@@ -8,6 +9,7 @@
</transition> </transition>
</router-view> </router-view>
<iframe-toggle /> <iframe-toggle />
</div>
</section> </section>
</template> </template>
@@ -54,13 +56,14 @@ function addIframe() {
<style lang="scss" scoped> <style lang="scss" scoped>
.app-main { .app-main {
/* 50= navbar 50 */ /* 50= navbar 50 */
min-height: calc(100vh - 50px); // min-height: calc(100vh - 50px);
flex: 1; flex: 1;
width: 100%; width: 100%;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
overflow-y: auto; overflow-y: auto;
background-color: #f2f1f6; background-color: #f2f1f6;
background-color: #dfedff;
} }
.fixed-header + .app-main { .fixed-header + .app-main {

View File

@@ -8,11 +8,12 @@
@toggle-click="toggleSideBar" @toggle-click="toggleSideBar"
> >
</hamburger> </hamburger>
<div class="flex-1 h-full"></div>
<div class="right-menu flex align-center"> <div class="right-menu flex align-center">
<template v-if="appStore.device !== 'mobile'"> <template v-if="appStore.device !== 'mobile'">
<el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom"> <el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom">
<div> <div>
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false"> <el-popover placement="bottom-end" trigger="click" transition="el-zoom-in-top" :width="400" :persistent="false">
<template #reference> <template #reference>
<el-badge :value="newNotice > 0 ? newNotice : ''" :max="99"> <el-badge :value="newNotice > 0 ? newNotice : ''" :max="99">
<div class="right-menu-item hover-effect" style="display: block"><svg-icon icon-class="message" /></div> <div class="right-menu-item hover-effect" style="display: block"><svg-icon icon-class="message" /></div>
@@ -36,7 +37,7 @@
<el-dropdown-item divided command="changecommunity"> <el-dropdown-item divided command="changecommunity">
<span>{{ proxy.$t('navbar.community') }}</span> <span>{{ proxy.$t('navbar.community') }}</span>
</el-dropdown-item> </el-dropdown-item>
<router-link to="/user/profile"> <router-link to="/system/user/profile">
<el-dropdown-item>{{ proxy.$t('navbar.personalCenter') }}</el-dropdown-item> <el-dropdown-item>{{ proxy.$t('navbar.personalCenter') }}</el-dropdown-item>
</router-link> </router-link>
<el-dropdown-item divided command="logout"> <el-dropdown-item divided command="logout">
@@ -51,6 +52,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import hamburger from '@/components/Hamburger/index.vue';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useUserStore } from '@/store/modules/user'; import { useUserStore } from '@/store/modules/user';
import { useSettingsStore } from '@/store/modules/settings'; import { useSettingsStore } from '@/store/modules/settings';
@@ -59,6 +61,13 @@ import notice from './notice/index.vue';
import router from '@/router'; import router from '@/router';
import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type'; import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type';
const props = defineProps({
Showhamburger: {
type: Boolean,
default: true
}
});
const appStore = useAppStore(); const appStore = useAppStore();
const userStore = useUserStore(); const userStore = useUserStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
@@ -71,13 +80,7 @@ const navType = computed(() => settingsStore.navType);
// 是否切换了租户 // 是否切换了租户
const dynamic = ref(true); const dynamic = ref(true);
const props = defineProps({
Showhamburger: {
type: Boolean,
default: true
}
});
console.log(props.Showhamburger);
const toggleSideBar = () => { const toggleSideBar = () => {
appStore.toggleSideBar(false); appStore.toggleSideBar(false);
}; };
@@ -147,10 +150,6 @@ watch(
top: 12px; top: 12px;
} }
.flex {
display: flex;
}
.align-center { .align-center {
align-items: center; align-items: center;
} }
@@ -159,42 +158,32 @@ watch(
height: 50px; height: 50px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
// background: #1a75ff;
background: #dfedff; background: #dfedff;
// border-bottom: 1px solid #1a75ff; background: transparent;
border-bottom: 1px solid #dfedff; border-bottom: 1px solid #dfedff;
color: #fff;
color: #c0c4cc; color: #c0c4cc;
box-shadow: none; box-shadow: none;
display: flex; display: flex;
align-items: center; align-items: center;
// padding: 0 8px; justify-content: space-between;
box-sizing: border-box; box-sizing: border-box;
.hamburger-container { .hamburger-container {
line-height: 46px; line-height: 46px;
height: 100%; height: 100%;
//float: left;
cursor: pointer; cursor: pointer;
transition: background 0.3s; transition: background 0.3s;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
display: flex; display: flex;
align-items: center; align-items: center;
flex-shrink: 0;
margin-right: 8px; margin-right: 8px;
&:hover { &:hover {
background: var(--el-fill-color-lighter); background: #fafafa52;
} }
} }
.breadcrumb-container {
//float: left;
flex-shrink: 0;
}
.topmenu-container { .topmenu-container {
position: absolute; position: absolute;
left: 50px; left: 50px;
@@ -215,13 +204,10 @@ watch(
} }
.right-menu { .right-menu {
//float: right;
height: 100%; height: 100%;
line-height: 50px; line-height: 50px;
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: auto;
&:focus { &:focus {
outline: none; outline: none;
} }
@@ -239,10 +225,6 @@ watch(
&.hover-effect { &.hover-effect {
cursor: pointer; cursor: pointer;
transition: background 0.3s; transition: background 0.3s;
// &:hover {
// background: var(--el-fill-color-lighter);
// }
} }
} }
@@ -251,6 +233,7 @@ watch(
.avatar-wrapper { .avatar-wrapper {
margin-top: 5px; margin-top: 5px;
// margin-left: 20px;
position: relative; position: relative;
.user-avatar { .user-avatar {

View File

@@ -118,7 +118,7 @@
<el-divider /> <el-divider />
<el-button type="primary" plain icon="DocumentAdd" @click="saveSetting">保存配置</el-button> <el-button type="primary" plain icon="DocumentAdd" @click="saveSetting">保存配置</el-button>
<el-button plain icon="Refresh" @click="resetSetting">重置配置</el-button> <el-button plain @click="resetSetting">重置配置</el-button>
</el-drawer> </el-drawer>
</template> </template>
@@ -161,15 +161,15 @@ const toggleDark = () => useToggle(isDark);
/** 菜单导航设置 */ /** 菜单导航设置 */
watch( watch(
() => navType, navType,
(val: string) => { (val: string) => {
if (val.value === NavTypeEnum.TOP) { if (val === NavTypeEnum.TOP) {
appStore.toggleSideBarHide(true); appStore.toggleSideBarHide(true);
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any); permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
} else if (val.value === NavTypeEnum.LEFT) { } else if (val === NavTypeEnum.LEFT) {
appStore.toggleSideBarHide(false); appStore.toggleSideBarHide(false);
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any); permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
} else if (val.value === NavTypeEnum.MIX) { } else if (val === NavTypeEnum.MIX) {
appStore.toggleSideBarHide(false); appStore.toggleSideBarHide(false);
} }
}, },

View File

@@ -36,7 +36,7 @@ const sideTheme = computed(() => settingsStore.sideTheme);
// 获取Logo背景色 // 获取Logo背景色
const getLogoBackground = computed(() => { const getLogoBackground = computed(() => {
if (settingsStore.isDark) { if (settingsStore.dark) {
return 'var(--sidebar-bg)'; return 'var(--sidebar-bg)';
} }
if (settingsStore.navType == NavTypeEnum.TOP) { if (settingsStore.navType == NavTypeEnum.TOP) {
@@ -47,7 +47,7 @@ const getLogoBackground = computed(() => {
// 获取Logo文字颜色 // 获取Logo文字颜色
const getLogoTextColor = computed(() => { const getLogoTextColor = computed(() => {
if (settingsStore.isDark) { if (settingsStore.dark) {
return 'var(--sidebar-text)'; return 'var(--sidebar-text)';
} }
if (settingsStore.navType == NavTypeEnum.TOP) { if (settingsStore.navType == NavTypeEnum.TOP) {

View File

@@ -50,7 +50,8 @@ const activeMenu = computed(() => {
} }
return path; return path;
}); });
const bgColor = '#eaf2ff'; // const bgColor = '#eaf2ff';
const bgColor = '#dfedff';
// const bgColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground)); // const bgColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground));
const textColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuColor : variables.menuLightColor)); const textColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuColor : variables.menuLightColor));
</script> </script>

View File

@@ -72,10 +72,10 @@ const init = async () => {
} }
const data: LoginData = { const data: LoginData = {
socialCode: code, // socialCode: code,
socialState: state, // socialState: state,
tenantId: tenantId, tenantId: tenantId,
source: source, // source: source,
clientId: import.meta.env.VITE_APP_CLIENT_ID, clientId: import.meta.env.VITE_APP_CLIENT_ID,
grantType: 'social' grantType: 'social'
}; };

View File

@@ -11,7 +11,7 @@ import { useTagsViewStore } from '@/store/modules/tagsView';
const tagAndTagSpacing = ref(4); const tagAndTagSpacing = ref(4);
const scrollContainerRef = ref<ElScrollbarInstance>(); const scrollContainerRef = ref<ElScrollbarInstance>();
const scrollWrapper = computed(() => scrollContainerRef.value?.$refs.wrapRef); const scrollWrapper = computed(() => scrollContainerRef.value?.$refs.wrapRef as HTMLElement);
onMounted(() => { onMounted(() => {
scrollWrapper.value?.addEventListener('scroll', emitScroll, true); scrollWrapper.value?.addEventListener('scroll', emitScroll, true);

View File

@@ -12,58 +12,58 @@
</template> </template>
<script setup> <script setup>
import SidebarItem from '../Sidebar/SidebarItem' import SidebarItem from '../Sidebar/SidebarItem';
import {useAppStore} from '@/store/modules/app' import { useAppStore } from '@/store/modules/app';
import {useSettingsStore} from '@/store/modules/settings' import { useSettingsStore } from '@/store/modules/settings';
import {usePermissionStore} from '@/store/modules/permission' import { usePermissionStore } from '@/store/modules/permission';
const route = useRoute() const route = useRoute();
const appStore = useAppStore() const appStore = useAppStore();
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore();
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore();
const sidebarRouters = computed(() => permissionStore.sidebarRouters) const sidebarRouters = computed(() => permissionStore.sidebarRouters);
const theme = computed(() => settingsStore.theme) const theme = computed(() => settingsStore.theme);
const device = computed(() => appStore.device) const device = computed(() => appStore.device);
const activeMenu = computed(() => { const activeMenu = computed(() => {
const { meta, path } = route const { meta, path } = route;
if (meta.activeMenu) { if (meta.activeMenu) {
return meta.activeMenu return meta.activeMenu;
} }
return path return path;
}) });
const visibleNumber = ref(5) const visibleNumber = ref(5);
const topMenus = computed(() => { const topMenus = computed(() => {
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(0, visibleNumber.value) return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(0, visibleNumber.value);
}) });
const moreRoutes = computed(() => { const moreRoutes = computed(() => {
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(visibleNumber.value, sidebarRouters.value.length - visibleNumber.value) return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(visibleNumber.value, sidebarRouters.value.length - visibleNumber.value);
}) });
function setVisibleNumber() { function setVisibleNumber() {
let width = document.body.getBoundingClientRect().width let width = document.body.getBoundingClientRect().width;
if (width >= 1000) { if (width >= 1000) {
width -= 500 width -= 500;
} }
visibleNumber.value = parseInt(width / 3 / 85) visibleNumber.value = parseInt(width / 3 / 85);
} }
onMounted(() => { onMounted(() => {
window.addEventListener('resize', setVisibleNumber) window.addEventListener('resize', setVisibleNumber);
}) });
onBeforeUnmount(() => { onBeforeUnmount(() => {
window.removeEventListener('resize', setVisibleNumber) window.removeEventListener('resize', setVisibleNumber);
}) });
onMounted(() => { onMounted(() => {
setVisibleNumber() setVisibleNumber();
}) });
</script> </script>
<style lang="scss"> <style lang="scss">
/* menu item */ /* menu item */
#app .topbar-menu.el-menu--horizontal .el-sub-menu__title, #app .topbar-menu.el-menu--horizontal .el-menu-item { #app .topbar-menu.el-menu--horizontal .el-sub-menu__title,
#app .topbar-menu.el-menu--horizontal .el-menu-item {
padding: 0 10px !important; padding: 0 10px !important;
} }
@@ -76,7 +76,10 @@ onMounted(() => {
margin: 0 10px !important; margin: 0 10px !important;
} }
.el-sub-menu.is-active .svg-icon, .el-menu-item.is-active .svg-icon + span, .el-sub-menu.is-active .svg-icon + span, .el-sub-menu.is-active .el-sub-menu__title span { .el-sub-menu.is-active .svg-icon,
.el-menu-item.is-active .svg-icon + span,
.el-sub-menu.is-active .svg-icon + span,
.el-sub-menu.is-active .el-sub-menu__title span {
color: v-bind(theme); color: v-bind(theme);
} }
@@ -85,7 +88,7 @@ onMounted(() => {
float: left; float: left;
line-height: 50px !important; line-height: 50px !important;
color: #303133 !important; color: #303133 !important;
margin: 0 15px -3px!important; margin: 0 15px -3px !important;
} }
/* topbar more arrow */ /* topbar more arrow */
@@ -97,7 +100,8 @@ onMounted(() => {
} }
/* menu__title el-menu-item */ /* menu__title el-menu-item */
.topbar-menu.el-menu--horizontal .el-sub-menu__title, .topbar-menu.el-menu--horizontal .el-menu-item { .topbar-menu.el-menu--horizontal .el-sub-menu__title,
.topbar-menu.el-menu--horizontal .el-menu-item {
height: 60px; height: 60px;
} }
</style> </style>

View File

@@ -19,13 +19,14 @@
</template> </template>
<el-empty v-else :description="'消息为空'"></el-empty> <el-empty v-else :description="'消息为空'"></el-empty>
</div> </div>
<div v-if="newsList.length > 0" class="foot-box" @click="onGoToGiteeClick">前往gitee</div>
</div> </div>
</template> </template>
<script setup lang="ts" name="layoutBreadcrumbUserNews"> <script setup lang="ts" name="layoutBreadcrumbUserNews">
import { useNoticeStore } from '@/store/modules/notice'; import { useNoticeStore } from '@/store/modules/notice';
const router = useRouter();
const noticeStore = useNoticeStore(); const noticeStore = useNoticeStore();
const { readAll } = useNoticeStore(); const { readAll } = useNoticeStore();
@@ -48,13 +49,10 @@ const getTableData = async () => {
//点击消息,写入已读 //点击消息,写入已读
const onNewsClick = (item: any) => { const onNewsClick = (item: any) => {
newsList.value[item].read = true; newsList.value[item].read = true;
//并且写入pinia
noticeStore.state.notices = newsList.value; noticeStore.state.notices = newsList.value;
}; router.push({
path: '/system/notice'
// 前往通知中心点击 });
const onGoToGiteeClick = () => {
window.open('https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/');
}; };
onMounted(() => { onMounted(() => {
@@ -85,7 +83,8 @@ onMounted(() => {
} }
} }
.content-box { .content-box {
height: 300px; height: 400px;
overflow: auto; overflow: auto;
font-size: 13px; font-size: 13px;
.content-box-item { .content-box-item {

View File

@@ -2,7 +2,7 @@
<!-- 👇 加这一层全局缩放容器 --> <!-- 👇 加这一层全局缩放容器 -->
<div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }"> <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
<side-bar class="sidebar-container" /> <side-bar class="sidebar-container" />
<div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container"> <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container" id="main-container">
<div> <div>
<navbar ref="navbarRef" @set-layout="setLayout" /> <navbar ref="navbarRef" @set-layout="setLayout" />
</div> </div>
@@ -134,6 +134,9 @@ const setLayout = () => {
top: 0; top: 0;
} }
} }
.main-container {
flex: 1;
}
.drawer-bg { .drawer-bg {
background: #000; background: #000;

View File

@@ -22,7 +22,18 @@ let isDynamicRoutesAdded = false;
const isWhiteList = (path: string) => { const isWhiteList = (path: string) => {
return whiteList.some((pattern) => isPathMatch(pattern, path)); return whiteList.some((pattern) => isPathMatch(pattern, path));
}; };
// ✅ 关键修改:记录当前已添加的动态路由名称列表
let currentDynamicRouteNames: string[] = [];
// ✅ 关键修改:清除所有已记录的动态路由
const removeDynamicRoutes = () => {
currentDynamicRouteNames.forEach((name) => {
if (name && router.hasRoute(name)) {
router.removeRoute(name);
}
});
// 清空记录数组
currentDynamicRouteNames = [];
};
router.beforeEach(async (to, from) => { router.beforeEach(async (to, from) => {
NProgress.start(); NProgress.start();
@@ -46,6 +57,7 @@ router.beforeEach(async (to, from) => {
} }
if (userStore.roles.length === 0) { if (userStore.roles.length === 0) {
isRelogin.show = true; isRelogin.show = true;
removeDynamicRoutes();
// 获取用户信息 // 获取用户信息
const [err, res] = await tos(userStore.getInfo()); const [err, res] = await tos(userStore.getInfo());
@@ -60,14 +72,19 @@ router.beforeEach(async (to, from) => {
permissionStore.resetRoutes(); permissionStore.resetRoutes();
// 生成动态路由 // 生成动态路由
const accessRoutes = await permissionStore.generateRoutes(); const accessRoutes = await permissionStore.generateRoutes();
// 添加路由(避免重复添加) // 添加路由(避免重复添加)
accessRoutes.forEach((route) => { accessRoutes.forEach((route) => {
if (route.name && router.hasRoute(route.name)) {
router.removeRoute(route.name);
}
if (!isHttp(route.path) && !router.hasRoute(route.name)) { if (!isHttp(route.path) && !router.hasRoute(route.name)) {
router.addRoute(route); router.addRoute(route);
// 记录顶层路由名称
if (route.name) {
currentDynamicRouteNames.push(route.name as string);
}
} }
}); });
// ✅ 标记:路由已添加 // ✅ 标记:路由已添加
isDynamicRoutesAdded = true; isDynamicRoutesAdded = true;

View File

@@ -54,6 +54,7 @@ export const constantRoutes: RouteRecordRaw[] = [
{ {
path: 'communitylist', path: 'communitylist',
name: 'communitylist', name: 'communitylist',
// system:village:list
hidden: true, hidden: true,
meta: { meta: {
title: '小区列表' title: '小区列表'
@@ -89,32 +90,37 @@ export const constantRoutes: RouteRecordRaw[] = [
{ {
path: '', path: '',
component: Layout, component: Layout,
redirect: '/equipment/WaterDevice', // redirect: '/index',
redirect: import.meta.env.VITE_LOGIN_BACK_URL,
// redirect: '/equipment/WaterDevice',
children: [ children: [
{ // {
path: '/index', // path: '/index',
component: () => import('@/views/workbench/index2.vue'), // component: () => import('@/views/workbench/index2.vue'),
name: 'Index', // name: 'Index',
hidden: true, // // hidden: true,
meta: { title: '工作台', icon: 'dashboard', affix: true } // meta: { title: '工作台', icon: 'dashboard', affix: true }
} // }
]
},
{
path: '/user',
component: Layout,
hidden: true,
redirect: 'noredirect',
children: [
{
path: 'profile',
component: () => import('@/views/system/user/profile/index.vue'),
name: 'Profile',
meta: { title: '个人中心', icon: 'user' }
}
] ]
} }
]; ];
if (import.meta.env.VITE_APP_SHOW_WORK_PLATFORM === 'true') {
const find = constantRoutes.find((item) => item.path === '');
if (find) {
find.children?.push({
path: '/index',
component: () => {
if (import.meta.env.VITE_LOGIN_BACK_URL_WORKBENCH_TRUE === 'true') {
return import('@/views/workbench/index.vue');
} else {
return import('@/views/workbench/index2.vue');
}
},
name: 'Index',
meta: { title: '工作台', icon: 'dashboard', affix: true }
});
}
}
// 动态路由,基于用户权限动态去加载 // 动态路由,基于用户权限动态去加载
export const dynamicRoutes: RouteRecordRaw[] = []; export const dynamicRoutes: RouteRecordRaw[] = [];

View File

@@ -2,6 +2,7 @@ import { LanguageEnum } from '@/enums/LanguageEnum';
import { NavTypeEnum } from '@/enums/NavTypeEnum'; import { NavTypeEnum } from '@/enums/NavTypeEnum';
const setting: DefaultSettings = { const setting: DefaultSettings = {
topNav: false,
/** /**
* 网页标题 * 网页标题
*/ */

View File

@@ -41,7 +41,6 @@ export const useHouseStore = defineStore(
'buildToward': '', 'buildToward': '',
'buildTall': '', 'buildTall': '',
'buildStructure': '', 'buildStructure': '',
'unitCount': null,
'floorCount': null, 'floorCount': null,
'remark': '' 'remark': ''
}); });
@@ -73,36 +72,15 @@ export const useHouseStore = defineStore(
}; };
// 初始化 // 初始化
const initHouse = async (currentbuildingtype: string) => { const initHouse = async () => {
currentBuildingInfoList.value = []; console.log('initHouse');
resertInit();
// 获取当前小区 所有楼栋信息 // 获取当前小区 所有楼栋信息
const res2 = await getBuildinglistAPI(currentbuildingtype); const res2 = await getBuildinglistAPI();
currentBuildingInfoList.value = res2.data; currentBuildingInfoList.value = res2.data;
// 得到小区 内 楼栋列表 -> 切换到第一个
// 获取当前小区 第一个楼栋信息 if (res2.data.length > 0) {
// ! 不同住宅类型 switchBuilding(res2.data[0].id);
if ((currentBuildingInfo.value && currentBuildingInfo.value.id === null) || buildingtype.value !== currentbuildingtype) {
currentBuildingInfo.value = res2.data[0];
// ! 相同住宅类型
} else if (buildingtype.value === currentbuildingtype) {
// 更新当前楼栋的 信息
currentBuildingInfo.value = res2.data.find((item) => item.id === currentBuildingInfo.value.id);
}
if (currentBuildingInfo.value && currentBuildingInfo.value.id) {
// 获取当前 第一个楼栋的所有信息
const res3 = await getHouselistAPI(currentBuildingInfo.value.id);
currentHouseInfoList.value = res3.data;
floorslist.value = new Set();
// 得到所有的有房屋的 楼层 数组
currentHouseInfoList.value.units.forEach((item) => {
item.floors.forEach((floor) => {
floorslist.value.add(floor.floorNo);
});
});
if (buildingtype.value !== currentbuildingtype) {
buildingtype.value = currentbuildingtype;
}
} }
}; };
@@ -118,7 +96,7 @@ export const useHouseStore = defineStore(
// 清空 // 清空
currentBuildingInfoList.value = []; currentBuildingInfoList.value = [];
// 获取当前小区 所有楼栋信息 // 获取当前小区 所有楼栋信息
const res2 = await getBuildinglistAPI(housetype); const res2 = await getBuildinglistAPI();
currentBuildingInfoList.value = res2.data; currentBuildingInfoList.value = res2.data;
// 获取当前小区 第一个楼栋信息 // 获取当前小区 第一个楼栋信息
if ((currentBuildingInfo.value && currentBuildingInfo.value.id === null) || buildingtype.value !== housetype) { if ((currentBuildingInfo.value && currentBuildingInfo.value.id === null) || buildingtype.value !== housetype) {
@@ -134,7 +112,6 @@ export const useHouseStore = defineStore(
'buildToward': '', 'buildToward': '',
'buildTall': '', 'buildTall': '',
'buildStructure': '', 'buildStructure': '',
'unitCount': null,
'floorCount': null, 'floorCount': null,
'remark': '' 'remark': ''
}; };
@@ -152,6 +129,7 @@ export const useHouseStore = defineStore(
// 更新当前楼栋信息 // 更新当前楼栋信息
const updateCureentBuilding = async () => { const updateCureentBuilding = async () => {
currentHouseInfoList.value = null; currentHouseInfoList.value = null;
if (currentBuildingInfo.value && currentBuildingInfo.value.id) {
// 获取当前 楼栋的所有信息 // 获取当前 楼栋的所有信息
const res3 = await getHouselistAPI(currentBuildingInfo.value.id); const res3 = await getHouselistAPI(currentBuildingInfo.value.id);
currentHouseInfoList.value = res3.data; currentHouseInfoList.value = res3.data;
@@ -162,7 +140,23 @@ export const useHouseStore = defineStore(
floorslist.value.add(floor.floorNo); floorslist.value.add(floor.floorNo);
}); });
}); });
}
}; };
// 根据房屋号,查询房屋
async function getHouseBybuildingNo(houseName: string, houseUseTypeId: string) {
currentHouseInfoList.value = null;
// 获取当前 楼栋的所有信息
const res3 = await getHouselistAPI(currentBuildingInfo.value.id, houseName, houseUseTypeId);
currentHouseInfoList.value = res3.data;
floorslist.value = null;
floorslist.value = new Set();
// 得到所有的有房屋的 楼层 数组
currentHouseInfoList.value.units.forEach((item) => {
item.floors.forEach((floor) => {
floorslist.value.add(floor.floorNo);
});
});
}
// 切换楼栋 // 切换楼栋
const switchBuilding = (buildingId: string) => { const switchBuilding = (buildingId: string) => {
if (!buildingId) return; if (!buildingId) return;
@@ -176,16 +170,15 @@ export const useHouseStore = defineStore(
'buildToward': '', 'buildToward': '',
'buildTall': '', 'buildTall': '',
'buildStructure': '', 'buildStructure': '',
'unitCount': null,
'floorCount': null, 'floorCount': null,
'remark': '' 'remark': ''
}; };
currentHouseInfoList.value = null; currentHouseInfoList.value = null;
currentBuildingInfo.value = currentBuildingInfoList.value.find((item) => item.id === buildingId);
getHouselistAPI(buildingId).then((res) => { getHouselistAPI(buildingId).then((res) => {
if (!res.data) return;
currentHouseInfoList.value = res.data; currentHouseInfoList.value = res.data;
// 获取当前楼栋信息并 赋值 // 获取当前楼栋信息并 赋值
currentBuildingInfo.value = currentBuildingInfoList.value.find((item) => item.id === buildingId);
// 清空 楼层信息 // 清空 楼层信息
floorslist.value.clear(); floorslist.value.clear();
// 重新获取楼层信息 // 重新获取楼层信息
@@ -198,9 +191,28 @@ export const useHouseStore = defineStore(
}); });
}; };
function resertInit() {
console.log('resertInit');
currentBuildingInfo.value = {
'id': null,
'villageId': null,
'villageName': '',
'buildingName': '',
'buildingUse': null,
'buildToward': '',
'buildTall': '',
'buildStructure': '',
'floorCount': null,
'remark': ''
};
currentBuildingInfoList.value = [];
currentHouseInfoList.value = null;
floorslist.value.clear();
}
// 更新数据 // 更新数据
const updateHouse = () => { const updateHouse = () => {
initHouse(buildingtype.value); initHouse();
}; };
// 删除指定楼栋 // 删除指定楼栋
const deleteBuildingFun = (buildingId: string) => { const deleteBuildingFun = (buildingId: string) => {
@@ -218,9 +230,10 @@ export const useHouseStore = defineStore(
// 房屋 树状结构类型 // 房屋 树状结构类型
const getCurrentVilageTreeHouse = async () => { const getCurrentVilageTreeHouse = async () => {
const villageid = localStorage.getItem('villageid');
try { try {
treedata.value = []; treedata.value = [];
const res = await getVillageTree(currentVilageInfo.value.villageId); const res = await getVillageTree(villageid);
treedata.value = convertBuildingToTree(res.data.buildings || []); treedata.value = convertBuildingToTree(res.data.buildings || []);
return treedata.value; return treedata.value;
} catch (err) { } catch (err) {
@@ -239,6 +252,7 @@ export const useHouseStore = defineStore(
buildingtype, buildingtype,
buildingtypelist, buildingtypelist,
treedata, treedata,
getHouseBybuildingNo,
updateVilageinfo, updateVilageinfo,
updateCureentBuilding, updateCureentBuilding,
getviliageinfo, getviliageinfo,

View File

@@ -8,13 +8,15 @@ interface NoticeItem {
time: string; time: string;
} }
export const useNoticeStore = defineStore('notice', () => { export const useNoticeStore = defineStore(
'notice',
() => {
const state = reactive({ const state = reactive({
notices: [] as NoticeItem[] notices: [] as NoticeItem[]
}); });
const addNotice = (notice: NoticeItem) => { const addNotice = (notice: NoticeItem) => {
state.notices.push(notice); state.notices.unshift(notice);
}; };
const removeNotice = (notice: NoticeItem) => { const removeNotice = (notice: NoticeItem) => {
@@ -38,4 +40,10 @@ export const useNoticeStore = defineStore('notice', () => {
readAll, readAll,
clearNotice clearNotice
}; };
}); },
{
persist: {
storage: sessionStorage
}
}
);

View File

@@ -70,16 +70,20 @@ export const usePermissionStore = defineStore('permission', () => {
}; };
const setRoutes = (newRoutes: RouteRecordRaw[]): void => { const setRoutes = (newRoutes: RouteRecordRaw[]): void => {
addRoutes.value = [];
addRoutes.value = newRoutes; addRoutes.value = newRoutes;
routes.value = constantRoutes.concat(newRoutes); routes.value = constantRoutes.concat(newRoutes);
}; };
const setDefaultRoutes = (routes: RouteRecordRaw[]): void => { const setDefaultRoutes = (routes: RouteRecordRaw[]): void => {
defaultRoutes.value = [];
defaultRoutes.value = constantRoutes.concat(routes); defaultRoutes.value = constantRoutes.concat(routes);
}; };
const setTopbarRoutes = (routes: RouteRecordRaw[]): void => { const setTopbarRoutes = (routes: RouteRecordRaw[]): void => {
topbarRouters.value = [];
topbarRouters.value = routes; topbarRouters.value = routes;
}; };
const setSidebarRouters = (routes: RouteRecordRaw[]): void => { const setSidebarRouters = (routes: RouteRecordRaw[]): void => {
sidebarRouters.value = [];
sidebarRouters.value = routes; sidebarRouters.value = routes;
}; };
@@ -104,10 +108,6 @@ export const usePermissionStore = defineStore('permission', () => {
const defaultRoutes = filterAsyncRouter(defaultData); const defaultRoutes = filterAsyncRouter(defaultData);
const asyncRoutes = filterDynamicRoutes(dynamicRoutes); const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
asyncRoutes.forEach((route) => {
router.addRoute(route);
});
setRoutes(rewriteRoutes); setRoutes(rewriteRoutes);
setSidebarRouters(constantRoutes.concat(sidebarRoutes)); setSidebarRouters(constantRoutes.concat(sidebarRoutes));
setDefaultRoutes(sidebarRoutes); setDefaultRoutes(sidebarRoutes);

10
src/types/global.d.ts vendored
View File

@@ -17,10 +17,14 @@ declare global {
} }
/** 地图 */ /** 地图 */
interface Window { interface Window {
// 高德全局对象
_AMapSecurityConfig?: { _AMapSecurityConfig?: {
securityJsCode: string; securityJsCode: string;
}; };
// 天地图全局对象
T: any;
} }
/** /**
* 弹窗属性 * 弹窗属性
*/ */
@@ -65,7 +69,7 @@ declare global {
declare interface DictDataOption { declare interface DictDataOption {
label: string; label: string;
value: string; value: string;
elTagType?: ElTagType; elTagType?: ElTagType | '' | 'default';
elTagClass?: string; elTagClass?: string;
} }
@@ -95,6 +99,10 @@ declare global {
pageSize: number; pageSize: number;
} }
declare interface LayoutSetting { declare interface LayoutSetting {
/**
* 是否显示顶部导航
*/
topNav: boolean; // 新增此字段
/** /**
* 默认布局 * 默认布局
*/ */

View File

@@ -64,3 +64,153 @@ export type RegType = keyof typeof reg;
export function validator(value: string | number, regType: RegType): boolean { export function validator(value: string | number, regType: RegType): boolean {
return reg[regType].test(String(value)); return reg[regType].test(String(value));
} }
/**
* 车牌验证工具类
* 支持传统燃油、新能源、军警、武警、使馆、临时、港澳等全量车牌
*/
export class LicensePlateValidator {
/**
* 省份简称正则片段
*/
private static readonly PROVINCE = '[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼]';
/**
* 使领馆前缀
*/
private static readonly EMBASSY_PREFIX = '[使领]';
/**
* 验证传统燃油车牌 (蓝牌/黄牌)
* 格式: 省份 + 字母 + 5位字符
* 示例: 京A·12345, 粤B·88888
*/
static isTraditionalPlate(plate: string): boolean {
// 包含普通民用以及使领馆非新能源部分(如果使领馆归为此类,通常使领馆有单独规则,这里仅指普通民用)
// 注意:使领馆车牌通常格式特殊,见 isEmbassyPlate
const reg = new RegExp(`^${this.PROVINCE}[A-HJ-NP-Z][A-HJ-NP-Z0-9]{5}$`);
return reg.test(plate.toUpperCase());
}
/**
* 验证新能源车牌 (绿牌)
* 小型: 省份 + 字母 + D/F + 4位数字/字母 或 省份 + 字母 + 5位数字/字母 + D/F
* 大型: 省份 + 字母 + 5位数字/字母 + D/F (通常最后一位是D/F)
* 示例: 京AD12345, 京A12345D, 沪AF12345
*/
static isNewEnergyPlate(plate: string): boolean {
// 小型新能源第2位或第7位是D/F
// 大型新能源最后一位是D/F (且总长8位)
// 综合正则:
// 1. [省份][A-HJ-NP-Z][D-F][0-9A-HJ-NP-Z]{4} (小型D/F在第2位)
// 2. [省份][A-HJ-NP-Z][0-9A-HJ-NP-Z]{4}[D-F] (小型/大型D/F在最后)
// 3. [省份][A-HJ-NP-Z][0-9A-HJ-NP-Z][D-F][0-9A-HJ-NP-Z]{3} (较少见但某些早期试点或特殊编排可能存在标准通常只认1和2)
// 更严谨的标准新能源正则 (GA 804-2018):
// 小型: [省份][A-HJ-NP-Z][D-F][0-9A-HJ-NP-Z]{4} | [省份][A-HJ-NP-Z][0-9A-HJ-NP-Z]{4}[D-F]
// 大型: [省份][A-HJ-NP-Z][0-9A-HJ-NP-Z]{5}[D-F] (其实包含在上面第二种情况的子集里只要保证总长8位)
const reg = new RegExp(`^${this.PROVINCE}[A-HJ-NP-Z](?:[D-F][0-9A-HJ-NP-Z]{4}|[0-9A-HJ-NP-Z]{5}[D-F])$`);
return reg.test(plate.toUpperCase());
}
/**
* 验证警车车牌
* 格式: 省份 + 字母 + 4位数字 + 警
* 示例: 京A·1234警
*/
static isPolicePlate(plate: string): boolean {
const reg = new RegExp(`^${this.PROVINCE}[A-HJ-NP-Z][0-9]{4}警$`);
return reg.test(plate.toUpperCase());
}
/**
* 验证教练车牌
* 格式: 省份 + 字母 + 4位数字 + 学
* 示例: 京A·1234学
*/
static isCoachPlate(plate: string): boolean {
const reg = new RegExp(`^${this.PROVINCE}[A-HJ-NP-Z][0-9]{4}学$`);
return reg.test(plate.toUpperCase());
}
/**
* 验证武警车牌 (2013式)
* 格式: WJ + 省份/部门简称 + 5位字符
* 示例: WJ·京12345, WJ·12345X, WJ·B12345
* 注意WJ后紧跟的可能是省份简称或部门字母
*/
static isArmedPolicePlate(plate: string): boolean {
// WJ + (省份简称 | 部门字母) + 5位字符
// 部门字母包括: 内边消水电林通海等,这里简化处理,允许字母或省份
const reg = /^WJ(?:[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼]|[A-Z])[0-9A-HJ-NP-Z]{5}$/;
return reg.test(plate.toUpperCase());
}
/**
* 验证军队车牌 (2012式)
* 格式: 军种字母 + · + 5位字符 (实际输入常不带点)
* 军种: V(军委), K(空军), H(海军), B(北京军区), S(沈阳), L(兰州), J(济南), N(南京), G(广州), C(成都) 等
* 示例: V·12345, K·A1234
*/
static isMilitaryPlate(plate: string): boolean {
// 简化版首字母为大写排除I,O后跟5位字符
// 更严谨应限制首字母为特定军种代号,但考虑到军改变化,此处做通用校验
// 2012式军牌格式XY·12345 (X为军种/大区Y为单位)
// 常见前缀: V, K, H, B, S, L, J, N, G, C, M, P, O, A, E 等
const reg = /^[VKBHSLJNGCMPEOA][A-HJ-NP-Z0-9][0-9A-HJ-NP-Z]{4}$/;
return reg.test(plate.toUpperCase());
}
/**
* 验证使馆/领馆车牌
* 格式: 使/领 + 3位数字 + 3位数字 (共6位数字或 使·123456)
* 示例: 使·123456, 领·123456
*/
static isEmbassyPlate(plate: string): boolean {
const reg = /^[使领][0-9]{6}$/;
return reg.test(plate);
}
/**
* 验证港澳入出境车 (粤Z牌)
* 格式: 粤Z + 4位字符 + 港/澳
* 示例: 粤Z·1234港, 粤Z·1234澳
*/
static isHKMacauPlate(plate: string): boolean {
const reg = /^粤Z[A-HJ-NP-Z0-9]{4}[港澳]$/;
return reg.test(plate.toUpperCase());
}
/**
* 验证临时车牌
* 格式: 省份 + 字母 + 5位字符 + 临 / 超
* 示例: 京A·12345临, 沪B·12345超
*/
static isTemporaryPlate(plate: string): boolean {
const reg = new RegExp(`^${this.PROVINCE}[A-HJ-NP-Z][0-9A-HJ-NP-Z]{5}[临超]$`);
return reg.test(plate.toUpperCase());
}
/**
* 验证所有合法车牌(全量)
* @param plate 车牌号码
*/
static isValidPlate(plate: string): boolean {
if (!plate || typeof plate !== 'string') return false;
const upperPlate = plate.toUpperCase().replace(/[·\s-]/g, ''); // 去除分隔符和空格
return (
this.isTraditionalPlate(upperPlate) ||
this.isNewEnergyPlate(upperPlate) ||
this.isPolicePlate(upperPlate) ||
this.isCoachPlate(upperPlate) ||
this.isArmedPolicePlate(upperPlate) ||
this.isMilitaryPlate(upperPlate) ||
this.isEmbassyPlate(upperPlate) ||
this.isHKMacauPlate(upperPlate) ||
this.isTemporaryPlate(upperPlate)
);
}
}

117
src/utils/exportZip.ts Normal file
View File

@@ -0,0 +1,117 @@
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
// 假设使用的是 Element Plus请根据实际 UI 库调整导入
import { ElMessage } from 'element-plus';
/**
* 图片数据接口
*/
interface ImageItem {
name: string;
url: string;
}
/**
* 批量导出图片并生成 ZIP
* @param imageList 图片数组
* @param zipName 压缩包名称
* @param concurrency 并发请求数,默认 5避免浏览器请求过多被拦截
*/
export async function exportImagesToZip(imageList: ImageItem[], zipName: string = 'images', concurrency: number = 5) {
if (!imageList || imageList.length === 0) {
ElMessage.warning('没有可导出的图片');
return;
}
const zip = new JSZip();
try {
// 使用并发控制处理图片获取
const tasks = imageList.map((item) => () => processImageItem(item, zip));
// 执行并发任务
await runWithConcurrency(tasks, concurrency);
ElMessage.success('导出完成');
// 生成 ZIP 并下载
const content = await zip.generateAsync({
type: 'blob',
compression: 'DEFLATE',
compressionOptions: { level: 6 }
});
saveAs(content, `${zipName}.zip`);
} catch (err) {
console.error('ZIP 导出失败:', err);
ElMessage.error('图片导出失败,请重试');
}
}
/**
* 处理单个图片项:获取 Blob 并添加到 Zip
*/
async function processImageItem(item: ImageItem, zip: JSZip): Promise<void> {
try {
// 清理文件名,防止非法字符或扩展名缺失
const safeName = sanitizeFileName(item.name);
const blob = await getImageBlobViaCanvas(item.url);
zip.file(`${safeName}.png`, blob, { binary: true });
} catch (error) {
console.warn(`图片加载失败: ${item.url}`, error);
throw error; // 抛出错误以便外层统计或处理
}
}
/**
* 通过 Canvas 获取图片 Blob (解决 OSS 跨域 fetch 问题)
* 注意:这要求 OSS _bucket_ 配置了 CORS 允许 '*' 或你的域名,且允许 GET 方法。
* 如果 OSS 完全禁止跨域,此方法也会失败,但通常公共读 OSS 是允许 img 标签跨域显示的。
*/
async function getImageBlobViaCanvas(url: string): Promise<Blob> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.blob();
}
/**
* 简单的并发控制执行器
*/
async function runWithConcurrency(tasks: (() => Promise<void>)[], limit: number): Promise<void> {
const results: Promise<void>[] = [];
let index = 0;
async function worker() {
while (index < tasks.length) {
const currentIndex = index++;
const task = tasks[currentIndex];
try {
await task();
} catch (e) {
// 这里可以选择忽略单个失败,或者累计错误
console.error(`Task ${currentIndex} failed`, e);
}
}
}
// 创建 limit 个工人
const workers = Array(Math.min(limit, tasks.length)).fill(null).map(worker);
await Promise.all(workers);
}
/**
* 清理文件名,确保合法且唯一(简单版)
*/
function sanitizeFileName(name: string): string {
// 移除非法字符,保留字母、数字、中文、下划线、点、横杠
let cleanName = name.replace(/[<>:"/\\|?*]/g, '_');
// 如果名字为空或只有扩展名,给一个默认名
if (!cleanName || cleanName.startsWith('.')) {
cleanName = `image_${Date.now()}_${Math.random().toString(36).substr(2, 9)}${cleanName}`;
}
return cleanName;
}

View File

@@ -26,9 +26,9 @@ export function convertBuildingToTree(buildings: any[]): Tree[] {
const units = building.units || []; const units = building.units || [];
const unitTrees = units.map((unit: any) => { const unitTrees = units.map((unit: any) => {
const unitTree: Tree = { const unitTree: Tree = {
label: `${unit.unitNo} 单元`, label: unit.unitNoName,
value: unit.unitNo, value: unit.unitNoId,
type: 'unitNo', type: 'unitNoId',
children: [] children: []
}; };
@@ -58,22 +58,6 @@ export function convertBuildingToTree(buildings: any[]): Tree[] {
}); });
} }
// 与业主关系
export const ownerRelationshiplist = [
{
label: '业主',
value: 0
},
{
label: '亲属',
value: 1
},
{
label: '租户',
value: 2
}
];
interface Tree { interface Tree {
label: string; label: string;
value: number; value: number;
@@ -120,8 +104,6 @@ export function getHousePathById(treeData: Tree[], houseId: string | number, res
return path; return path;
} }
export const houseUselist = ['普通住宅', '商业用地', '商住两用', '其他'];
// 对字符串 图片地址,处理 // 对字符串 图片地址,处理
export function splitStringUrl(str: string, splitLabel: string = ',') { export function splitStringUrl(str: string, splitLabel: string = ',') {
const list = str.split(splitLabel); const list = str.split(splitLabel);
@@ -149,25 +131,3 @@ export function hasFormData(formData: FormData): boolean {
} }
return false; return false;
} }
/** 方法渲燃模板数据 */
type dataType = {
'label': string;
'value': string;
'elTagType': string;
'elTagClass': string;
};
/**
* 通用:根据 value 从字典数组中获取 label
* @param dict 字典数组
* @param value 要匹配的值
* @returns 匹配到的label无则返回原value
*/
export function getDictLabel(dict: Ref<dataType>, value: number): string {
// 空值直接返回
if (value == null) return '-';
// 统一转字符串匹配
const item = (dict.value || []).find((i) => String(i.value) === String(value));
return item ? item.label : String(value);
}

View File

@@ -220,14 +220,19 @@ export const getTime = (type: string) => {
}; };
/** /**
* @param {Function} func * 创建一个防抖函数,确保在指定的等待时间内只执行一次目标函数。
* @param {number} wait *
* @param {boolean} immediate * @param func - 需要被防抖处理的目标函数
* @return {*} * @param wait - 等待时间(毫秒),在此时间内重复调用将重置计时器
* @param immediate - 是否立即执行。如果为 true则在第一次调用时立即执行函数并在等待期结束后才能再次触发如果为 false则在等待期结束后执行最后一次调用
* @returns 返回一个新的防抖函数,该函数在被调用时会应用防抖逻辑
*/ */
export const debounce = (func: any, wait: number, immediate: boolean) => { export const debounce = (func: any, wait: number, immediate: boolean) => {
let timeout: any, args: any, context: any, timestamp: any, result: any; let timeout: any, args: any, context: any, timestamp: any, result: any;
/**
* 延迟执行的内部函数,用于判断是否真正执行目标函数
*/
const later = function () { const later = function () {
// 据上一次触发时间间隔 // 据上一次触发时间间隔
const last = +new Date() - timestamp; const last = +new Date() - timestamp;

View File

@@ -80,3 +80,71 @@ export function convertToChineseCapital(num: number | string): string {
return result; return result;
} }
// 金额加法
export function add(a, b) {
let r1, r2;
try {
r1 = a.toString().split('.')[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = b.toString().split('.')[1].length;
} catch (e) {
r2 = 0;
}
const m = Math.pow(10, Math.max(r1, r2));
return (a * m + b * m) / m;
}
// 金额减法
export function sub(a, b) {
let r1, r2;
try {
r1 = a.toString().split('.')[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = b.toString().split('.')[1].length;
} catch (e) {
r2 = 0;
}
const m = Math.pow(10, Math.max(r1, r2));
return (a * m - b * m) / m;
}
// 金额乘法
export function mul(a, b) {
let m = 0;
const s1 = a.toString(),
s2 = b.toString();
try {
m += s1.split('.')[1].length;
} catch (e) {}
try {
m += s2.split('.')[1].length;
} catch (e) {}
return (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / Math.pow(10, m);
}
// 金额除法
export function div(a, b) {
let t1 = 0,
t2 = 0;
try {
t1 = a.toString().split('.')[1].length;
} catch (e) {}
try {
t2 = b.toString().split('.')[1].length;
} catch (e) {}
const x = Number(a.toString().replace('.', ''));
const y = Number(b.toString().replace('.', ''));
return (x / y) * Math.pow(10, t2 - t1);
}
// 保留2位小数金额专用
export function toFixed2(val) {
return Number(val).toFixed(2);
}

View File

@@ -34,15 +34,16 @@ const service = axios.create({
clarifyTimeoutError: true clarifyTimeoutError: true
} }
}); });
// 不需要小区 ID 的接口白名单(支持 * 通配符) // 不需要小区 ID 的接口白名单(支持 * 通配符)
const whiteVillageId = [ const whiteVillageId = [
'/village', '/village',
'/village/*', '/village/*',
'/waterDevice/generateBarCode', // 水表 条码 '/waterDevice/generateBarCode', // 水表 条码
'/electricityDevice/generateBarCodee', // 电表 条码 '/electricityDevice/generateBarCode', // 电表 条码
'/payRecordList/payRecordList/list',
'/payRecordList/payRecordList/list' '/warehouse/*'
// '/storeroom/getList/*'
// 以后直接在这里加新地址,支持 /xxx/*、/xxx/xxx 写法 // 以后直接在这里加新地址,支持 /xxx/*、/xxx/xxx 写法
]; ];
@@ -203,9 +204,6 @@ service.interceptors.response.use(
useUserStore().logout().then(() => { useUserStore().logout().then(() => {
router.replace({ router.replace({
path: '/login', path: '/login',
query: {
redirect: encodeURIComponent(router.currentRoute.value.fullPath || '/')
}
}) })
}); });
}).catch(() => { }).catch(() => {

198
src/utils/weather.ts Normal file
View File

@@ -0,0 +1,198 @@
const weatherIcons: Record<number, string> = {
100: '☀️',
101: '⛅',
102: '🌤️',
103: '⛅',
104: '☁️',
150: '🌙',
151: '🌙',
152: '🌙',
153: '🌙',
300: '🌦️',
301: '🌧️',
302: '⛈️',
303: '⛈️',
304: '⛈️',
305: '🌧️',
306: '🌧️',
307: '🌧️',
308: '🌧️',
309: '🌧️',
310: '🌊',
311: '🌊',
312: '🌊',
313: '🧊',
314: '🌧️',
315: '🌧️',
316: '🌊',
317: '🌊',
318: '🌊',
350: '🌙',
351: '🌙',
399: '🌧️',
400: '🌨️',
401: '🌨️',
402: '❄️',
403: '❄️',
404: '🌨️',
405: '🌨️',
406: '🌨️',
407: '🌨️',
408: '🌨️',
409: '❄️',
410: '❄️',
456: '🌙',
457: '🌙',
499: '❄️',
500: '🌫️',
501: '🌫️',
502: '😶‍🌫️',
503: '💨',
504: '💨',
507: '🏜️',
508: '🏜️',
509: '🌫️',
510: '🌫️',
511: '😶‍🌫️',
512: '😶‍🌫️',
513: '😶‍🌫️',
514: '🌫️',
515: '🌫️',
800: '🌑',
801: '🌒',
802: '🌓',
803: '🌔',
804: '🌕',
805: '🌖',
806: '🌗',
807: '🌘',
900: '🥵',
901: '🥶',
999: '❓',
1001: '🌀',
1002: '🌪️',
1003: '🌊',
1004: '❄️',
1005: '🥶',
1006: '💨',
1007: '🏜️',
1008: '🧊',
1009: '🌡️',
1010: '🥵',
1011: '🌾',
1012: '💨',
1013: '🏔️',
1014: '⚡',
1015: '🧊',
1016: '🥶',
1017: '🌫️',
1018: '✈️',
1019: '😶‍🌫️',
1020: '⛈️',
1021: '🧊',
1022: '☀️',
1023: '🌊',
1024: '🥵',
1025: '🔥',
1026: '🔥',
1027: '🧊',
1028: '🛰️',
1029: '😶‍🌫️',
1030: '🌨️',
1031: '⛈️',
1032: '☀️',
1033: '❄️',
1034: '🥶',
1035: '🌧️',
1036: '🌊',
1037: '⛰️',
1038: '🌧️',
1039: '🌡️',
1040: '❄️',
1041: '🔥',
1042: '🏥',
1043: '⛈️',
1044: '🏫',
1045: '🏭',
1046: '🚢',
1047: '💨',
1048: '🌡️',
1049: '🌀',
1050: '🥶',
1051: '💨',
1052: '⛈️',
1053: '🌫️',
1054: '⚡',
1055: '🌀',
1056: '🌡️',
1057: '🧊',
1058: '⛈️',
1059: '🥶',
1060: '🌫️',
1061: '💨',
1062: '🌊',
1063: '🌧️',
1064: '🌧️',
1065: '🌫️',
1066: '🥵',
1067: '😶‍🌫️',
1068: '⚠️',
1069: '🏥',
1071: '🏥',
1072: '🏥',
1073: '🌊',
1074: '😶‍🌫️',
1075: '🏙️',
1076: '🌊',
1077: '🔥',
1078: '☀️',
1079: '🌾',
1080: '💨',
1081: '🧊',
1082: '🏥',
1084: '🔥',
1085: '⛈️',
1086: '🧊',
1087: '🌡️',
1088: '🌾',
1089: '🌾',
1201: '🌊',
1202: '🏙️',
1203: '⚠️',
1204: '⚠️',
1205: '🧊',
1206: '🌊',
1207: '🌊',
1208: '☀️',
1209: '🌊',
1210: '💧',
1211: '🌊',
1212: '🌊',
1213: '🏙️',
1214: '🌊',
1215: '☀️',
1216: '💧',
1217: '🌿',
1218: '⚠️',
1219: '🌊',
1221: '☀️',
1241: '⛰️',
1242: '⛰️',
1243: '⛰️',
1244: '⛰️',
1245: '⛰️',
1246: '⛰️',
1247: '🌋',
1248: '⛰️',
1249: '⛰️',
1250: '⛰️',
1251: '⛰️',
1271: '😶‍🌫️',
1272: '😶‍🌫️',
1273: '😶‍🌫️',
9999: '⚠️'
};
export const getWeatherIcon = (weatherCode: string): string => {
return weatherIcons[weatherCode] || weatherIcons[100];
};

View File

@@ -2,7 +2,7 @@
<div class="box"> <div class="box">
<div class="header"> <div class="header">
<span>智慧物业后台管理系统</span> <span>智慧物业后台管理系统</span>
<navbar ref="navbarRef" /> <navbar :Showhamburger="false" ref="navbarRef" />
</div> </div>
<div class="container AddBuildingBox"> <div class="container AddBuildingBox">
<el-form inline class="formBody" ref="formRef" :model="form" :rules="rules" label-width="100px"> <el-form inline class="formBody" ref="formRef" :model="form" :rules="rules" label-width="100px">
@@ -185,7 +185,6 @@ function getCommunityinfo() {
const id = editId.value; const id = editId.value;
getVillageByIdAPI(id).then((res) => { getVillageByIdAPI(id).then((res) => {
city.value = res.data.city.split('/'); city.value = res.data.city.split('/');
console.log(res.data.city.split('/'));
if (res.data.villageImageUrl) { if (res.data.villageImageUrl) {
villageImg.value = res.data.villageImageUrl villageImg.value = res.data.villageImageUrl
.split(',') .split(',')
@@ -311,25 +310,25 @@ const submitForm = () => {
}; };
function submitFormInfo() { function submitFormInfo() {
if (form.value.managePhone.trim()) { if (form.value.managePhone && form.value.managePhone.trim()) {
if (!reg.phone.test(form.value.managePhone)) { if (!reg.phone.test(form.value.managePhone)) {
ElMessage.error('请输入正确的手机号码'); ElMessage.error('请输入正确的手机号码');
return; return;
} }
} }
if (form.value.manageVx.trim()) { if (form.value.manageVx && form.value.manageVx.trim()) {
if (!reg.wechat.test(form.value.manageVx)) { if (!reg.wechat.test(form.value.manageVx)) {
ElMessage.error('请输入正确的微信号'); ElMessage.error('请输入正确的微信号');
return; return;
} }
} }
if (form.value.manageQq.trim()) { if (form.value.manageQq && form.value.manageQq.trim()) {
if (!reg.qq.test(form.value.manageQq)) { if (!reg.qq.test(form.value.manageQq)) {
ElMessage.error('请输入正确的QQ号码'); ElMessage.error('请输入正确的QQ号码');
return; return;
} }
} }
if (form.value.manageEmail.trim()) { if (form.value.manageEmail && form.value.manageEmail.trim()) {
if (!reg.email.test(form.value.manageEmail)) { if (!reg.email.test(form.value.manageEmail)) {
ElMessage.error('请输入正确的邮箱号码'); ElMessage.error('请输入正确的邮箱号码');
return; return;
@@ -349,9 +348,7 @@ function submitFormInfo() {
} }
} }
const cancelForm = () => { const cancelForm = () => {
router.push({ router.back();
path: '/community'
});
}; };
</script> </script>

View File

@@ -2,7 +2,7 @@
<div class="h-full box"> <div class="h-full box">
<div class="header"> <div class="header">
<span class="">智慧物业后台管理系统</span> <span class="">智慧物业后台管理系统</span>
<navbar ref="navbarRef" /> <navbar :Showhamburger="false" ref="navbarRef" />
</div> </div>
<div class="containerbox"> <div class="containerbox">
<div class="community_nav_box"> <div class="community_nav_box">
@@ -95,6 +95,7 @@ function handleEdit(item: communitylist_rows_type) {
/** 管理小区 */ /** 管理小区 */
function handleclick(item: communitylist_rows_type) { function handleclick(item: communitylist_rows_type) {
switchVillageAPI(item.villageId).then(() => { switchVillageAPI(item.villageId).then(() => {
localStorage.setItem('villageInfo', JSON.stringify(item));
localStorage.setItem('villageid', item.villageId); localStorage.setItem('villageid', item.villageId);
houseStore.getviliageinfo(item.villageId); houseStore.getviliageinfo(item.villageId);
router.push({ router.push({
@@ -200,12 +201,15 @@ function handleclick(item: communitylist_rows_type) {
display: grid; display: grid;
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, 1fr);
@media (max-width: 1900px) { @media (max-width: 2350px) {
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
} }
@media (max-width: 1500px) { @media (max-width: 1900px) {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
} }
@media (max-width: 1450px) {
grid-template-columns: repeat(2, 1fr);
}
.coummunity_card { .coummunity_card {
min-width: 369px; min-width: 369px;
max-width: 369px; max-width: 369px;

View File

@@ -3,7 +3,7 @@
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]"> <div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover"> <el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <el-form ref="queryFormRef" @submit.prevent="handleQuery" :model="queryParams" :inline="true">
<el-form-item label="key键" prop="testKey"> <el-form-item label="key键" prop="testKey">
<el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@@ -11,8 +11,8 @@
<el-input v-model="queryParams.value" placeholder="请输入值" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.value" placeholder="请输入值" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>
@@ -26,7 +26,7 @@
<el-button v-hasPermi="['demo:demo:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button> <el-button v-hasPermi="['demo:demo:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button v-hasPermi="['demo:demo:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button> <el-button v-hasPermi="['demo:demo:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">编辑</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button v-hasPermi="['demo:demo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" <el-button v-hasPermi="['demo:demo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
@@ -148,10 +148,14 @@ const { queryParams, form, rules } = toRefs(data);
/** 查询测试单列表 */ /** 查询测试单列表 */
const getList = async () => { const getList = async () => {
loading.value = true; loading.value = true;
const res = await listDemo(queryParams.value); listDemo(queryParams.value)
.then((res) => {
demoList.value = res.rows; demoList.value = res.rows;
total.value = res.total; total.value = res.total;
})
.finally(() => {
loading.value = false; loading.value = false;
});
}; };
/** 取消按钮 */ /** 取消按钮 */

View File

@@ -3,13 +3,13 @@
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]"> <div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover"> <el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <el-form ref="queryFormRef" @submit.prevent="handleQuery" :model="queryParams" :inline="true">
<el-form-item label="树节点名" prop="treeName"> <el-form-item label="树节点名" prop="treeName">
<el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>

View File

@@ -15,7 +15,7 @@
<div class="bullshit__info"> <div class="bullshit__info">
对不起您正在寻找的页面不存在尝试检查URL的错误然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容 对不起您正在寻找的页面不存在尝试检查URL的错误然后按浏览器上的刷新按钮或尝试在我们的应用程序中找到其他内容
</div> </div>
<router-link to="/index" class="bullshit__return-home"> 返回首页 </router-link> <router-link to="/" class="bullshit__return-home"> 返回首页 </router-link>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,11 +1,11 @@
<template> <template>
<div class="p-2"> <div class="h-full flex flex-col p-2">
<pageheader></pageheader> <pageheader></pageheader>
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]"> <div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover"> <el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <el-form ref="queryFormRef" @submit.prevent="handleQuery" :model="queryParams" :inline="true">
<el-form-item label="登录地址" prop="ipaddr"> <el-form-item label="登录地址" prop="ipaddr">
<el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@@ -29,15 +29,15 @@
></el-date-picker> ></el-date-picker>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition> </transition>
<el-card shadow="hover"> <el-card class="flex-1" shadow="hover">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
@@ -123,7 +123,7 @@ const single = ref(true);
const multiple = ref(true); const multiple = ref(true);
const selectName = ref<Array<string>>([]); const selectName = ref<Array<string>>([]);
const total = ref(0); const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']); const dateRange = ref<[string, string]>(['', '']);
const defaultSort = ref<any>({ prop: 'loginTime', order: 'descending' }); const defaultSort = ref<any>({ prop: 'loginTime', order: 'descending' });
const queryFormRef = ref<ElFormInstance>(); const queryFormRef = ref<ElFormInstance>();
@@ -209,3 +209,9 @@ onMounted(() => {
getList(); getList();
}); });
</script> </script>
<style lang="scss" scoped>
.el-table {
height: calc(100% - 80px);
}
</style>

Some files were not shown because too many files have changed in this diff Show More