diff --git a/.env.production b/.env.production index 24f4611..e794369 100644 --- a/.env.production +++ b/.env.production @@ -17,7 +17,7 @@ VITE_APP_SNAILJOB_ADMIN='/snail-job' # 生产环境 # VITE_APP_BASE_API = 'https://wuyeapi.bugtc.com' VITE_APP_BASE_API='https://wuyeapi.ckdzkj.com' - +#VITE_APP_BASE_API='http://192.168.1.198' # 登录成功跳转地址 # VITE_LOGIN_BACK_URL = '/equipment/WaterDevice' VITE_LOGIN_BACK_URL='/index' diff --git a/src/api/system/ParkingCost/index.ts b/src/api/system/ParkingCost/index.ts index cffef45..d0f573e 100644 --- a/src/api/system/ParkingCost/index.ts +++ b/src/api/system/ParkingCost/index.ts @@ -61,3 +61,61 @@ export const delParkingCost = (id: string | number | Array) => method: 'delete' }); }; + +export type Request = { + /** + * 区域id + */ + areaId?: string; + /** + * 0 审核中 1 审核通过 2 审核不通过 + */ + checkStatus?: string; + /** + * 添加者姓名,sys_user nickname + */ + createName?: string; + /** + * id车位管理表 + */ + id?: string; + /** + * 车位面积(单位㎡) + */ + parkingArea?: number; + /** + * 车位编号 + */ + parkingCode?: string; + /** + * 车位楼层 + */ + parkingFloor?: string; + /** + * 车位状态 0 闲置 1自用 2出租 + */ + parkingStatus?: string; + /** + * 备注 + */ + remark?: string; + /** + * 住户id + */ + residentId?: string; + /** + * 所属小区id + */ + villageId?: string; +}; +/** + * 查询停车缴费 车位列表 + * @param {Request} queryParams + */ +export const getParkingCostCarAreaApi = (queryParams: Request) => { + return request({ + url: '/parkingCost/parkingList', + method: 'get', + params: queryParams + }); +}; diff --git a/src/api/system/SdbCashier/index.ts b/src/api/system/SdbCashier/index.ts index ed99743..580a138 100644 --- a/src/api/system/SdbCashier/index.ts +++ b/src/api/system/SdbCashier/index.ts @@ -22,8 +22,9 @@ export const cashQuery = (data: { residentName?: string; queryType: number }): A }; export type billlistType = { - actualAccount: string; - singleDiscountAmount: string; + needPayAmount: string; // 需要支付金额 + actualAccount: string; // 实际支付金额 + singleDiscountAmount: string; // 单笔优惠金额 'villageId': string; 'residentId': string; 'houseId': string; @@ -61,7 +62,10 @@ export const billEdit = (data: { 'detailsId': string[]; 'payType': string; 'payS return request({ url: '/cash/billEdit', method: 'post', - data: data + data: data, + headers: { + 'isEncrypt': 'true' + } }); }; diff --git a/src/api/system/community/type.ts b/src/api/system/community/type.ts index dc799ab..c446d72 100644 --- a/src/api/system/community/type.ts +++ b/src/api/system/community/type.ts @@ -4,9 +4,9 @@ export interface communitylist_rows_type { villageCode: string; address: string; houseUse: number | null; - villageArea: number; + villageArea: string; remark: string; - parkingSpaceCount: number; + parkingSpaceCount: string; villageImageUrl: string; city: string; status: '0' | '1'; diff --git a/src/api/system/residentReviewProcess/resident/type.ts b/src/api/system/residentReviewProcess/resident/type.ts index a74ecaa..b52ae86 100644 --- a/src/api/system/residentReviewProcess/resident/type.ts +++ b/src/api/system/residentReviewProcess/resident/type.ts @@ -5,6 +5,7 @@ export interface ResidentVO { userId: string; id: string; unitNoId: string; + idCardType: string; /** * 住户编号 */ @@ -81,7 +82,13 @@ export interface ResidentForm extends BaseEntity { */ userId?: string; unitNoId?: string; - + /** + * 证件类型 + * 0 身份证 + * 1 护照 + * 2 港澳通行证 + */ + idCardType?: string; /** * 住户编号 */ diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index ba5288a..5fe571d 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -212,10 +212,11 @@ export const deptTreeSelect = (): AxiosPromise => { /** * 查询用户 根据不同的角色key */ -export const getUserByRoleKey = (roleKey: string = 'repair') => { +export const getUserByRoleKey = (data: { roleKey: string; pageSize?: number; pageNum?: number; phonenumber?: string; nickName?: string }) => { return request({ - url: '/system/user/list/byRoleKey?roleKey=' + roleKey, - method: 'get' + url: '/system/user/list/byRoleKey', + method: 'get', + params: data }); }; diff --git a/src/assets/icons/svg/user.svg b/src/assets/icons/svg/user.svg index 0ba0716..78d5981 100644 --- a/src/assets/icons/svg/user.svg +++ b/src/assets/icons/svg/user.svg @@ -1 +1,5 @@ - \ No newline at end of file + + + + + diff --git a/src/assets/icons/svg/user_login.svg b/src/assets/icons/svg/user_login.svg new file mode 100644 index 0000000..da730ce --- /dev/null +++ b/src/assets/icons/svg/user_login.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/Editor/index.vue b/src/components/Editor/index.vue index 2f906ea..20b14a4 100644 --- a/src/components/Editor/index.vue +++ b/src/components/Editor/index.vue @@ -382,6 +382,9 @@ onUnmounted(() => { } diff --git a/src/components/RightToolbar/index.vue b/src/components/RightToolbar/index.vue index 77ed1da..7fbe383 100644 --- a/src/components/RightToolbar/index.vue +++ b/src/components/RightToolbar/index.vue @@ -52,7 +52,7 @@ import { CSSProperties } from 'vue'; const props = defineProps({ columns: propTypes.fieldOption, gutter: propTypes.number.def(10), - moreTitle: propTypes.string.def('批量操作'), + moreTitle: propTypes.string.def('更多操作'), showAdd: propTypes.bool.def(true), showDelete: propTypes.bool.def(true), diff --git a/src/components/StepTimeline/index.vue b/src/components/StepTimeline/index.vue index 520cde5..c418177 100644 --- a/src/components/StepTimeline/index.vue +++ b/src/components/StepTimeline/index.vue @@ -79,7 +79,7 @@ defineProps<{ display: flex; align-items: center; justify-content: center; - font-size: 12px; + font-size: 11px; font-weight: 800; flex-shrink: 0; z-index: 1; diff --git a/src/store/modules/house.ts b/src/store/modules/house.ts index 11a9324..6b6ea67 100644 --- a/src/store/modules/house.ts +++ b/src/store/modules/house.ts @@ -183,11 +183,32 @@ export const useHouseStore = defineStore( // 清空 楼层信息 floorslist.value.clear(); // 重新获取楼层信息 - currentHouseInfoList.value.units.forEach((item) => { + const units = currentHouseInfoList.value.units.toSorted((a, b) => { + const aVal = a.unitNoName; + const bVal = b.unitNoName; + // 判断是否为纯数字 + const aIsNumber = /^-?\d+$/.test(aVal); + const bIsNumber = /^-?\d+$/.test(bVal); + if (aIsNumber && bIsNumber) { + // 都是数字,按数值排序 + return Number(aVal) - Number(bVal); + } else if (aIsNumber) { + // a是数字,优先排前面 + return -1; + } else if (bIsNumber) { + // b是数字,优先排前面 + return 1; + } else { + // 都是文字,按中文字典序排序 + return aVal.localeCompare(bVal, 'zh-CN'); + } + }); + units.forEach((item) => { item.floors.forEach((floor) => { floorslist.value.add(floor.floorNo); }); }); + console.log(units); return; }); }; diff --git a/src/utils/Reg.ts b/src/utils/Reg.ts index 26b8230..1fad5f8 100644 --- a/src/utils/Reg.ts +++ b/src/utils/Reg.ts @@ -51,7 +51,11 @@ const reg = { /** QQ */ qq: /^[1-9]\d{4,11}$/, /** 微信 */ - wechat: /^[a-zA-Z][a-zA-Z0-9_-]{5,19}$/ + wechat: /^[a-zA-Z][a-zA-Z0-9_-]{5,19}$/, + /** 护照 */ + passport: /^[EG]\d{8}$/, + /** 港澳通行证 */ + hongkongPassport: /^[CHMW]\d{8}$/ }; // 自动推导 TS 类型(最标准) export type RegType = keyof typeof reg; diff --git a/src/utils/Reg2.ts b/src/utils/Reg2.ts new file mode 100644 index 0000000..dca27bd --- /dev/null +++ b/src/utils/Reg2.ts @@ -0,0 +1,699 @@ +/** + * 中国车牌号校验工具 + * + * 支持的车牌类型: + * 1. 普通蓝牌/黄牌(7位)— 小型汽车 / 大型汽车 / 摩托车 + * 2. 新能源小车(8位)— 纯电动(D) / 非纯电动(F) + * 3. 新能源大车(8位)— 纯电动(D) / 非纯电动(F) + * 4. 警车牌(7位) + * 5. 教练车牌(7位) + * 6. 挂车牌(7位) + * 7. 港澳入出境车牌(7位) + * 8. 临时行驶车牌(8位) + * 9. 使馆车牌 + * 10. 领事馆车牌(7位) + * 11. 武警车牌 + * 12. 试车牌(7位) + * + * 合理性校验规则: + * - 字母数字混合段至少包含 1 位数字,拒绝纯字母组合(如京AAAAAA) + * - 字母排除 I、O(避免与数字 1、0 混淆) + */ + +// ===================== 常量 ===================== + +/** 省份简称(31个) */ +export const PROVINCES = [ + '京', + '津', + '沪', + '渝', + '冀', + '豫', + '云', + '辽', + '黑', + '湘', + '皖', + '鲁', + '新', + '苏', + '浙', + '赣', + '鄂', + '桂', + '甘', + '晋', + '蒙', + '陕', + '吉', + '闽', + '贵', + '粤', + '青', + '藏', + '川', + '宁', + '琼' +] as const; + +/** 省份正则字符集 */ +const PROVINCE_CHARS = `[${PROVINCES.join('')}]`; + +/** 合法字母数字(排除 I、O) */ +const ALPHANUM = `[A-HJ-NP-Z0-9]`; + +/** 纯数字 */ +const DIGIT = `[0-9]`; + +/** 字母(排除 I、O) */ +const LETTER = `[A-HJ-NP-Z]`; + +/** 车牌类型枚举 */ +export enum PlateType { + /** 普通蓝牌/黄牌(7位) */ + STANDARD = 'standard', + /** 新能源小车(8位) */ + NEW_ENERGY_SMALL = 'newEnergySmall', + /** 新能源大车(8位) */ + NEW_ENERGY_LARGE = 'newEnergyLarge', + /** 警车(7位) */ + POLICE = 'police', + /** 教练车(7位) */ + COACH = 'coach', + /** 挂车(7位) */ + TRAILER = 'trailer', + /** 港澳入出境车(7位) */ + HK_MACAU = 'hkMacau', + /** 临时车牌(8位) */ + TEMPORARY = 'temporary', + /** 使馆车 */ + EMBASSY = 'embassy', + /** 领事馆车(7位) */ + CONSULATE = 'consulate', + /** 武警车 */ + ARMED_POLICE = 'armedPolice', + /** 试车牌(7位) */ + TEST = 'test' +} + +/** 车牌类型中文标签 */ +export const PLATE_TYPE_LABELS: Record = { + [PlateType.STANDARD]: '普通车牌', + [PlateType.NEW_ENERGY_SMALL]: '新能源小型车', + [PlateType.NEW_ENERGY_LARGE]: '新能源大型车', + [PlateType.POLICE]: '警车', + [PlateType.COACH]: '教练车', + [PlateType.TRAILER]: '挂车', + [PlateType.HK_MACAU]: '港澳入出境车', + [PlateType.TEMPORARY]: '临时车牌', + [PlateType.EMBASSY]: '使馆车', + [PlateType.CONSULATE]: '领事馆车', + [PlateType.ARMED_POLICE]: '武警车', + [PlateType.TEST]: '试车牌' +}; + +/** 车牌类型颜色(用于 UI 标识) */ +export const PLATE_TYPE_COLORS: Record = { + [PlateType.STANDARD]: '#409eff', // 蓝色 + [PlateType.NEW_ENERGY_SMALL]: '#67c23a', // 绿色 + [PlateType.NEW_ENERGY_LARGE]: '#67c23a', // 绿色 + [PlateType.POLICE]: '#f56c6c', // 红色 + [PlateType.COACH]: '#e6a23c', // 黄色 + [PlateType.TRAILER]: '#e6a23c', // 黄色 + [PlateType.HK_MACAU]: '#909399', // 灰色 + [PlateType.TEMPORARY]: '#9b59b6', // 紫色 + [PlateType.EMBASSY]: '#909399', // 灰色 + [PlateType.CONSULATE]: '#909399', // 灰色 + [PlateType.ARMED_POLICE]: '#f56c6c', // 红色 + [PlateType.TEST]: '#909399' // 灰色 +}; + +/** 特殊结尾字符 */ +export const SPECIAL_CHARS = ['警', '学', '挂', '临', '港', '澳', '领', '试'] as const; + +/** 首位可输入字符集说明 */ +export const POSITION_RULES: { position: number; description: string }[] = [ + { position: 0, description: '省份简称(京、津、沪、渝等31个)' }, + { position: 1, description: '发牌机关代号字母(A-Z,排除I、O)' }, + { position: 2, description: '字母或数字(新能源小车此位为D纯电/F非纯电)' }, + { position: 3, description: '字母或数字' }, + { position: 4, description: '字母或数字' }, + { position: 5, description: '字母或数字' }, + { position: 6, description: '字母或数字,或特殊字符(警/学/挂/港/澳/领/试)' }, + { position: 7, description: '字母或数字,或特殊字符(临)/新能源标识(D/F)' } +]; + +// ===================== 正则表达式 ===================== + +/** + * 各类车牌正则表达式 + * + * 基础格式说明: + * - 省份: 1位(31个省份简称) + * - 字母: 发牌机关代号(排除I、O避免与数字1、0混淆) + * - 中间位: 字母或数字组合 + * - 特殊结尾: 警/学/挂/港/澳/领/临/试 + * - 新能源标识: D(纯电)/ F(非纯电/插混/燃料电池) + */ +const PATTERNS: Record = { + /** + * 普通蓝牌/黄牌(7位) + * 格式: 省 + 字母 + 5位字母数字 + * 示例: 京A12345, 京A123AB, 京AB1234 + * 适用: 小型汽车(蓝牌)、大型汽车(黄牌)、普通摩托车(黄牌)、轻便摩托车(蓝牌) + */ + [PlateType.STANDARD]: new RegExp(`^${PROVINCE_CHARS}${LETTER}${ALPHANUM}{5}$`), + + /** + * 新能源小型车(8位) + * 格式: 省 + 字母 + D/F + 5位字母数字 + * 示例: 京AD12345, 沪AF12345 + * 说明: D=纯电动, F=非纯电动(插电混动/增程式/燃料电池) + */ + [PlateType.NEW_ENERGY_SMALL]: new RegExp(`^${PROVINCE_CHARS}${LETTER}[DF]${ALPHANUM}{5}$`), + + /** + * 新能源大型车(8位) + * 格式: 省 + 字母 + 5位字母数字 + D/F + * 示例: 京A12345D, 沪A12345F + */ + [PlateType.NEW_ENERGY_LARGE]: new RegExp(`^${PROVINCE_CHARS}${LETTER}${ALPHANUM}{5}[DF]$`), + + /** + * 警车牌(7位) + * 格式: 省 + 字母 + 4位数字 + 警 + * 示例: 京A1234警, 粤B5678警 + */ + [PlateType.POLICE]: new RegExp(`^${PROVINCE_CHARS}${LETTER}${DIGIT}{4}警$`), + + /** + * 教练车牌(7位) + * 格式: 省 + 字母 + 4位字母数字 + 学 + * 示例: 京A1234学, 粤B123A学 + */ + [PlateType.COACH]: new RegExp(`^${PROVINCE_CHARS}${LETTER}${ALPHANUM}{4}学$`), + + /** + * 挂车牌(7位) + * 格式: 省 + 字母 + 4位字母数字 + 挂 + * 示例: 京A1234挂, 鲁B5678挂 + */ + [PlateType.TRAILER]: new RegExp(`^${PROVINCE_CHARS}${LETTER}${ALPHANUM}{4}挂$`), + + /** + * 港澳入出境车牌(7位) + * 格式: 省 + Z + 4位数字 + 港/澳 + * 示例: 粤Z1234港, 粤Z5678澳 + * 说明: 第二位固定为Z,黑底白字 + */ + [PlateType.HK_MACAU]: new RegExp(`^${PROVINCE_CHARS}Z${DIGIT}{4}[港澳]$`), + + /** + * 临时行驶车牌(8位) + * 格式: 省 + 字母 + 5位字母数字 + 临 + * 示例: 京A12345临, 沪B67890临 + */ + [PlateType.TEMPORARY]: new RegExp(`^${PROVINCE_CHARS}${LETTER}${ALPHANUM}{5}临$`), + + /** + * 使馆车牌 + * 格式: 使 + 3~5位数字 + * 示例: 使123, 使12345 + * 说明: 黑底白字,不以省份开头 + */ + [PlateType.EMBASSY]: /^使[0-9]{3,5}$/, + + /** + * 领事馆车牌(7位) + * 格式: 省 + 字母 + 4位数字 + 领 + * 示例: 京A1234领, 沪B5678领 + */ + [PlateType.CONSULATE]: new RegExp(`^${PROVINCE_CHARS}${LETTER}${DIGIT}{4}领$`), + + /** + * 武警车牌 + * 格式1: WJ + 省 + 4位数字 + 字母 (例: WJ京1234X) + * 格式2: WJ + 5位数字 + 字母 (例: WJ12345X) + * 格式3: WJ + 省 + 5位数字 (例: WJ京12345) + * 说明: 白底红字"WJ" + */ + [PlateType.ARMED_POLICE]: /^WJ(?:[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼])?[0-9]{4,5}[A-Z]?$/, + + /** + * 试车牌(7位) + * 格式: 省 + 字母 + 试 + 4位字母数字 + * 示例: 京A试1234, 沪B试5678 + */ + [PlateType.TEST]: new RegExp(`^${PROVINCE_CHARS}${LETTER}试${ALPHANUM}{4}$`) +}; + +// ===================== 合理性校验 ===================== + +/** + * 字母数字混合段至少包含 N 位数字的规则。 + * + * 车牌序号(字母数字混合)不能全为字母—— + * 现实中不存在 京AAAAAA / 京ADAAAAA / 京A试AAAA 这类车牌。 + */ +interface DigitRequirement { + /** 需要统计数字的起始索引(含) */ + start: number; + /** 需要统计数字的结束索引(含) */ + end: number; + /** 区间内最少数字个数 */ + minDigits: number; +} + +/** 各类型字母数字段的数字最低要求 */ +const DIGIT_REQUIREMENT: Partial> = { + [PlateType.STANDARD]: { start: 2, end: 6, minDigits: 1 }, + [PlateType.NEW_ENERGY_SMALL]: { start: 3, end: 7, minDigits: 1 }, + [PlateType.NEW_ENERGY_LARGE]: { start: 2, end: 6, minDigits: 1 }, + [PlateType.COACH]: { start: 2, end: 5, minDigits: 1 }, + [PlateType.TRAILER]: { start: 2, end: 5, minDigits: 1 }, + [PlateType.TEMPORARY]: { start: 2, end: 6, minDigits: 1 }, + [PlateType.TEST]: { start: 3, end: 6, minDigits: 1 } +}; + +// ===================== 校验类 ===================== + +export interface ValidationResult { + valid: boolean; + type?: PlateType; + label?: string; + color?: string; + message?: string; +} + +export class LicensePlateValidator2 { + /** 省份列表 */ + static readonly PROVINCES = PROVINCES; + + /** 特殊字符列表 */ + static readonly SPECIAL_CHARS = SPECIAL_CHARS; + + /** 所有正则 */ + static readonly PATTERNS = PATTERNS; + + // ---- 公开方法 ---- + + /** + * 校验车牌号是否合法(格式 + 合理性) + * @param value 车牌号字符串 + * @returns 是否合法 + */ + static isValidPlate(value: string): boolean { + const val = (value || '').toUpperCase().trim(); + if (!val) return false; + // 统一走 getPlateType,确保格式校验和合理性校验一致 + return this.getPlateType(val) !== null; + } + + /** + * 获取车牌类型(含格式校验 + 合理性校验) + * @param value 车牌号字符串 + * @returns 车牌类型枚举,未匹配返回 null + */ + static getPlateType(value: string): PlateType | null { + const val = (value || '').toUpperCase().trim(); + if (!val) return null; + + // 使馆车(不以省份开头,先匹配再校验数字段不需要额外检查) + if (val.startsWith('使') && PATTERNS[PlateType.EMBASSY].test(val)) { + return PlateType.EMBASSY; + } + + // 武警车(以WJ开头) + if (val.startsWith('WJ') && PATTERNS[PlateType.ARMED_POLICE].test(val)) { + return PlateType.ARMED_POLICE; + } + + // 以省份开头的车牌 + if (PROVINCES.includes(val[0] as any)) { + // 按优先级逐条匹配(特殊结尾 > 新能源 > 普通) + + if (val.endsWith('警') && PATTERNS[PlateType.POLICE].test(val)) { + return PlateType.POLICE; + } + if (val.endsWith('学')) { + if (PATTERNS[PlateType.COACH].test(val) && this._checkDigits(val, PlateType.COACH)) { + return PlateType.COACH; + } + return null; + } + if (val.endsWith('挂')) { + if (PATTERNS[PlateType.TRAILER].test(val) && this._checkDigits(val, PlateType.TRAILER)) { + return PlateType.TRAILER; + } + return null; + } + if (val.endsWith('港') || val.endsWith('澳')) { + if (PATTERNS[PlateType.HK_MACAU].test(val)) { + return PlateType.HK_MACAU; + } + return null; + } + if (val.endsWith('领') && PATTERNS[PlateType.CONSULATE].test(val)) { + return PlateType.CONSULATE; + } + if (val.endsWith('临')) { + if (PATTERNS[PlateType.TEMPORARY].test(val) && this._checkDigits(val, PlateType.TEMPORARY)) { + return PlateType.TEMPORARY; + } + return null; + } + if (val.includes('试')) { + if (PATTERNS[PlateType.TEST].test(val) && this._checkDigits(val, PlateType.TEST)) { + return PlateType.TEST; + } + return null; + } + + // 8位新能源 + if (val.length === 8) { + // 新能源小车: 第3位是D或F + if (/^[DF]$/.test(val[2]) && PATTERNS[PlateType.NEW_ENERGY_SMALL].test(val) && this._checkDigits(val, PlateType.NEW_ENERGY_SMALL)) { + return PlateType.NEW_ENERGY_SMALL; + } + // 新能源大车: 最后一位是D或F + if (/^[DF]$/.test(val[7]) && PATTERNS[PlateType.NEW_ENERGY_LARGE].test(val) && this._checkDigits(val, PlateType.NEW_ENERGY_LARGE)) { + return PlateType.NEW_ENERGY_LARGE; + } + } + + // 普通蓝牌/黄牌(7位) + if (val.length === 7 && PATTERNS[PlateType.STANDARD].test(val) && this._checkDigits(val, PlateType.STANDARD)) { + return PlateType.STANDARD; + } + } + + return null; + } + + /** + * 完整校验(返回类型和描述) + * @param value 车牌号字符串 + * @returns 校验结果对象 + */ + static validate(value: string): ValidationResult { + const val = (value || '').toUpperCase().trim(); + + if (!val) { + return { valid: false, message: '请输入车牌号' }; + } + + // 格式预检(提供更精准的错误信息) + const formatError = this._formatCheck(val); + if (formatError) { + return formatError; + } + + // 合法性校验 + const type = this.getPlateType(val); + + if (!type) { + return { valid: false, message: '请输入正确的车牌号' }; + } + + return { + valid: true, + type, + label: PLATE_TYPE_LABELS[type], + color: PLATE_TYPE_COLORS[type] + }; + } + + /** + * 获取车牌类型描述信息 + * @param value 车牌号字符串 + * @returns 描述字符串 + */ + static getPlateInfo(value: string): string { + const result = this.validate(value); + if (result.valid && result.label) { + return result.label; + } + return result.message || '未知格式'; + } + + /** + * 获取所有支持的车牌类型及其格式说明 + */ + static getAllPlateTypes(): { type: PlateType; label: string; format: string; example: string; color: string; length: number }[] { + return [ + { + type: PlateType.STANDARD, + label: '普通蓝牌/黄牌', + format: '省+字母+5位字母数字', + example: '京A12345', + color: PLATE_TYPE_COLORS[PlateType.STANDARD], + length: 7 + }, + { + type: PlateType.NEW_ENERGY_SMALL, + label: '新能源小型车(纯电D/非纯电F)', + format: '省+字母+D/F+5位字母数字', + example: '京AD12345', + color: PLATE_TYPE_COLORS[PlateType.NEW_ENERGY_SMALL], + length: 8 + }, + { + type: PlateType.NEW_ENERGY_LARGE, + label: '新能源大型车(纯电D/非纯电F)', + format: '省+字母+5位字母数字+D/F', + example: '京A12345D', + color: PLATE_TYPE_COLORS[PlateType.NEW_ENERGY_LARGE], + length: 8 + }, + { + type: PlateType.POLICE, + label: '警车', + format: '省+字母+4位数字+警', + example: '京A1234警', + color: PLATE_TYPE_COLORS[PlateType.POLICE], + length: 7 + }, + { + type: PlateType.COACH, + label: '教练车', + format: '省+字母+4位字母数字+学', + example: '京A1234学', + color: PLATE_TYPE_COLORS[PlateType.COACH], + length: 7 + }, + { + type: PlateType.TRAILER, + label: '挂车', + format: '省+字母+4位字母数字+挂', + example: '京A1234挂', + color: PLATE_TYPE_COLORS[PlateType.TRAILER], + length: 7 + }, + { + type: PlateType.HK_MACAU, + label: '港澳入出境车', + format: '省+Z+4位数字+港/澳', + example: '粤Z1234港', + color: PLATE_TYPE_COLORS[PlateType.HK_MACAU], + length: 7 + }, + { + type: PlateType.TEMPORARY, + label: '临时车牌', + format: '省+字母+5位字母数字+临', + example: '京A12345临', + color: PLATE_TYPE_COLORS[PlateType.TEMPORARY], + length: 8 + }, + { + type: PlateType.CONSULATE, + label: '领事馆车', + format: '省+字母+4位数字+领', + example: '京A1234领', + color: PLATE_TYPE_COLORS[PlateType.CONSULATE], + length: 7 + }, + { + type: PlateType.TEST, + label: '试车牌', + format: '省+字母+试+4位字母数字', + example: '京A试1234', + color: PLATE_TYPE_COLORS[PlateType.TEST], + length: 7 + }, + { + type: PlateType.EMBASSY, + label: '使馆车', + format: '使+3~5位数字', + example: '使12345', + color: PLATE_TYPE_COLORS[PlateType.EMBASSY], + length: 0 + }, + { + type: PlateType.ARMED_POLICE, + label: '武警车', + format: 'WJ+省(可选)+4~5位数字+字母(可选)', + example: 'WJ京1234X', + color: PLATE_TYPE_COLORS[PlateType.ARMED_POLICE], + length: 0 + } + ]; + } + + /** + * 校验指定位置的字符是否合法(逐字输入场景,如多格拆分输入框) + * @param char 待校验字符 + * @param index 位置索引(0-7) + * @param currentChars 当前所有字符(用于上下文判断) + * @returns 是否合法 + */ + static isValidCharAtPosition(char: string, index: number, currentChars: string[] = []): boolean { + if (!char) return true; + + const c = char.toUpperCase(); + const joined = currentChars.join('').toUpperCase().trim(); + + // 使馆车(以"使"开头):后续只能输入数字 + if (index === 0 && c === '使') return true; + if (joined.startsWith('使')) { + return /[0-9]/.test(c); + } + + // 武警车(WJ 前缀):前两位必须依次是 W、J + if (index === 0 && c === 'W') return true; + if (index === 1 && joined[0] === 'W' && c === 'J') return true; + if (joined.startsWith('WJ')) { + // WJ 后可以是 [省份] 或 [数字] + if (index === 2 && PROVINCES.includes(c as any)) return true; + // 数字段 + if (index >= 2 && /[0-9]/.test(c)) return true; + // 末尾可选字母 + if (index >= 6 && index <= 7 && /[A-Z]/.test(c)) return true; + return false; + } + + // 位置 0:必须是省份(W 已被 WJ 分支处理,不会到此处) + if (index === 0) { + return PROVINCES.includes(c as any); + } + + // 位置 1:必须是字母(排除 I、O) + if (index === 1) { + return /^[A-HJ-NP-Z]$/i.test(c); + } + + // 位置 2-5:字母或数字 + if (index >= 2 && index <= 5) { + return /^[A-HJ-NP-Z0-9]$/i.test(c); + } + + // 位置 6:字母数字 或 特殊结尾(警/学/挂/港/澳/领/试),排除"临" + if (index === 6) { + if (SPECIAL_CHARS.includes(c as any)) { + if (c === '临') return false; + return true; + } + return /^[A-HJ-NP-Z0-9]$/i.test(c); + } + + // 位置 7:字母数字 或 D/F(新能源大车)或 临(临时车牌) + if (index === 7) { + if (c === '临') return true; + return /^[A-HJ-NP-Z0-9]$/i.test(c); + } + + return false; + } + + /** + * 根据当前已输入内容推测车牌长度(7或8位) + * @param currentChars 当前已输入字符数组 + * @returns 推测的车牌长度 + */ + static guessPlateLength(currentChars: string[]): 7 | 8 { + const joined = currentChars.join('').toUpperCase().trim(); + + // 已有确定长度的特殊结尾 + if (['警', '学', '挂', '领', '港', '澳'].some((s) => joined.endsWith(s))) return 7; + if (joined.endsWith('临')) return 8; + if (joined.includes('试')) return 7; + + // 第3位是D或F → 新能源小车(8位) + if (joined.length >= 3 && /^[DF]$/.test(joined[2])) return 8; + + // 默认7位 + if (joined.length <= 7) return 7; + + // 已满8位,检查最后一位是否为D/F + if (joined.length === 8 && /^[DF]$/.test(joined[7])) return 8; + + return 8; + } + + // ---- 内部方法 ---- + + /** + * 合理性校验:字母数字混合段至少包含 minDigits 位数字 + * 用于拒绝京AAAAAA / 京ADAAAAA / 京A试AAAA 等纯字母组合 + */ + private static _checkDigits(val: string, type: PlateType): boolean { + const req = DIGIT_REQUIREMENT[type]; + if (!req) return true; // 无数字要求则通过 + + let count = 0; + const end = Math.min(req.end, val.length - 1); + for (let i = req.start; i <= end; i++) { + if (/[0-9]/.test(val[i])) count++; + } + return count >= req.minDigits; + } + + /** + * 格式预检:在 getPlateType 之前做轻量格式判断,给出更友好的错误提示 + */ + private static _formatCheck(val: string): ValidationResult | null { + // 首位检查(使馆和武警由 getPlateType 处理) + if (!PROVINCES.includes(val[0] as any) && !val.startsWith('使') && !val.startsWith('WJ')) { + return { valid: false, message: '请输入正确的车牌号' }; + } + + // 长度检查 + if (val.startsWith('使')) { + if (val.length < 4) { + return { valid: false, message: '请输入正确的车牌号' }; + } + if (val.length > 6) { + return { valid: false, message: '请输入正确的车牌号' }; + } + return null; + } + + if (val.startsWith('WJ')) { + if (val.length < 6) { + return { valid: false, message: '请输入正确的车牌号' }; + } + if (val.length > 9) { + return { valid: false, message: '请输入正确的车牌号' }; + } + return null; + } + + // 省份开头的车牌 + if (val.length < 7) { + return { valid: false, message: '请输入正确的车牌号' }; + } + if (val.length > 8) { + return { valid: false, message: '请输入正确的车牌号' }; + } + + // 第二位检查(排除 I、O) + if (val.length >= 2 && /[IO]/.test(val[1])) { + return { valid: false, message: '请输入正确的车牌号' }; + } + + return null; + } +} + +export default LicensePlateValidator2; diff --git a/src/utils/money.ts b/src/utils/money.ts index aee33ac..163e7ec 100644 --- a/src/utils/money.ts +++ b/src/utils/money.ts @@ -134,8 +134,8 @@ export function add(a: string | number, b: string | number): number { /** * 乘法 a * b */ -export function mul(a: string | number, b: string | number): number { - return Number(toBig(a).times(toBig(b))); +export function mul(a: string | number, b: string | number, decimal = 2): number { + return Number(toBig(a).times(toBig(b)).round(decimal)); } /** @@ -147,6 +147,50 @@ export function div(a: string | number, b: string | number): number { if (bigB.eq(0)) throw new Error('除数不能为0'); return Number(toBig(a).div(bigB)); } + +/** + * a > b + */ +export function gtMoney(a: string | number, b: string | number): boolean { + return toBig(a).gt(toBig(b)); +} + +/** + * a >= b + */ +export function gteMoney(a: string | number, b: string | number): boolean { + return toBig(a).gte(toBig(b)); +} + +/** + * a < b + */ +export function ltMoney(a: string | number, b: string | number): boolean { + return toBig(a).lt(toBig(b)); +} + +/** + * a <= b + */ +export function lteMoney(a: string | number, b: string | number): boolean { + return toBig(a).lte(toBig(b)); +} + +/** + * a === b 金额相等 + */ +export function eqMoney(a: string | number, b: string | number): boolean { + return toBig(a).eq(toBig(b)); +} + +/** + * a !== b 金额不相等 + */ +export function neqMoney(a: string | number, b: string | number): boolean { + return !toBig(a).eq(toBig(b)); +} + + // 保留2位小数(金额专用) export function toFixed2(val) { return Number(val).toFixed(2); diff --git a/src/utils/request.ts b/src/utils/request.ts index fdfc3af..99d4c79 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -78,7 +78,7 @@ service.interceptors.request.use( const villageid = localStorage.getItem('villageid'); config.headers['Content-Language'] = getLanguage(); config.headers['villageId'] = villageid; - const disableAutoVillage = config.headers?.autoVillage === false; + // const disableAutoVillage = config.headers?.autoVillage === false; const isToken = config.headers?.isToken === false; // 是否需要防止数据重复提交 diff --git a/src/views/community/AddCommunity.vue b/src/views/community/AddCommunity.vue index 7227d3b..fb3e6e5 100644 --- a/src/views/community/AddCommunity.vue +++ b/src/views/community/AddCommunity.vue @@ -10,16 +10,16 @@
基本信息
- + - + - + - + @@ -30,19 +30,10 @@ - + - - + @@ -50,11 +41,13 @@ @@ -70,12 +63,12 @@
- + @@ -83,19 +76,19 @@
负责人信息
- + - + - - + + - - + + - - + +
提交 @@ -122,12 +115,11 @@ import { Delete, Plus } from '@element-plus/icons-vue'; const route = useRoute(); const router = useRouter(); const editId = ref(route.query.id as string); -const isedit = computed(() => Boolean(editId.value)); - -const apipriex = import.meta.env.VITE_APP_BASE_API; -const uploadfileurl = `${apipriex}/village/uploadImages`; -// 获取省 市 区 -const pcaTextArrlist = ref(pcaTextArr); +const isEdit = computed(() => Boolean(editId.value)); +const apiPrefix = import.meta.env.VITE_APP_BASE_API; +const uploadFileUrl = `${apiPrefix}/village/uploadImages`; +// 获取省 市 区╬╬╬╬╬╬╬ +const pcaTextArrList = ref(pcaTextArr); // 获取小区用途 const houseUserList = ref([ { @@ -156,9 +148,9 @@ const form = ref({ villageCode: '', address: '', houseUse: null, - villageArea: 0, + villageArea: '', remark: '', - parkingSpaceCount: 0, + parkingSpaceCount: '', villageImageUrl: '', city: '', status: '0', @@ -177,12 +169,131 @@ const villageImg = ref([]); // 表单验证 const rules = ref({ villageName: [{ required: true, message: '请输入小区名称', trigger: 'blur' }], - villageCode: [{ required: true, message: '请输入小区编号', trigger: 'blur' }], - city: [{ required: true, message: '请选择城市', trigger: 'blur' }] + villageArea: [ + { required: false, message: '请输入小区面积', trigger: 'blur' }, + { + validator: (rule, value, callback) => { + const num = Number(value); + if (value === '' || value === null) { + return callback(); + } + if (Number.isNaN(num)) { + return callback(new Error('请输入正确的小区面积')); + } + if (num < 0) { + return callback(new Error('小区面积不能小于0')); + } + callback(); + } + } + ], + parkingSpaceCount: [ + { required: false, message: '请输入车位数量', trigger: 'blur' }, + { + validator: (rule, value, callback) => { + const num = Number(value); + if (value === '' || value === null) { + return callback(); + } + if (Number.isNaN(num)) { + return callback(new Error('请输入正确的车位数量')); + } + if (num < 0) { + return callback(new Error('车位数量不能小于0')); + } + if (num % 1 !== 0) { + return callback(new Error('请输入正确的整数车位数量')); + } + callback(); + } + } + ], + villageCode: [ + { required: true, message: '请输入小区编号', trigger: 'blur' }, + { + validator: (_, value, callback) => { + if (value === '' || value === null) { + return callback(new Error('请输入小区编号')); + } + if (!/^[a-zA-Z0-9_-]{1,20}$/.test(value)) { + callback(new Error('请输入正确的小区编号,只能包含字母、数字、_和-')); + } else { + callback(); + } + } + } + ], + city: [{ required: true, message: '请选择城市', trigger: 'blur' }], + managePhone: [ + { required: false, message: '请输入电话', trigger: 'blur' }, + { + validator: (_, value, callback) => { + // 值为空直接放行(非必填) + if (value === '' || value === null) { + return callback(); + } + if (!/^1[3456789]\d{9}$/.test(value)) { + callback(new Error('请输入正确的手机号')); + } else { + callback(); + } + } + } + ], + manageEmail: [ + { required: false, message: '请输入邮箱', trigger: 'blur' }, + { + validator: (_, value, callback) => { + if (value === '' || value === null) { + return callback(); + } + // 邮箱总长度不超过254个字符,用户名不超过64个字符 + if (value.length > 254 || value.split('@')[0].length > 64) { + callback(new Error('邮箱地址过长')); + } else if (!/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/.test(value)) { + callback(new Error('请输入正确的邮箱')); + } else { + callback(); + } + } + } + ], + manageVx: [ + { required: false, message: '请输入微信', trigger: 'blur' }, + { + validator: (_, value, callback) => { + // 值为空直接放行(非必填) + if (value === '' || value === null) { + return callback(); + } + if (!/^[a-zA-Z0-9_]{4,20}$/.test(value)) { + callback(new Error('请输入正确的微信')); + } else { + callback(); + } + } + } + ], + manageQq: [ + { required: false, message: '请输入QQ', trigger: 'blur' }, + { + validator: (_, value, callback) => { + // 值为空直接放行(非必填) + if (value === '' || value === null) { + return callback(); + } + if (!/^[1-9]\d{4,10}$/.test(value)) { + callback(new Error('请输入正确的QQ')); + } else { + callback(); + } + } + } + ] }); /** 获取编辑小区的信息 */ -function getCommunityinfo() { +function getCommunityInfo() { const id = editId.value; getVillageByIdAPI(id).then((res) => { city.value = res.data.city ? res.data.city.split('/') : []; @@ -206,9 +317,11 @@ function getCommunityinfo() { }; }); } -if (isedit.value) { - getCommunityinfo(); + +if (isEdit.value) { + getCommunityInfo(); } + // 请求头 function headerToken() { const useStore = useUserStore(); @@ -216,9 +329,11 @@ function headerToken() { Authorization: 'Bearer ' + useStore.token }; } + const allowImgSuffix = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic']; /** 检测文件大小和格式 */ function handleBeforeUpload(file: UploadFile): Promise { + const type = file.raw.type.split('/').pop(); return new Promise((resolve, reject) => { const isLt10M = file.size / 1024 / 1024 < 10; if (!isLt10M) { @@ -226,11 +341,13 @@ function handleBeforeUpload(file: UploadFile): Promise { villageImg.value = villageImg.value.filter((item) => item.uid !== file.uid); @@ -256,6 +376,7 @@ const handleRemove = (file: UploadFile) => { const changeBuiltTime = (value: string) => { form.value.completDate = value; }; + const cityChange = (value: CascaderValue[]) => { form.value.city = value.join('/'); }; @@ -299,7 +420,7 @@ const submitForm = () => { // 3) 有新文件:先上传新文件,再与已有URL合并 const formdata = new FormData(); newFiles.forEach((file) => formdata.append('files', file)); - + console.log('newFiles', newFiles); uploadVillageImageAPI(formdata).then((res) => { const uploadedUrls = res.data.success.map((x) => x.url).filter(Boolean); // 4) 最终图片 = 已有图片 + 新上传图片(避免覆盖) @@ -336,18 +457,19 @@ function submitFormInfo() { } } - if (isedit.value) { - editVillageAPI(form.value).then((res) => { + if (isEdit.value) { + editVillageAPI(form.value).then(() => { ElMessage.success('编辑成功'); cancelForm(); }); } else { - addVillageAPI(form.value).then((res) => { + addVillageAPI(form.value).then(() => { ElMessage.success('添加成功'); cancelForm(); }); } } +// 返回 const cancelForm = () => { router.back(); }; @@ -368,7 +490,6 @@ const cancelForm = () => { & > span { padding-left: 20px; font-size: 20px; - color: white; color: #000; } } diff --git a/src/views/login.vue b/src/views/login.vue index cf27183..de1a562 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -10,7 +10,7 @@
- + @@ -255,7 +255,7 @@ onMounted(() => { height: 393px; padding: 30px; border-radius: 10px; - background: linear-gradient(180deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.3) 100%); + background: linear-gradient(180deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.85) 60%, rgba(0, 0, 0, 0.55) 100%); .input-icon { height: 40px; width: 23px; diff --git a/src/views/system/Activity/addActivity.vue b/src/views/system/Activity/addActivity.vue index 6930714..e000514 100644 --- a/src/views/system/Activity/addActivity.vue +++ b/src/views/system/Activity/addActivity.vue @@ -12,12 +12,12 @@ - + - + @@ -48,7 +48,7 @@ - + @@ -77,19 +77,19 @@ - + - + - - + + @@ -97,8 +97,8 @@
- - (填 0 代表不限制名额 + + (填 0 代表 不限制名额
@@ -124,14 +124,21 @@ - + - + @@ -296,6 +303,8 @@ const upload = ref(); const handleAvatarSuccess: UploadProps['onSuccess'] = (response) => { if (response.data && response.data.url) { form.value.coverImage = response.data.url; + } else { + ElMessage.warning('上传频繁,请稍后再试'); } }; diff --git a/src/views/system/Activity/index.vue b/src/views/system/Activity/index.vue index 12a4a63..9361205 100644 --- a/src/views/system/Activity/index.vue +++ b/src/views/system/Activity/index.vue @@ -44,6 +44,7 @@ v-if="scope.row.coverImage" :src="scope.row.coverImage" fit="cover" + style="width: 100px; height: 100px" > 暂无 @@ -71,7 +72,7 @@ {{ scope.row.quota }}人 - +