From 4c48b04d5e2366166cdf6f78e3ea772ba1896f89 Mon Sep 17 00:00:00 2001 From: andyesfly <35127884@qq.com> Date: Sat, 24 Oct 2026 21:15:14 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E9=89=B4=E6=9D=83=E4=B8=8E=E7=99=BB=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debug.log | 1 + preview-qr.png | Bin 0 -> 47503 bytes server/.env | 4 + server/.env.local | 4 + server/src/config/config.default.ts | 6 + server/src/configuration.ts | 5 +- .../src/controller/customer_app.controller.ts | 91 ++++ server/src/dto/customer_app.dto.ts | 44 ++ server/src/middleware/auth.middleware.ts | 4 +- .../middleware/customer_auth.middleware.ts | 52 ++ server/src/middleware/rbac.middleware.ts | 2 +- server/src/service/customer_app.service.ts | 444 ++++++++++++++++++ server/src/util/token.ts | 33 ++ 13 files changed, 685 insertions(+), 5 deletions(-) create mode 100644 debug.log create mode 100644 preview-qr.png create mode 100644 server/src/controller/customer_app.controller.ts create mode 100644 server/src/dto/customer_app.dto.ts create mode 100644 server/src/middleware/customer_auth.middleware.ts create mode 100644 server/src/service/customer_app.service.ts diff --git a/debug.log b/debug.log new file mode 100644 index 0000000..dccf702 --- /dev/null +++ b/debug.log @@ -0,0 +1 @@ +[1024/162159.849:ERROR:registration_protocol_win.cc(102)] CreateFile: ϵͳÕҲ»µ½ָ¶¨µÄÎļþ¡£ (0x2) diff --git a/preview-qr.png b/preview-qr.png new file mode 100644 index 0000000000000000000000000000000000000000..d88f7633a75bdf22201811ccc2532a5b937891f7 GIT binary patch literal 47503 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 Rr(U(zyo>)MfA%wF|38q-hqeF! literal 0 HcmV?d00001 diff --git a/server/.env b/server/.env index 8225d21..4159300 100644 --- a/server/.env +++ b/server/.env @@ -18,6 +18,10 @@ RUSTFS_BUCKET_TEST=xintong-test # 当前生效的存储桶:生产=xintong,本地测试桶在 .env.local 中覆盖为 xintong-test RUSTFS_BUCKET=xintong +# 微信小程序配置(静默登录 code2session) +WX_APPID=wxd582e7ae4c9e1d14 +WX_SECRET=37bb9fcd1a9c9e903101364d6b780e93 + # Redis 配置(与 PostgreSQL / RustFS 同机部署) REDIS_HOST=222.243.55.42 REDIS_PORT=26678 diff --git a/server/.env.local b/server/.env.local index bf8cc79..570eff1 100644 --- a/server/.env.local +++ b/server/.env.local @@ -7,3 +7,7 @@ PG_PASSWORD=postgres # 本地开发使用测试桶(RustFS 仍复用服务器上的同一套地址与密钥) RUSTFS_BUCKET=xintong-test + +# 微信小程序配置(静默登录 code2session) +WX_APPID=wxd582e7ae4c9e1d14 +WX_SECRET=37bb9fcd1a9c9e903101364d6b780e93 diff --git a/server/src/config/config.default.ts b/server/src/config/config.default.ts index c8eabd8..c2f03f1 100644 --- a/server/src/config/config.default.ts +++ b/server/src/config/config.default.ts @@ -83,6 +83,12 @@ export default { refreshExpiresIn: "7d" }, + // 微信小程序(静默登录 code2session) + wx: { + appId: process.env.WX_APPID || "", + secret: process.env.WX_SECRET || "" + }, + // multipart/form-data 解析配置(现场照片上传) upload: { mode: "file", diff --git a/server/src/configuration.ts b/server/src/configuration.ts index e52b0a9..8dec9c5 100644 --- a/server/src/configuration.ts +++ b/server/src/configuration.ts @@ -7,6 +7,7 @@ import * as Upload from "@midwayjs/upload"; import { join } from "path"; import { AuthMiddleware } from "./middleware/auth.middleware"; import { WorkerAuthMiddleware } from "./middleware/worker_auth.middleware"; +import { CustomerAuthMiddleware } from "./middleware/customer_auth.middleware"; import { RbacMiddleware } from "./middleware/rbac.middleware"; import { LogMiddleware } from "./middleware/log.middleware"; import { JobSchedulerService } from "./service/job_scheduler.service"; @@ -20,8 +21,8 @@ export class ContainerConfigLifeCycle implements ILifeCycle { app!: Koa.Application; async onReady() { - // 注册全局中间件(顺序:电工鉴权 → 鉴权 → 权限 → 日志) - this.app.useMiddleware([WorkerAuthMiddleware, AuthMiddleware, RbacMiddleware, LogMiddleware]); + // 注册全局中间件(顺序:电工鉴权 → 小程序客户鉴权 → 管理端鉴权 → 权限 → 日志) + this.app.useMiddleware([WorkerAuthMiddleware, CustomerAuthMiddleware, AuthMiddleware, RbacMiddleware, LogMiddleware]); try { const scheduler = await this.app.getApplicationContext().getAsync(JobSchedulerService); await scheduler.start(); diff --git a/server/src/controller/customer_app.controller.ts b/server/src/controller/customer_app.controller.ts new file mode 100644 index 0000000..a575841 --- /dev/null +++ b/server/src/controller/customer_app.controller.ts @@ -0,0 +1,91 @@ +import { Body, Controller, Get, Inject, Param, Post, Put, Query, Files } from "@midwayjs/core"; +import { Context } from "@midwayjs/koa"; +import { UploadFileInfo } from "@midwayjs/upload"; +import { CustomerAppService } from "../service/customer_app.service"; +import { UploadService } from "../service/upload.service"; +import { + CustomerAcceptRepairDTO, + CustomerCreateRepairDTO, + CustomerLoginDTO, + CustomerWxBindDTO, + CustomerWxLoginDTO +} from "../dto/customer_app.dto"; + +@Controller("/api/customer-app") +export class CustomerAppController { + @Inject() + ctx!: Context; + + @Inject() + customerAppService!: CustomerAppService; + + @Inject() + uploadService!: UploadService; + + // 账号密码登录 + @Post("/auth/login") + async login(@Body() dto: CustomerLoginDTO) { + return this.customerAppService.login(dto.phone, dto.password); + } + + // 微信静默登录(未绑定返回 bindToken) + @Post("/auth/wx-login") + async wxLogin(@Body() dto: CustomerWxLoginDTO) { + return this.customerAppService.wxLogin(dto.code); + } + + // 微信首次绑定:手机号 + 密码 + bindToken + @Post("/auth/wx-bind") + async wxBind(@Body() dto: CustomerWxBindDTO) { + return this.customerAppService.wxBind(dto.bindToken, dto.phone, dto.password); + } + + // 我的:账号、客户、统计、客户经理 + @Get("/profile") + async profile() { + return this.customerAppService.profile(this.currentAccountId()); + } + + // 报修工单列表(按状态页签过滤) + @Get("/repairs") + async listRepairs( + @Query("tab") tab: string, + @Query("page") page: string, + @Query("pageSize") pageSize: string + ) { + return this.customerAppService.listRepairs(this.currentAccountId(), tab, Number(page) || 1, Number(pageSize) || 20); + } + + // 客户提交报修,落库为待处理工单 + @Post("/repairs") + async createRepair(@Body() dto: CustomerCreateRepairDTO) { + return this.customerAppService.createRepair(this.currentAccountId(), dto); + } + + // 客户验收并评价 + @Put("/repairs/:id/accept") + async acceptRepair(@Param("id") id: string, @Body() dto: CustomerAcceptRepairDTO) { + return this.customerAppService.acceptRepair(this.currentAccountId(), Number(id), dto.rating); + } + + // 报修照片上传 + @Post("/upload/image") + async uploadImage(@Files() files: UploadFileInfo[]) { + const file = files && files[0]; + if (!file) return { code: 400, message: "请选择要上传的图片", data: null }; + try { + const data = await this.uploadService.uploadFile(file); + return { code: 200, message: "上传成功", data }; + } catch (err: any) { + return { code: 500, message: `图片上传失败:${err?.message || "对象存储不可用"}`, data: null }; + } + } + + private currentAccountId(): number { + const customer = this.ctx.state.customer; + if (!customer?.accountId) { + throw new Error("未登录"); + } + return Number(customer.accountId); + } +} diff --git a/server/src/dto/customer_app.dto.ts b/server/src/dto/customer_app.dto.ts new file mode 100644 index 0000000..91aaef9 --- /dev/null +++ b/server/src/dto/customer_app.dto.ts @@ -0,0 +1,44 @@ +import { + IsArray, + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Matches, + Max, + MaxLength, + Min, + MinLength +} from "class-validator"; + +// 账号密码登录 +export class CustomerLoginDTO { + @IsString() @Matches(/^1\d{10}$/, { message: "请输入正确的手机号" }) phone!: string; + @IsString() @MinLength(6) @MaxLength(32) password!: string; +} + +// 微信静默登录 +export class CustomerWxLoginDTO { + @IsString() @IsNotEmpty() code!: string; +} + +// 微信首次绑定(未绑定 openid 时,用手机号密码完成绑定) +export class CustomerWxBindDTO { + @IsString() @IsNotEmpty() bindToken!: string; + @IsString() @Matches(/^1\d{10}$/, { message: "请输入正确的手机号" }) phone!: string; + @IsString() @MinLength(6) @MaxLength(32) password!: string; +} + +// 发起报修 +export class CustomerCreateRepairDTO { + @IsString() @MaxLength(256) location!: string; + @IsString() @MaxLength(2000) repairContent!: string; + @IsOptional() @IsArray() photos?: string[]; + @IsOptional() @IsInt() buildingId?: number; + @IsOptional() @IsString() @MaxLength(16) floorNo?: string; +} + +// 验收评价 +export class CustomerAcceptRepairDTO { + @IsInt() @Min(1) @Max(5) rating!: number; +} diff --git a/server/src/middleware/auth.middleware.ts b/server/src/middleware/auth.middleware.ts index d55a911..60d1647 100644 --- a/server/src/middleware/auth.middleware.ts +++ b/server/src/middleware/auth.middleware.ts @@ -5,8 +5,8 @@ import { TokenUtil } from '../util/token'; @Provide() export class AuthMiddleware implements IMiddleware { - // 中间件匹配范围:全局生效,但跳过公开接口与电工 APP 接口(电工 APP 走独立鉴权) - ignore = ['/api/auth/*', '/api/health', '/api/worker-app/*']; + // 中间件匹配范围:全局生效,但跳过公开接口、电工 APP 与小程序客户接口(各自走独立鉴权) + ignore = ['/api/auth/*', '/api/health', '/api/worker-app/*', '/api/customer-app/*']; @Inject() tokenUtil!: TokenUtil; diff --git a/server/src/middleware/customer_auth.middleware.ts b/server/src/middleware/customer_auth.middleware.ts new file mode 100644 index 0000000..0f072bb --- /dev/null +++ b/server/src/middleware/customer_auth.middleware.ts @@ -0,0 +1,52 @@ +import { Middleware, IMiddleware } from "@midwayjs/core"; +import { Context, NextFunction } from "@midwayjs/koa"; +import { Provide, Inject } from "@midwayjs/core"; +import { TokenUtil } from "../util/token"; + +// 小程序客户侧独立鉴权:仅作用于 /api/customer-app/*, +// 登录、微信登录与微信绑定接口无需鉴权。 +@Provide() +export class CustomerAuthMiddleware implements IMiddleware { + match = ["/api/customer-app/*"]; + + @Inject() + tokenUtil!: TokenUtil; + + resolve() { + return async (ctx: Context, next: NextFunction) => { + if (ctx.path.startsWith("/api/customer-app/auth/")) { + await next(); + return; + } + + const authHeader = ctx.header.authorization; + if (!authHeader || !authHeader.startsWith("Bearer ")) { + ctx.status = 401; + ctx.body = { code: 401, message: "未登录或登录已过期" }; + return; + } + + const token = authHeader.substring(7); + let decoded: any; + try { + decoded = this.tokenUtil.verifyCustomerToken(token); + } catch (error: any) { + ctx.status = 401; + ctx.body = { + code: 401, + message: error.name === "TokenExpiredError" ? "登录已过期,请重新登录" : "未登录或登录已过期", + expired: error.name === "TokenExpiredError" + }; + return; + } + + ctx.state.customer = { + accountId: Number(decoded.accountId), + customerId: Number(decoded.customerId), + phone: decoded.phone + }; + + await next(); + }; + } +} diff --git a/server/src/middleware/rbac.middleware.ts b/server/src/middleware/rbac.middleware.ts index 448cfb7..68d2489 100644 --- a/server/src/middleware/rbac.middleware.ts +++ b/server/src/middleware/rbac.middleware.ts @@ -51,7 +51,7 @@ export class RbacMiddleware implements IMiddleware { // 中间件匹配范围:全局生效,跳过公开接口、仪表盘与电工 APP 接口 // /api/workers/options 与 /api/workers/recommend 放行:业务员在「项目勘察派单」页 // 仍可调用电工下拉与推荐接口,不受 dispatch:worker 权限限制(本次需求有意设计) - ignore = ["/api/auth/*", "/api/health", "/api/dashboard/*", "/api/worker-app/*", "/api/workers/options", "/api/workers/recommend"]; + ignore = ["/api/auth/*", "/api/health", "/api/dashboard/*", "/api/worker-app/*", "/api/customer-app/*", "/api/workers/options", "/api/workers/recommend"]; @Inject() permissionService!: PermissionService; diff --git a/server/src/service/customer_app.service.ts b/server/src/service/customer_app.service.ts new file mode 100644 index 0000000..4956b8d --- /dev/null +++ b/server/src/service/customer_app.service.ts @@ -0,0 +1,444 @@ +import { Config, Inject, Provide } from "@midwayjs/core"; +import { InjectRepository } from "@midwayjs/sequelize"; +import { Repository } from "sequelize-typescript"; +import { Op } from "sequelize"; +import { request } from "undici"; +import CustomerAccount from "../entity/customer_account.entity"; +import Customer from "../entity/customer.entity"; +import CustomerBuilding from "../entity/customer_building.entity"; +import CustomerCircuit from "../entity/customer_circuit.entity"; +import CustomerEquipment from "../entity/customer_equipment.entity"; +import Contract from "../entity/contract.entity"; +import RepairRequest from "../entity/repair_request.entity"; +import RepairDispatch from "../entity/repair_dispatch.entity"; +import Worker from "../entity/worker.entity"; +import SysUser from "../entity/sysUser.entity"; +import { TokenUtil } from "../util/token"; +import { comparePassword } from "../util/crypto"; +import { UploadService } from "./upload.service"; + +// 小程序报修页签:待接单/处置中/待确认验收/已完成 +export type CustomerRepairTab = "all" | "pending" | "processing" | "acceptance" | "completed"; + +@Provide() +export class CustomerAppService { + @Config("wx") + wxConfig!: { appId: string; secret: string }; + + @Inject() + tokenUtil!: TokenUtil; + + @Inject() + uploadService!: UploadService; + + @InjectRepository(CustomerAccount) + accountModel!: Repository; + + @InjectRepository(CustomerBuilding) + buildingModel!: Repository; + + @InjectRepository(CustomerCircuit) + circuitModel!: Repository; + + @InjectRepository(CustomerEquipment) + equipmentModel!: Repository; + + @InjectRepository(Contract) + contractModel!: Repository; + + @InjectRepository(RepairRequest) + requestModel!: Repository; + + @InjectRepository(RepairDispatch) + dispatchModel!: Repository; + + @InjectRepository(Worker) + workerModel!: Repository; + + @InjectRepository(SysUser) + sysUserModel!: Repository; + + // ================= 登录 ================= + + async login(phone: string, password: string): Promise { + const account = await this.findActiveAccount({ phone }); + if (!account) return { code: 400, message: "账号不存在或已被禁用", data: null }; + const valid = await comparePassword(password, account.passwordHash); + if (!valid) return { code: 400, message: "密码错误", data: null }; + await account.update({ lastLoginAt: new Date() }); + return this.buildLoginResult(account, "登录成功"); + } + + // 微信静默登录:已绑定 openid 直接登录,未绑定返回 bindToken 供绑定 + async wxLogin(code: string): Promise { + if (!this.wxConfig?.appId || !this.wxConfig?.secret) { + return { code: 503, message: "微信登录未配置,请使用账号密码登录", data: null }; + } + + let openid = ""; + try { + const url = + "https://api.weixin.qq.com/sns/jscode2session" + + `?appid=${encodeURIComponent(this.wxConfig.appId)}` + + `&secret=${encodeURIComponent(this.wxConfig.secret)}` + + `&js_code=${encodeURIComponent(code)}` + + "&grant_type=authorization_code"; + const res = await request(url); + const body: any = await res.body.json(); + if (body.errcode) { + return { code: 400, message: `微信登录失败:${body.errmsg || body.errcode}`, data: null }; + } + openid = body.openid || ""; + } catch (err: any) { + return { code: 502, message: `微信登录请求失败:${err?.message || "网络异常"}`, data: null }; + } + + if (!openid) return { code: 400, message: "微信登录失败:未返回 openid", data: null }; + + const account = await this.findActiveAccount({ wxOpenid: openid }); + if (!account) { + return { + code: 200, + message: "微信尚未绑定客户账号", + data: { needBind: true, bindToken: this.tokenUtil.generateWxBindToken(openid) } + }; + } + + await account.update({ lastLoginAt: new Date() }); + return this.buildLoginResult(account, "登录成功"); + } + + // 微信首次绑定:校验账号密码后写入 openid 并直接登录 + async wxBind(bindToken: string, phone: string, password: string): Promise { + let decoded: any; + try { + decoded = this.tokenUtil.verifyWxBindToken(bindToken); + } catch { + return { code: 400, message: "绑定凭据已失效,请重新进入小程序", data: null }; + } + + const openid = String(decoded.openid || ""); + const account = await this.findActiveAccount({ phone }, false); + if (!account) return { code: 400, message: "账号不存在或已被禁用", data: null }; + const valid = await comparePassword(password, account.passwordHash); + if (!valid) return { code: 400, message: "密码错误", data: null }; + + if (account.wxOpenid && account.wxOpenid !== openid) { + return { code: 400, message: "该账号已绑定其他微信,请联系管理员解绑", data: null }; + } + const occupied = await this.accountModel.findOne({ where: { wxOpenid: openid } }); + if (occupied && Number(occupied.id) !== Number(account.id)) { + return { code: 400, message: "该微信已绑定其他客户账号", data: null }; + } + + await account.update({ wxOpenid: openid, lastLoginAt: new Date() }); + const fresh = await this.findActiveAccount({ phone }); + return this.buildLoginResult(fresh || account, "微信绑定并登录成功"); + } + + // ================= 我的 ================= + + async profile(accountId: number): Promise { + const account = await this.accountModel.findByPk(accountId, { include: [Customer] }); + if (!account || Number(account.status) !== 1) { + return { code: 401, message: "账号不存在或已被禁用", data: null }; + } + const customer = (account as any).customer as Customer; + if (!customer) return { code: 404, message: "客户信息不存在", data: null }; + + const customerId = Number(customer.id); + const [buildingCount, circuitCount, gatewayCount, contract] = await Promise.all([ + this.buildingModel.count({ where: { customerId } }), + this.circuitModel.count({ where: { customerId } }), + this.equipmentModel.count({ + where: { + customerId, + [Op.or]: [{ equipType: { [Op.like]: "%网关%" } }, { equipName: { [Op.like]: "%网关%" } }] + } + }), + this.contractModel.findOne({ + where: { customerId, status: 1 }, + order: [["startDate", "ASC"]] + }) + ]); + + let startDate = (customer as any).created_at ? new Date((customer as any).created_at) : new Date(); + if (contract?.startDate) { + const parsed = new Date(`${contract.startDate}T00:00:00+08:00`); + if (!Number.isNaN(parsed.getTime())) startDate = parsed; + } + const safetyDays = Math.max(1, Math.floor((Date.now() - startDate.getTime()) / 86400000)); + + let salesman: any = null; + if (customer.salesmanId) { + const user = await this.sysUserModel.findByPk(Number(customer.salesmanId), { + attributes: ["id", "realName", "phone"] + }); + if (user) salesman = { id: user.id, name: user.realName || "客户经理", phone: user.phone || "" }; + } + + return { + code: 200, + message: "获取成功", + data: { + account: this.toAccountVO(account), + customer: this.toCustomerVO(customer), + stats: { + buildingCount, + circuitCount, + gatewayCount, + // 暂无网关在线遥测数据,按已接入网关数展示 + gatewayOnline: gatewayCount, + safetyDays + }, + salesman, + hotline: "400-880-9119" + } + }; + } + + // ================= 报修 ================= + + async listRepairs(accountId: number, tab: string = "all", page: number = 1, pageSize: number = 20): Promise { + const account = await this.accountModel.findByPk(accountId); + if (!account) return { code: 401, message: "账号不存在", data: null }; + const customerId = Number(account.customerId); + + const [requests, dispatches] = await Promise.all([ + this.requestModel.findAll({ where: { customerId }, order: [["requestTime", "DESC"]] }), + this.dispatchModel.findAll({ + where: { customerId }, + include: [{ model: Worker, attributes: ["id", "workerName", "phone", "workerTitle", "rating", "certInfo"] }], + order: [["id", "DESC"]] + }) + ]); + + // 同一报修单取最新一次派单 + const latestDispatch = new Map(); + for (const row of dispatches) { + const repairId = Number(row.repairId); + if (!latestDispatch.has(repairId)) latestDispatch.set(repairId, row); + } + + const all = requests.map((item) => this.toRepairVO(item, latestDispatch.get(Number(item.id)))); + + // 报修照片与派单处理照片均以对象 key 入库,读取时统一换为预签名 URL + const keys = new Set(); + for (const item of all) { + for (const key of item.photoKeys) keys.add(key); + for (const key of item.processPhotoKeys) keys.add(key); + } + const urlMap = await this.uploadService.getPresignedUrlMap(Array.from(keys)); + for (const item of all) { + item.photos = item.photoKeys.map((key: string) => urlMap[key] || key); + item.dispatch && (item.dispatch.processPhotos = item.processPhotoKeys.map((key: string) => urlMap[key] || key)); + delete item.photoKeys; + delete item.processPhotoKeys; + } + + const counts = { all: all.length, pending: 0, processing: 0, acceptance: 0, completed: 0 }; + for (const item of all) counts[item.category as CustomerRepairTab] += 1; + + const normalizedTab: CustomerRepairTab = ["all", "pending", "processing", "acceptance", "completed"].includes(tab) + ? (tab as CustomerRepairTab) + : "all"; + const filtered = normalizedTab === "all" ? all : all.filter((item) => item.category === normalizedTab); + const safePage = Math.max(1, Number(page) || 1); + const safeSize = Math.min(100, Math.max(1, Number(pageSize) || 20)); + const list = filtered.slice((safePage - 1) * safeSize, safePage * safeSize); + + return { + code: 200, + message: "获取报修工单成功", + data: { list, counts, tab: normalizedTab, total: filtered.length, page: safePage, pageSize: safeSize } + }; + } + + async createRepair(accountId: number, body: any): Promise { + const account = await this.accountModel.findByPk(accountId, { include: [Customer] }); + if (!account) return { code: 401, message: "账号不存在", data: null }; + const customer = (account as any).customer as Customer; + const repairNo = await this.generateRepairNo(); + const photos = Array.isArray(body.photos) ? body.photos.filter((p: any) => typeof p === "string").slice(0, 9) : []; + + const record = await this.requestModel.create({ + repairNo, + customerId: Number(account.customerId), + requester: customer?.contactPerson || account.phone, + requesterPhone: account.phone, + requestTime: new Date(), + buildingId: body.buildingId ? Number(body.buildingId) : undefined, + floorNo: body.floorNo || undefined, + location: String(body.location || "").trim(), + repairContent: String(body.repairContent || "").trim(), + photos, + status: 0 + } as any); + + return { + code: 200, + message: "报修已提交,等待调度派单", + data: { + id: record.id, + repairNo: record.repairNo, + status: record.status, + requestTime: record.requestTime + } + }; + } + + // 客户验收并评价:派单已完成且未评分 → 写入评分、报修单置为已完成 + async acceptRepair(accountId: number, repairId: number, rating: number): Promise { + const account = await this.accountModel.findByPk(accountId); + if (!account) return { code: 401, message: "账号不存在", data: null }; + + const req = await this.requestModel.findOne({ + where: { id: repairId, customerId: Number(account.customerId) } + }); + if (!req) return { code: 404, message: "报修工单不存在", data: null }; + if (Number(req.status) === 2) return { code: 400, message: "该工单已验收", data: null }; + + const dispatch = await this.dispatchModel.findOne({ + where: { repairId, customerId: Number(account.customerId) }, + order: [["id", "DESC"]] + }); + if (!dispatch) return { code: 400, message: "该工单尚未派单,暂不能验收", data: null }; + if (Number(dispatch.status) !== 1) return { code: 400, message: "电工尚未完成处理,暂不能验收", data: null }; + if (dispatch.rating !== null && dispatch.rating !== undefined) { + return { code: 400, message: "该工单已评价", data: null }; + } + + await dispatch.update({ rating } as any); + await req.update({ status: 2 } as any); + + return { + code: 200, + message: "验收评价成功", + data: { repairId, dispatchId: dispatch.id, rating, status: 2 } + }; + } + + // ================= 内部方法 ================= + + private async findActiveAccount(where: any, withCustomer: boolean = true): Promise { + return this.accountModel.findOne({ + where, + include: withCustomer ? [Customer] : [] + }); + } + + private buildLoginResult(account: CustomerAccount, message: string): any { + const customer = (account as any).customer as Customer; + const accessToken = this.tokenUtil.generateCustomerToken({ + accountId: Number(account.id), + customerId: Number(account.customerId), + phone: account.phone + }); + return { + code: 200, + message, + data: { + accessToken, + account: this.toAccountVO(account), + customer: customer ? this.toCustomerVO(customer) : null + } + }; + } + + private toAccountVO(account: CustomerAccount): any { + return { + id: Number(account.id), + phone: account.phone, + wxBound: !!account.wxOpenid, + lastLoginAt: account.lastLoginAt || null + }; + } + + private toCustomerVO(customer: Customer): any { + return { + id: Number(customer.id), + customerNo: customer.customerNo, + customerName: customer.customerName, + contactPerson: customer.contactPerson || "", + contactPhone: customer.contactPhone || "", + address: customer.address || "", + customerLevel: customer.customerLevel || "", + status: Number(customer.status) + }; + } + + private toRepairVO(req: RepairRequest, dispatch?: RepairDispatch): any { + const category = this.resolveCategory(Number(req.status), dispatch); + const worker = dispatch ? ((dispatch as any).worker as Worker) : null; + const photoKeys = Array.isArray(req.photos) ? req.photos.filter((p: any) => typeof p === "string") : []; + const processPhotoKeys = + dispatch && Array.isArray(dispatch.processPhotos) + ? dispatch.processPhotos.filter((p: any) => typeof p === "string") + : []; + return { + id: Number(req.id), + repairNo: req.repairNo, + requestTime: req.requestTime, + location: req.location, + repairContent: req.repairContent, + floorNo: req.floorNo || "", + photos: photoKeys, + photoKeys, + processPhotoKeys, + status: Number(req.status), + category, + worker: worker + ? { + id: Number(worker.id), + name: worker.workerName, + phone: worker.phone, + title: worker.workerTitle || "共享电工", + rating: worker.rating === null || worker.rating === undefined ? null : Number(worker.rating), + certInfo: worker.certInfo || "" + } + : null, + dispatch: dispatch + ? { + id: Number(dispatch.id), + dispatchNo: dispatch.dispatchNo, + status: Number(dispatch.status), + processPhotos: processPhotoKeys, + rating: dispatch.rating === null || dispatch.rating === undefined ? null : Number(dispatch.rating), + completedAt: dispatch.completedAt || null, + requestTime: dispatch.requestTime || null + } + : null + }; + } + + private resolveCategory(status: number, dispatch?: RepairDispatch): CustomerRepairTab { + if (!dispatch) return status === 2 ? "completed" : "pending"; + const dispatchStatus = Number(dispatch.status); + const rated = dispatch.rating !== null && dispatch.rating !== undefined; + if (dispatchStatus === 0) return "processing"; + if (dispatchStatus === 1 && !rated) return "acceptance"; + if (dispatchStatus === 1 && rated) return "completed"; + return status === 2 ? "completed" : "pending"; + } + + // 报修单号:BX-YYYYMMDD-NNN(当日序号) + private async generateRepairNo(retry: number = 0): Promise { + const now = new Date(Date.now() + 8 * 3600 * 1000); + const ymd = `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}${String( + now.getUTCDate() + ).padStart(2, "0")}`; + const prefix = `BX-${ymd}-`; + const last = await this.requestModel.findOne({ + where: { repairNo: { [Op.like]: `${prefix}%` } }, + order: [["repairNo", "DESC"]] + }); + const seq = last ? (parseInt(String(last.repairNo).slice(-3), 10) || 0) + 1 : 1; + const repairNo = `${prefix}${String(seq).padStart(3, "0")}`; + const exists = await this.requestModel.findOne({ where: { repairNo } }); + if (exists) { + if (retry < 1) return this.generateRepairNo(retry + 1); + throw new Error("报修单号生成冲突,请重试"); + } + return repairNo; + } +} diff --git a/server/src/util/token.ts b/server/src/util/token.ts index c8c555b..4f4d194 100644 --- a/server/src/util/token.ts +++ b/server/src/util/token.ts @@ -72,6 +72,39 @@ export class TokenUtil { return decoded; } + // 生成小程序客户令牌:携带 userType=customer 以区分管理端/电工端 token + generateCustomerToken(payload: { accountId: number; customerId: number; phone: string }): string { + return jwt.sign({ ...payload, userType: "customer" }, this.jwtConfig.secret, { + expiresIn: this.jwtConfig.expiresIn as jwt.SignOptions["expiresIn"] + }); + } + + // 校验小程序客户令牌:除有效期/签名外,还需 userType 为 customer + verifyCustomerToken(token: string): any { + const decoded = this.verifyToken(token); + if (!decoded || decoded.userType !== "customer") { + const invalidError = new JsonWebTokenError("无效的认证令牌"); + invalidError.name = "JsonWebTokenError"; + throw invalidError; + } + return decoded; + } + + // 微信静默登录未绑定时的临时凭据:仅用于绑定,10 分钟有效 + generateWxBindToken(openid: string): string { + return jwt.sign({ openid, userType: "wx-bind" }, this.jwtConfig.secret, { expiresIn: "10m" }); + } + + verifyWxBindToken(token: string): any { + const decoded = this.verifyToken(token); + if (!decoded || decoded.userType !== "wx-bind") { + const invalidError = new JsonWebTokenError("无效的绑定凭据"); + invalidError.name = "JsonWebTokenError"; + throw invalidError; + } + return decoded; + } + decodeToken(token: string): any { return jwt.decode(token); } From d44e57c32da8828dc69df6b71716e06985333eff Mon Sep 17 00:00:00 2001 From: andyesfly <35127884@qq.com> Date: Sat, 24 Oct 2026 21:17:52 +0800 Subject: [PATCH 2/3] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E4=BA=8C=E7=BB=B4=E7=A0=81=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- preview-qr.png | Bin 47503 -> 47359 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/preview-qr.png b/preview-qr.png index d88f7633a75bdf22201811ccc2532a5b937891f7..f04b3bbe0b642764c8775ee3b848bc135371bdc3 100644 GIT binary patch delta 19726 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 M^DkKH^0meP2km@nrT_o{ From 663585b96ff533a884e1fc038f4feb8d8827567a Mon Sep 17 00:00:00 2001 From: andyesfly <35127884@qq.com> Date: Sat, 24 Oct 2026 21:35:42 +0800 Subject: [PATCH 3/3] =?UTF-8?q?build:=20=E6=9B=B4=E6=96=B0=20.gitignore=20?= =?UTF-8?q?=E5=B9=B6=E5=88=A0=E9=99=A4=E5=B7=B2=E8=B7=9F=E8=B8=AA=E7=9A=84?= =?UTF-8?q?=E9=A2=84=E8=A7=88=E4=BA=8C=E7=BB=B4=E7=A0=81=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加 preview-qr.png 到 .gitignore,同时删除已被跟踪的该图片文件 --- .gitignore | 1 + preview-qr.png | Bin 47359 -> 0 bytes 2 files changed, 1 insertion(+) delete mode 100644 preview-qr.png diff --git a/.gitignore b/.gitignore index c4c4ffc..79f9497 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.zip +preview-qr.png diff --git a/preview-qr.png b/preview-qr.png deleted file mode 100644 index f04b3bbe0b642764c8775ee3b848bc135371bdc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47359 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