From 8ea6a7dd1fcdbd4e22c564ed63a5a8023510161a Mon Sep 17 00:00:00 2001
From: zjk <852185829@qq.com>
Date: 星期三, 14 十月 2026 08:56:45 +0800
Subject: [PATCH] feat: 新增实名认证、企业认证、我的邀请与头像上传功能

---
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountCertification.java                           |  182 +
 doc/design/用户端_我的资料_头像上传_20261013.md                                                                                           |  229 ++
 hugray-user-miniapp/pages/profile/certification.vue                                                                            |  390 +++
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java                   |  142 +
 hugray-user-miniapp/utils/request.js                                                                                           |   94 
 hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/service/WeChatMiniappService.java                                        |  140 +
 hugray-server/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java                  |    5 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountCertificationApply.java                      |  184 +
 hugray-server/ruoyi-modules/ruoyi-user/src/test/java/com/ruoyi/user/service/impl/AccountCertificationApplyServiceImplTest.java |  292 ++
 hugray-server/ruoyi-modules/ruoyi-user/src/test/java/com/ruoyi/user/service/impl/AccountUserServiceImplTest.java               |   78 
 hugray-server/sql/hugray_account_company_certification_20261013.sql                                                            |  113 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountCertificationApplyService.java             |   47 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/controller/AccountProfileController.java                   |  144 +
 hugray-admin/src/utils/member.js                                                                                               |    6 
 hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java                                          |   22 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountUserMapper.java                              |   26 
 hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/form/InviteQrcodeQuery.java                                              |   27 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/CompanyCertificationApplyDto.java               |  334 ++
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/CompanyCertificationStatusVo.java                |  314 ++
 hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountCertificationApplyMapper.xml                      |  126 +
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountCertificationApplyMapper.java                |   33 
 hugray-user-miniapp/pages.json                                                                                                 |   21 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountUserInfoVo.java                           |   29 
 hugray-user-miniapp/pages/profile/invite.vue                                                                                   |  781 ++++++
 hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountUserMapper.xml                                    |   47 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountCertificationMapper.xml                           |   55 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountCertificationApplyServiceImpl.java     |  246 ++
 hugray-user-miniapp/pages/login/register.vue                                                                                   |   19 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountUserService.java                           |   40 
 hugray-user-miniapp/pages/profile/edit.vue                                                                                     |  104 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountCertificationStatusVo.java                |   66 
 hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/domain/vo/InviteQrcodeVo.java                                            |   22 
 hugray-user-miniapp/pages/profile/index.vue                                                                                    |    5 
 hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/AccountAuthController.java                                    |   23 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountInviteeVo.java                            |  104 
 doc/design/用户端_个人中心_20261013.md                                                                                                |    2 
 hugray-user-miniapp/api/index.js                                                                                               |   92 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/InviteCodeVo.java                                |   24 
 doc/design/用户端_我的邀请_20261013.md                                                                                                |  239 ++
 hugray-admin/src/views/member/certification/detail.vue                                                                         |   36 
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountReferrerVo.java                           |   89 
 doc/design/用户端_实名认证_20261013.md                                                                                                |  305 ++
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountUser.java                                    |   21 
 doc/design/用户端_企业个体认证_20261013.md                                                                                              |  330 ++
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/AccountCertificationApplyDto.java               |   57 
 hugray-user-miniapp/pages/profile/company-certification.vue                                                                    | 1038 +++++++++
 hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/AccountUserAvatarDto.java                       |   32 
 doc/nacos/dev/ruoyi-file-dev.yml                                                                                               |   34 
 48 files changed, 6,766 insertions(+), 23 deletions(-)

diff --git "a/doc/design/\347\224\250\346\210\267\347\253\257_\344\270\252\344\272\272\344\270\255\345\277\203_20261013.md" "b/doc/design/\347\224\250\346\210\267\347\253\257_\344\270\252\344\272\272\344\270\255\345\277\203_20261013.md"
index cf76667..8ad8db4 100644
--- "a/doc/design/\347\224\250\346\210\267\347\253\257_\344\270\252\344\272\272\344\270\255\345\277\203_20261013.md"
+++ "b/doc/design/\347\224\250\346\210\267\347\253\257_\344\270\252\344\272\272\344\270\255\345\277\203_20261013.md"
@@ -18,7 +18,7 @@
 | 姓名 `nickname` | 头部文本 | mock `辉煌G891` | 是(`account_user.nickname`;注册未采集姓名字段,落库为空,前端回退展示脱敏手机号) |
 | 手机号 `phone`(脱敏) | 头部名称回退 | 无 | 是(`account_user.phone`,脱敏为 `133****5555`,不下发明文) |
 | 会员等级 `memberLevel` | 等级胶囊 `C`/`L1~L4` | mock `L4` | 是(复用既有 `account_user.system_level`) |
-| 头像 `avatar` | 头部头像 | 相机图标占位 | 是(已存在字段,有值前端渲染图片,无值回退图标) |
+| 头像 `avatar` | 头部头像 | 相机图标占位(「我的资料」页已支持点击上传更换) | 是(已存在字段,有值前端渲染图片,无值回退图标) |
 | 今日 / 本月 / 累计收益 | 收益玻璃卡三栏 | mock `10.00 / 400.00 / 128000.00` | 是(新增收益流水表,实时聚合) |
 | 仓库订单角标数量 | 菜单角标 | mock `1` | 否(见 2.5,无数据来源,本期不入接口) |
 
diff --git "a/doc/design/\347\224\250\346\210\267\347\253\257_\344\274\201\344\270\232\344\270\252\344\275\223\350\256\244\350\257\201_20261013.md" "b/doc/design/\347\224\250\346\210\267\347\253\257_\344\274\201\344\270\232\344\270\252\344\275\223\350\256\244\350\257\201_20261013.md"
new file mode 100644
index 0000000..1efc3b2
--- /dev/null
+++ "b/doc/design/\347\224\250\346\210\267\347\253\257_\344\274\201\344\270\232\344\270\252\344\275\223\350\256\244\350\257\201_20261013.md"
@@ -0,0 +1,330 @@
+# 用户端小程序 企业/个体商户认证 开发设计文档
+
+- 模块:ruoyi-user(会员业务)+ ruoyi-gateway(路由/鉴权头注入)+ hugray-admin(后台主体认证审核)+ hugray-user-miniapp(前端)
+- 日期:2026-10-13
+- 需求来源:"继续实现企业、个体商户的认证,字段只做最基础的必填校验,根据原型设计ui稿并实现"
+- 原型:4 张 UI 稿(企业信息 / 法人信息 / 收款信息 / 提交审核 四步向导)
+- 关联文档:`doc/design/用户端_实名认证_20261013.md`(个人实名认证,本期复用其提交/审核链路范式)
+- 数据权威源:`192.168.110.111:3306/hugray-account`(实际表结构优先于本文档)
+
+---
+
+## 一、功能概述
+
+「我的资料」页(`pages/profile/edit.vue`)「企业/个体户认证」行当前点击仅弹占位 toast,本次改为**跳转到企业/个体认证四步向导页**:
+
+1. 新增 C 端页面 `pages/profile/company-certification.vue`,顶部为**四段式步骤条**(企业信息 / 法人信息 / 收款信息 / 提交审核),已完成打勾、当前高亮,底部分步表单 + 「下一步 / 提交资料」按钮(原型见 4 张 UI 稿);
+2. 页面**只做基础必填校验**(名称/代码/姓名/电话/邮箱/卡号/图片等非空),**不接入任何格式/三方核验**;
+3. 提交后落 `account_certification_apply`(`subject_type` 取 2 个体工商户 / 3 企业、`audit_status='0'` 待审核),并联动 `account_user.company_auth_status='1'`(企业认证中),使其出现在后台**主体认证审核列表**;
+4. 「我的资料」页「企业/个体户认证」徽标改为按后端新下发的 `companyAuthStatus` 显示真实状态(未认证 / 认证中 / 已认证 / 认证失败),与「实名认证」徽标**相互独立**;
+5. 复核进入时**回填**已提交资料与状态:审核中 → 直接进入第 4 步「资料审核中」占位页;已通过 → 展示已通过提示;已驳回 → 回到第 1 步空表单并提示驳回原因,可修改重提。
+
+后台「审」链路(列表 / 详情 / 通过 / 驳回)**已存在**,本次做两处增强:审核结果按主体类型分流回写 `auth_status` / `company_auth_status`;审核详情页补展示企业认证新增字段。
+
+---
+
+## 二、现状与关键约束
+
+### 2.1 数据模型已预留主体类型
+
+`account_certification_apply.subject_type`(`char(1)`)取值语义已预置:**1个人 2个体工商户 3企业**,个人实名走 `'1'`,企业/个体走 `'2'`/`'3'`,**无需改类型语义,只需补列**。
+
+### 2.2 现有表缺列,需补 DDL
+
+`account_certification_apply` 与 `account_certification` 现有企业相关列仅到「营业执照」为止(`company_name`、`credit_code`、`legal_person`、`registered_region`、`company_address`、`register_date`、`license_valid_period`、`license_image`)。原型所需的**营业执照有效期类型、法人身份证正反面、法人身份证有效期、法人身份证详细地址、负责人姓名/电话/邮箱、收款类型、银行卡照片、银行卡号、开户城市**共 13 列缺失,需补。
+
+### 2.3 个人 / 企业认证状态必须隔离
+
+`account_user` 当前只有一个 `auth_status`(个人实名认证状态),而个人中心需**两行独立徽标**(「实名认证」/「企业个体户认证」)。若企业认证复用 `auth_status`,提交企业认证会同时把「实名认证」徽标改成「认证中」,语义错误。故**新增 `account_user.company_auth_status`**,个人 / 企业互不覆盖(已确认)。
+
+### 2.4 C 端状态查询需按主体类型隔离
+
+现有 `selectLatestApplyByUserId` / `countPendingApplyByUserId` **不区分主体类型**,企业认证上线后会把企业申请当作个人申请回填/防重。本次为两者都加主体类型过滤:个人限 `subject_type='1'`,企业限 `subject_type in ('2','3')`。
+
+### 2.5 路径规划
+
+- 网关注入 `user_id`;`SecurityUtils.getUserId()` 取会员身份,**C 端接口不接受前端传 userId**(防水平越权)。
+- 在既有 C 端个人中心控制器 `AccountProfileController`(`@RequestMapping("/user")`,外部 `/miniapp/account/user/**`)上**新增**两条企业认证接口,与个人认证接口并列。
+- 与后台 `AccountCertificationApplyController`(外部 `/account/user/certification/**`)**前缀不同域**,无冲突;网关 `/miniapp/account/**` 路由已存在,**无需改网关**。
+
+### 2.6 主键与落库策略
+
+`account_certification_apply.id` 为 `bigint AUTO_INCREMENT`,沿用既有 `useGeneratedKeys="true" keyProperty="id"`;列按 `<if>` 动态拼,未填字段不落列。
+
+---
+
+## 三、接口设计
+
+统一响应体:C 端 `R<T>`(成功 `code=200`)。
+
+### 3.1 提交企业/个体认证(本次新增)
+
+- **外部路径**:`POST /miniapp/account/user/company-certification`(网关 `StripPrefix=2` → 服务内 `POST /user/company-certification`)
+- **鉴权**:需登录,未登录由 `AuthFilter` 直接 401;会员身份取自网关注入的 `user_id` 头,**不接受前端传 userId**。
+- **入参**:JSON(`CompanyCertificationApplyDto`),字段与校验见下表:
+
+| 步骤 | 字段 | 类型 | 必填 | 校验 | 说明(对应 DB 列) |
+| --- | --- | --- | --- | --- | --- |
+| 企业信息 | subjectType | String | 是 | `@NotBlank` + `@Pattern(^[23]$)` | 主体类型:2个体工商户 / 3企业(`subject_type`) |
+| 企业信息 | companyName | String | 是 | `@NotBlank`+`@Size(max=128)` | 企业/个体名称(`company_name`) |
+| 企业信息 | licenseImage | String | 是 | `@NotBlank`+`@Size(max=500)` | 营业执照图片地址(`license_image`) |
+| 企业信息 | creditCode | String | 是 | `@NotBlank`+`@Size(max=32)` | 统一社会信用代码(`credit_code`) |
+| 企业信息 | licenseValidType | String | 是 | `@NotBlank`+`@Pattern(^[12]$)` | 营业执照有效期类型:1长期 / 2短期(`license_valid_type`) |
+| 企业信息 | licenseValidPeriod | String | 短期必填 | `@Size(max=32)`;类型=2 时服务层必填 | 营业执照有效期起止(`license_valid_period`,存 `2024-01-01~2034-01-01`) |
+| 企业信息 | registeredRegion | String | 是 | `@NotBlank`+`@Size(max=128)` | 营业执照注册地区(`registered_region`) |
+| 企业信息 | companyAddress | String | 是 | `@NotBlank`+`@Size(max=255)` | 营业执照详细地址(`company_address`) |
+| 法人信息 | legalPerson | String | 是 | `@NotBlank`+`@Size(max=64)` | 法人姓名(`legal_person`) |
+| 法人信息 | legalIdCardFront | String | 是 | `@NotBlank`+`@Size(max=500)` | 法人身份证人像面图片(`legal_id_card_front`) |
+| 法人信息 | legalIdCardBack | String | 是 | `@NotBlank`+`@Size(max=500)` | 法人身份证国徽面图片(`legal_id_card_back`) |
+| 法人信息 | legalIdValidType | String | 是 | `@NotBlank`+`@Pattern(^[12]$)` | 法人身份证有效期类型(`legal_id_valid_type`) |
+| 法人信息 | legalIdValidPeriod | String | 短期必填 | `@Size(max=32)`;类型=2 时服务层必填 | 法人身份证有效期起止(`legal_id_valid_period`) |
+| 法人信息 | legalIdAddress | String | 是 | `@NotBlank`+`@Size(max=255)` | 法人身份证详细地址(`legal_id_address`) |
+| 法人信息 | contactName | String | 是 | `@NotBlank`+`@Size(max=64)` | 负责人姓名(`contact_name`) |
+| 法人信息 | contactPhone | String | 是 | `@NotBlank`+`@Size(max=20)` | 负责人联系电话(`contact_phone`) |
+| 法人信息 | contactEmail | String | 是 | `@NotBlank`+`@Size(max=128)` | 负责人邮箱(`contact_email`) |
+| 收款信息 | payeeType | String | 是 | `@NotBlank`+`@Size(max=16)` | 收款类型(`payee_type`) |
+| 收款信息 | bankCardImage | String | 是 | `@NotBlank`+`@Size(max=500)` | 银行卡号码面照片(`bank_card_image`) |
+| 收款信息 | bankCardNo | String | 是 | `@NotBlank`+`@Size(max=32)` | 银行卡号(`bank_card_no`) |
+| 收款信息 | bankCity | String | 是 | `@NotBlank`+`@Size(max=128)` | 开户城市(`bank_city`) |
+
+- **出参**:`R<Void>`(成功即 `code=200`)
+- **错误提示**:字段级 `@NotBlank`/`@Pattern` 提示 + 业务提示「短期有效期必须填写起止日期」/「已有企业认证申请正在审核中,请勿重复提交」/「主体类型不合法」。
+
+### 3.2 查询企业/个体认证状态(本次新增)
+
+- **外部路径**:`GET /miniapp/account/user/company-certification`(→ 服务内 `GET /user/company-certification`)
+- 取该会员**最近一次**企业/个体申请(`subject_type in ('2','3')`,`del_flag='0'`,`id desc limit 1`),出参 `CompanyCertificationStatusVo`:
+
+| 字段 | 说明 |
+| --- | --- |
+| subjectType / companyName / licenseImage / creditCode / licenseValidType / licenseValidPeriod / registeredRegion / companyAddress | 企业信息回填 |
+| legalPerson / legalIdCardFront / legalIdCardBack / legalIdValidType / legalIdValidPeriod / legalIdAddress | 法人信息回填 |
+| contactName / contactPhone / contactEmail | 负责人信息回填 |
+| payeeType / bankCardImage / bankCardNo / bankCity | 收款信息回填 |
+| auditStatus | 0待审核 1已通过 2已驳回;**为空表示从未提交** |
+| auditRemark | 驳回原因(`audit_status='2'` 时有值) |
+
+无申请时返回**空字段对象**(各字段为 null),前端按「未认证」处理,不返回 404。
+
+### 3.3 查询会员信息(本次扩展)
+
+- **外部路径**:`GET /miniapp/account/user/info`(既有)
+- **改动**:出参 `AccountUserInfoVo` 新增 `companyAuthStatus` 字段(`account_user.company_auth_status` 原值 0/1/2/3),供「我的资料」页「企业/个体户认证」徽标回显。
+
+### 3.4 后台审核链路(既有,增强)
+
+`GET /account/user/certification/list`、`GET /account/user/certification/detail/{id}`、`PUT /account/user/certification/audit`(权限 `member:certification:audit`)**沿用**。企业申请同链路进入列表(`subject_type='2'/'3'`、待审核在前)。增强点:
+
+- **审核分流**:通过 / 驳回时按 `apply.subjectType` 写回——个人写 `auth_status`,企业/个体写 `company_auth_status`(通过时同步 `account_user.subject_type`);
+- **详情展示**:`detail.vue` 补展示企业/个体新增字段(见 7.3)。
+
+---
+
+## 四、数据库设计
+
+库:`hugray-account`。新增 DDL 脚本:`hugray-server/sql/hugray_account_company_certification_20261013.sql`(沿用既有幂等过程 `hugray_add_column` 风格;与代码同一次提交)。
+
+### 4.1 `account_certification_apply` / `account_certification` 各新增 13 列
+
+| 列 | 类型 | 注释 |
+| --- | --- | --- |
+| license_valid_type | varchar(16) | 营业执照有效期类型(1长期 2短期) |
+| legal_id_card_front | varchar(500) | 法人身份证人像面图片 |
+| legal_id_card_back | varchar(500) | 法人身份证国徽面图片 |
+| legal_id_valid_type | varchar(16) | 法人身份证有效期类型(1长期 2短期) |
+| legal_id_valid_period | varchar(32) | 法人身份证有效期(长期/起止文本) |
+| legal_id_address | varchar(255) | 法人身份证详细地址 |
+| contact_name | varchar(64) | 负责人姓名 |
+| contact_phone | varchar(20) | 负责人联系电话 |
+| contact_email | varchar(128) | 负责人邮箱 |
+| payee_type | varchar(16) | 收款类型 |
+| bank_card_image | varchar(500) | 银行卡号码面照片 |
+| bank_card_no | varchar(32) | 银行卡号 |
+| bank_city | varchar(128) | 开户城市 |
+
+### 4.2 `account_user` 新增 1 列
+
+| 列 | 类型 | 注释 |
+| --- | --- | --- |
+| company_auth_status | char(1) DEFAULT '0' | 企业/个体认证状态(0未认证 1审核中 2已认证 3认证失败) |
+
+> `license_valid_period` 沿用既有 `varchar(32)`:`2024-01-01~2034-01-01` 共 21 字符,够用,不改类型。
+
+---
+
+## 五、流程设计
+
+### 5.1 前端四步向导(`pages/profile/company-certification.vue`)
+
+```
+STEP 1: onShow 校验登录态(ensureLogin),未登录跳登录页
+STEP 2: 调 GET /company-certification 拉取状态与资料
+STEP 3: 按 auditStatus 决定首屏:
+        '' 未认证 → 停在第 1 步空表单
+        '2' 已驳回 → 停在第 1 步并回填资料,展示驳回原因
+        '0' 审核中 → 直接进入第 4 步「资料审核中」占位页
+        '1' 已通过 → 第 4 步展示「认证已通过」
+STEP 4: 第 1~3 步表单填写,点「下一步」逐级校验当前步必填项,通过则 step+1,否则 toast 首个缺失项
+STEP 5: 第 3 步(收款信息)按钮为「提交资料」,点后整表单再校验一次,通过则 POST 提交
+STEP 6: 成功后进入第 4 步「您的资料正在审核中,请耐心等待」;失败提示后端信息
+STEP 7: 步骤条已完成的步骤打勾,可点已完成步骤回退修改(未提交前)
+```
+
+交互细节(对齐原型):
+- **有效期类型 = 长期** 时,其**起止日期行隐藏**且不参与校验;= 短期 时显示且必填(已确认)。
+- 「营业执照注册地区」「开户城市」用 `<picker mode="region">`(沿用 `pages/address/add.vue` 范式);「营业执照详细地址」「法人身份证详细地址」为文本输入。
+- 图片项(营业执照 / 法人身份证人像面 / 法人身份证国徽面 / 银行卡号码面)统一走 `uni.chooseImage` + `uploadAvatarFile`(`/file/upload`)拿到 url 后存本地表单,**均为必填**(已确认)。
+- 有效期起止日期用两个 `<picker mode="date">`(开始 / 截止)并列在同一行内展示,回填时拼为 `起~止` 文本;存储为 `license_valid_period` / `legal_id_valid_period` 文本。
+
+### 5.2 后端提交(`submitCompanyCertification`)
+
+```
+STEP 1: 校验 userId 非空(未登录由网关拦截,此处兜底),为空抛业务异常
+STEP 2: 校验 subjectType ∈ {2 个体工商户, 3 企业};非法抛「主体类型不合法」
+STEP 3: 有效期类型=短期(2) 时,对应起止日期必填(服务层兜底,防绕过 Controller)
+STEP 4: 防重闸门:该会员若存在 subject_type in ('2','3') 且 audit_status='0' 的未删除申请,
+        抛「已有企业认证申请正在审核中,请勿重复提交」
+STEP 5: 组装申请快照:subject_type、各资料字段、audit_status='0'、create_by=登录用户名
+STEP 6: insertCertificationApply 落 account_certification_apply(自增回填 id)
+STEP 7: 联动 account_user.company_auth_status='1'(企业认证中,单列更新语句)
+STEP 8: STEP 6/7 同事务(@Transactional),任一失败整体回滚
+```
+
+### 5.3 审核分流(`auditCertificationApply` 增强)
+
+```
+STEP 1: 既有并发闸门(where audit_status='0')+ 驳回必填意见 不变
+STEP 2: 取 apply.subjectType 判断分支:
+        subject_type='1'(个人)→ 写 account_user.auth_status(2 通过 / 3 失败),行为同现状
+        subject_type∈{'2','3'} → 写 account_user.company_auth_status(2 通过 / 3 失败)
+                                且通过时同步 account_user.subject_type = apply.subjectType
+STEP 3: 通过分支继续 saveCertificationSnapshot(补全企业新增字段)
+```
+
+### 5.4 状态流转(端到端)
+
+```
+C 端提交 ─► account_certification_apply.audit_status='0' + account_user.company_auth_status='1'
+                    │
+后台「通过」─► audit_status='1' + company_auth_status='2' + subject_type 同步 + 落 account_certification
+后台「驳回」─► audit_status='2' + company_auth_status='3' + 记审核意见
+```
+
+---
+
+## 六、枚举与状态
+
+| 名称 | 取值 | 说明 |
+| --- | --- | --- |
+| `subject_type` 主体类型 | `1` 个人 / `2` 个体工商户 / `3` 企业 | 企业向导页提交 `2`/`3`(页内选择);个人页提交 `1` |
+| `audit_status` 审核状态 | `0` 待审核 / `1` 已通过 / `2` 已驳回 | 提交置 `0`,审核改 `1`/`2` |
+| `auth_status` 个人认证状态 | `0` 未认证 / `1` 审核中 / `2` 已认证 / `3` 认证失败 | 个人实名专用 |
+| `company_auth_status` 企业认证状态 | `0` 未认证 / `1` 审核中 / `2` 已认证 / `3` 认证失败 | 企业/个体专用(本期新增) |
+| `license_valid_type` / `legal_id_valid_type` 有效期类型 | `1` 长期 / `2` 短期 | 长期隐藏起止日期;短期必填 |
+
+「我的资料」页徽标映射:`auth_status` →「实名认证」行;`company_auth_status` →「企业/个体户认证」行(`0`未认证、`1`认证中、`2`已认证、`3`认证失败)。
+
+---
+
+## 七、影响范围与文件清单
+
+### 7.1 新增(ruoyi-user)
+
+| 文件 | 说明 |
+| --- | --- |
+| `domain/dto/CompanyCertificationApplyDto.java` | 企业/个体认证提交入参(含校验注解与字段注释) |
+| `domain/vo/CompanyCertificationStatusVo.java` | 企业/个体认证状态与资料回填出参 |
+| `src/test/.../AccountCertificationApplyServiceImplTest.java` | 追加提交/审核分流用例(正常、重复提交拒绝、短期缺日期、状态分流) |
+
+### 7.2 修改(ruoyi-user)
+
+| 文件 | 改动 |
+| --- | --- |
+| `domain/AccountCertificationApply.java` | 新增 13 个企业/个体字段 + getter/setter + toString |
+| `domain/AccountCertification.java` | 同步新增 13 个字段 |
+| `mapper/AccountCertificationApplyMapper.java` | 新增 `selectLatestCompanyApplyByUserId`、`countPendingCompanyApplyByUserId`;`selectLatestApplyByUserId`→`selectLatestPersonalApplyByUserId`(加 `subject_type='1'`)、`countPendingApplyByUserId`→`countPendingPersonalApplyByUserId`(加 `subject_type='1'`) |
+| `resources/mapper/user/AccountCertificationApplyMapper.xml` | resultMap / `selectCertificationApplyVo` / 列表 / insert 补新增列;主体类型过滤调整;新增企业两条查询 |
+| `resources/mapper/user/AccountCertificationMapper.xml` | resultMap / `selectCertificationVo` / insert / update 补新增列 |
+| `mapper/AccountUserMapper.java` | 新增 `updateUserCompanyAuthStatus(AccountUser)` |
+| `resources/mapper/user/AccountUserMapper.xml` | 新增 `updateUserCompanyAuthStatus`(只写 `company_auth_status`,通过时带 `<if>` 同步 `subject_type`) |
+| `service/IAccountCertificationApplyService.java` | 新增 `submitCompanyCertification(Long, CompanyCertificationApplyDto)`、`getCompanyCertificationStatus(Long)` |
+| `service/impl/AccountCertificationApplyServiceImpl.java` | 常量补 `SUBJECT_INDIVIDUAL='2'`/`SUBJECT_COMPANY='3'`;实现企业提交与状态查询;`auditCertificationApply` 按主体类型分流;`saveCertificationSnapshot` 补字段 |
+| `controller/AccountProfileController.java` | 新增 `POST /user/company-certification`、`GET /user/company-certification` |
+| `domain/vo/AccountUserInfoVo.java` | 新增 `companyAuthStatus` 字段 |
+| `service/impl/AccountUserServiceImpl.java` | `getAccountUserInfo` 填充 `companyAuthStatus` |
+
+### 7.3 修改(后台 hugray-admin)
+
+| 文件 | 改动 |
+| --- | --- |
+| `views/member/certification/detail.vue` | 「主体资料」区块补展示:主体类型、营业执照有效期类型/起止、法人身份证人像/国徽面(`image-preview`)、法人身份证有效期类型/起止、法人身份证详细地址、负责人姓名/电话/邮箱、收款类型、银行卡照片(`image-preview`)、银行卡号、开户城市 |
+
+> 列表页 `index.vue` 的主体类型/主体名称列已由 `subject_type` 与 `companyName \|\| realName` 覆盖,**无需改动**。
+
+### 7.4 新增(前端 hugray-user-miniapp)
+
+| 文件 | 说明 |
+| --- | --- |
+| `pages/profile/company-certification.vue` | 企业/个体四步向导页(步骤条 + 分步表单 + 图片上传 + 地区/日期选择) |
+
+### 7.5 修改(前端 hugray-user-miniapp)
+
+| 文件 | 改动 |
+| --- | --- |
+| `pages.json` | 注册 `pages/profile/company-certification`(`navigationStyle: custom`) |
+| `api/index.js` | 新增 `getCompanyCertificationStatus()`(→ GET)、`submitCompanyCertification(data)`(→ POST) |
+| `pages/profile/edit.vue` | 「企业/个体户认证」行由 `handlePlaceholder` 改为 `uni.navigateTo` 跳向导页;`companyStatus` 改按 `/info` 返回的 `companyAuthStatus` 映射回显 |
+
+> 网关无需改动;无需新增菜单/权限。
+
+### 7.6 新增(DDL)
+
+| 文件 | 说明 |
+| --- | --- |
+| `hugray-server/sql/hugray_account_company_certification_20261013.sql` | 两张认证表各补 13 列 + `account_user` 补 `company_auth_status`(幂等,含回滚注释) |
+
+---
+
+## 八、边界与异常清单
+
+| # | 场景 | 处理 |
+| --- | --- | --- |
+| 1 | 未登录访问 | 网关 `AuthFilter` 返回 401,前端跳登录页;后端不做分支 |
+| 2 | `user_id` 头缺失/为空 | 服务内兜底判空,抛业务异常(不 NPE) |
+| 3 | 必填项为空 / 纯空格 | DTO `@NotBlank` 拦截,返回字段级中文提示;前端「下一步」逐级预校验并 toast 首个缺失项 |
+| 4 | 有效期类型=长期 | 起止日期行隐藏且不校验;短期时必填 |
+| 5 | 有效期类型=短期但未填日期 | 服务层兜底抛「短期有效期必须填写起止日期」 |
+| 6 | 图片未上传 | 按必填拦截(toast「请上传XX图片」) |
+| 7 | 主体类型非法(非 2/3) | 服务层抛「主体类型不合法」 |
+| 8 | 已有待审核企业申请再次提交 | 服务层企业防重闸门提示「已有企业认证申请正在审核中,请勿重复提交」 |
+| 9 | 个人 / 企业申请互相干扰 | 状态查询与防重均按 `subject_type` 隔离;认证状态分列 `auth_status` / `company_auth_status` |
+| 10 | 审核通过 / 驳回 | 按 `subject_type` 分流回写,通过时同步 `subject_type` 并落 `account_certification` |
+| 11 | 落库或状态联动失败 | 同一事务回滚,返回统一错误,不产生半更新 |
+| 12 | 越权 | 会员身份取自令牌注入头,不接受前端传 `userId`,天然防水平越权 |
+| 13 | 三方核验 / 格式校验 | **本期明确不做**,仅基础必填校验 |
+| 14 | 图片上传失败 | 沿用 `uploadAvatarFile` 错误处理,toast 提示并可重试 |
+
+---
+
+## 九、规范符合性说明
+
+- 分层:Controller 只取身份 + 转发;业务在 Service;SQL 在 Mapper;Controller 不直接调 Mapper。
+- 命名:`submitCompanyCertification` / `getCompanyCertificationStatus` / `selectLatestCompanyApplyByUserId` / `countPendingCompanyApplyByUserId` / `updateUserCompanyAuthStatus` 见名知意;DTO / VO / Entity 分离。
+- 注释:DTO / VO / Mapper / Service 方法具备 Javadoc;核心流程按 `// STEP x:` 分步注释;实体新增字段附列名与取值注释。
+- 魔法值:`subject_type`、`audit_status`、`auth_status`、`company_auth_status`、`*_valid_type` 取值抽为常量/枚举语义。
+- 事务:提交接口 `insert + 状态联动` 同事务;审核 `更新申请 + 状态分流 + 落档` 同事务。
+- 幂等/防重:以待审核企业申请为闸门,拒绝重复提交。
+- 数据库:新增列显式长度 + COMMENT;无 `SELECT *`;无 `${}` 拼接;无循环查库;DDL 与代码同次提交。
+- 单测:覆盖企业提交正常、短期缺日期拒绝、重复提交拒绝、审核分流(企业写 `company_auth_status`)。
+- 文档:本文档与代码同仓库、同分支、同节奏提交。
+
+---
+
+## 十、已确认决策
+
+1. **认证状态存储**:**已定** —— 新增 `account_user.company_auth_status`,个人 / 企业徽标各自独立,互不覆盖。
+2. **主体类型**:**已定** —— 「企业信息」步骤顶部增加「主体类型」选择项(企业 / 个体工商户),`subject_type` 存 3 / 2(比原型多一个字段)。
+3. **有效期校验**:**已定** —— 「有效期类型」选**长期**时**隐藏起止日期且不必填**;选短期时显示且必填。
+4. **图片必填**:**已定** —— 营业执照、法人身份证人像面/国徽面、银行卡号码面照片**均为必填**。
+5. **校验深度**:**已定** —— 仅基础必填校验,**不做格式/三方核验**。
+6. **审核入口**:**已定** —— 复用后台「主体认证审核」列表/详情/通过/驳回链路,列表无需改,详情补展示新增字段。
diff --git "a/doc/design/\347\224\250\346\210\267\347\253\257_\345\256\236\345\220\215\350\256\244\350\257\201_20261013.md" "b/doc/design/\347\224\250\346\210\267\347\253\257_\345\256\236\345\220\215\350\256\244\350\257\201_20261013.md"
new file mode 100644
index 0000000..747e6ad
--- /dev/null
+++ "b/doc/design/\347\224\250\346\210\267\347\253\257_\345\256\236\345\220\215\350\256\244\350\257\201_20261013.md"
@@ -0,0 +1,305 @@
+# 用户端小程序 实名认证 开发设计文档
+
+- 模块:ruoyi-user(会员业务)+ ruoyi-gateway(路由/鉴权头注入)+ hugray-admin(后台主体认证审核)
+- 日期:2026-10-13
+- 需求来源:"点击实名认证跳转到实名认证页面,只做基础的身份证格式校验和姓名必填校验不做三方真实验证,提交后的数据需要到管理后台的主体认证审核列表"
+- 关联前端:`hugray-user-miniapp`(`pages/profile/edit.vue` 入口、新增 `pages/profile/certification.vue`)
+- 关联后台:`hugray-admin/src/views/member/certification/index.vue`(主体认证审核列表,菜单 2202)
+- 关联文档:`doc/design/用户端_我的资料_头像上传_20261013.md`(同类 C 端写接口范式)、`doc/design/用户端_我的资料_20261013.html`(本页 UI 参考)
+- 数据权威源:`192.168.110.111:3306/hugray-account`(实际表结构优先于本文档)
+
+---
+
+## 一、功能概述
+
+「我的资料」页(`pages/profile/edit.vue`)「实名认证」行当前点击仅弹占位 toast,本次改为**跳转到实名认证页**:
+
+1. 新增 C 端实名认证页 `pages/profile/certification.vue`(自定义导航栏 + 姓名 / 身份证号两个输入 + 底部「提交」按钮,见原型图);
+2. 页面**只做基础校验**:姓名必填、身份证号为 18 位标准格式;**不接入任何三方实名核验**;
+3. 提交后落 `account_certification_apply`(`subject_type='1'` 个人、`audit_status='0'` 待审核),并联动 `account_user.auth_status='1'`(审核中),使其出现在后台**主体认证审核列表**;
+4. 「我的资料」页实名认证徽标改为按后端下发的 `authStatus` 显示真实状态(未认证 / 认证中 / 已认证 / 认证失败),形成闭环。
+
+后台「审」链路(列表 / 详情 / 通过 / 驳回)**已存在且无需改动**:`AccountCertificationApplyController`(`/account/user/certification/**`,权限 `member:certification:*`)、`AccountCertificationApplyServiceImpl`(审核 + `auth_status` 联动 + 通过后落 `account_certification`)。本次只补 C 端"提交"这一缺口。
+
+---
+
+## 二、现状与关键约束
+
+### 2.1 缺口:只有"审"没有"增"
+
+`AccountCertificationApplyController` 类注释明确:*"本模块只有"审"没有"增删":申请由 C 端会员提交(不在本仓库范围)"*。C 端提交接口与页面均需**新建**。
+
+### 2.2 数据模型已就绪(无 DDL 变更)
+
+| 表 | 用途 | 关键列 |
+| --- | --- | --- |
+| `account_certification_apply` | 每次提交的申请(驳回重提多一条),**本次落这张表** | `id`(自增主键兼申请号)、`user_id`、`subject_type`、`real_name`、`id_card`、`audit_status`、`del_flag`、`create_by`、`create_time` |
+| `account_certification` | 当前生效认证资料,审核**通过**时由服务层落一份 | 审核通过后写入,本次不直接写 |
+| `account_user.auth_status` | 会员整体认证状态 | 0未认证 1审核中 2已认证 3认证失败 |
+
+> 状态联动口径(见 DDL 注释):**提交申请 → `auth_status='1'`;通过 → `'2'`;驳回 → `'3'`**。通过/驳回联动已由既有审核服务实现,本次只补"提交 → '1'"。
+
+### 2.3 主键策略
+
+`account_certification_apply.id` 为 `bigint AUTO_INCREMENT`(既有表,非雪花),故 insert 走 `useGeneratedKeys="true" keyProperty="id"`,与既有 `insertCertification` 一致。
+
+### 2.4 认证状态取值必须收口
+
+状态/类型取值语义已在多处存在,本次沿用既有字符串约定(与 `AccountCertificationApplyServiceImpl` 中的 `AUDIT_PASS="1"` 等常量同风格),在服务层抽为常量,不散落魔法值。
+
+### 2.5 路径规划
+
+- 网关注入 `user_id`;`SecurityUtils.getUserId()` 取会员身份,**C 端接口不接受前端传 userId**(防水平越权)。
+- C 端个人中心控制器 `AccountProfileController`(`@RequestMapping("/user")`,外部 `/miniapp/account/user/**`)已承载 `/info`、`/avatar`,本次**在其上新增** `POST /user/certification`(外部 `POST /miniapp/account/user/certification`)。
+- 与后台 `AccountCertificationApplyController`(外部 `/account/user/certification/**`)**前缀不同域**(`/miniapp/account` vs `/account`),无冲突。
+
+---
+
+## 三、接口设计
+
+统一响应体:C 端 `R<T>`(成功 `code=200`);后台 `AjaxResult` / `TableDataInfo`。
+
+### 3.1 提交实名认证(本次新增)
+
+- **外部路径**:`POST /miniapp/account/user/certification`(网关 `StripPrefix=2` → 服务内 `POST /user/certification`)
+- **鉴权**:需登录。路径不在网关白名单,未登录由 `AuthFilter` 直接返回 401,前端 `request.js` 自动跳登录页;**后端不做未登录分支**。
+- **入参**:JSON,会员身份取自网关注入的 `user_id` 头,**不接受前端传 userId**
+
+| 字段 | 类型 | 必填 | 校验 | 说明 |
+| --- | --- | --- | --- | --- |
+| realName | String | 是 | `@NotBlank` + `@Size(max=64)` | 姓名(对应 `real_name varchar(64)`) |
+| idCard | String | 是 | `@NotBlank` + 18 位格式 `@Pattern` + `@Size(max=32)` | 身份证号(对应 `id_card varchar(32)`) |
+
+- **出参**:`R<Void>`(成功即 `code=200`)
+- **错误提示**:`姓名不能为空` / `姓名过长` / `身份证号不能为空` / `身份证号格式不正确` / `已有实名认证申请正在审核中,请勿重复提交`
+
+身份证号基础格式正则(18 位:地区码 6 位 + 出生日期 + 顺序码 + 校验位;**只校验结构与日期范围,不校验校验码、不核验真实性**):
+
+```
+^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$
+```
+
+### 3.2 查询会员信息(本次扩展)
+
+- **外部路径**:`GET /miniapp/account/user/info`(既有)
+- **改动**:出参 `AccountUserInfoVo` 新增 `authStatus` 字段(`account_user.auth_status` 原值:0/1/2/3),供「我的资料」页显示实名认证真实状态。手机号仍只下发脱敏值。
+
+### 3.3 后台审核链路(既有,不改)
+
+`GET /account/user/certification/list`(`member:certification:list`)、`GET /account/user/certification/detail/{id}`、`PUT /account/user/certification/audit`(`member:certification:audit`)。C 端提交的申请将直接出现在该列表(`subject_type='1'` 个人、`audit_status='0'` 待审核,列表按待审核在前排序)。
+
+---
+
+## 四、数据库设计
+
+库:`hugray-account`。
+
+**无 DDL 变更**:`account_certification_apply` 与 `account_user.auth_status` 均已存在,本次仅插入 / 更新。
+
+---
+
+## 五、流程设计
+
+### 5.1 前端提交链路(`pages/profile/certification.vue`)
+
+```
+STEP 1: onShow 校验登录态(ensureLogin),未登录跳登录页
+STEP 2: 用户填写姓名 / 身份证号
+STEP 3: 点「提交」→ 本地校验:姓名非空(trim)且 ≤64;身份证号匹配 18 位正则
+STEP 4: 校验不通过则 toast 提示并中止,不发请求
+STEP 5: 调 submitCertification(realName, idCard) → POST /miniapp/account/user/certification
+STEP 6: 成功后 toast「提交成功,等待审核」并 navigateBack 回我的资料页;失败提示后端错误信息
+```
+
+### 5.2 后端提交(`AccountCertificationApplyServiceImpl.submitPersonalCertification`)
+
+```
+STEP 1: 校验 userId 非空(未登录由网关拦截,此处兜底),为空抛业务异常
+STEP 2: 防重复提交:该会员若存在 audit_status='0'(待审核)且未删除的申请,
+        抛「已有实名认证申请正在审核中,请勿重复提交」
+STEP 3: 组装申请快照:subject_type='1'(个人)、real_name、id_card、
+        audit_status='0'、create_by=登录用户名
+STEP 4: insertCertificationApply 落 account_certification_apply(自增回填 id)
+STEP 5: 联动 account_user.auth_status='1'(审核中,调既有 updateUserAuthStatus 单列语句)
+STEP 6: 以上 STEP 4/5 同事务(@Transactional),任一失败整体回滚
+```
+
+### 5.3 状态流转(端到端)
+
+```
+C 端提交 ──► account_certification_apply.audit_status='0'  +  account_user.auth_status='1'
+                    │
+后台「通过」──► audit_status='1' + auth_status='2' + 落 account_certification(既有逻辑)
+后台「驳回」──► audit_status='2' + auth_status='3' + 记审核意见(既有逻辑)
+```
+
+---
+
+## 六、枚举与状态
+
+| 名称 | 取值 | 说明 |
+| --- | --- | --- |
+| `subject_type` 主体类型 | `1` 个人 / `2` 个体工商户 / `3` 企业 | 本次固定提交 `1`(个人实名);企业认证入口本期仍为占位 |
+| `audit_status` 审核状态 | `0` 待审核 / `1` 已通过 / `2` 已驳回 | 提交置 `0`,审核改 `1`/`2` |
+| `auth_status` 会员认证状态 | `0` 未认证 / `1` 审核中 / `2` 已认证 / `3` 认证失败 | 提交置 `1`;通过置 `2`;驳回置 `3` |
+
+「我的资料」页徽标与 `auth_status` 映射:`0`→未认证、`1`→认证中、`2`→已认证、`3`→认证失败。
+
+---
+
+## 七、影响范围与文件清单
+
+### 新增(ruoyi-user)
+
+| 文件 | 说明 |
+| --- | --- |
+| `domain/dto/AccountCertificationApplyDto.java` | C 端实名认证入参(`realName` / `idCard`,含校验注解与字段注释) |
+| `src/test/.../AccountCertificationApplyServiceImplTest.java` | 提交服务单测(正常 / 待审核重复提交拒绝 / 状态联动) |
+
+### 修改(ruoyi-user)
+
+| 文件 | 改动 |
+| --- | --- |
+| `controller/AccountProfileController.java` | 注入 `IAccountCertificationApplyService`;新增 `POST /user/certification` |
+| `service/IAccountCertificationApplyService.java` | 新增 `submitPersonalCertification(Long userId, String realName, String idCard)` |
+| `service/impl/AccountCertificationApplyServiceImpl.java` | 实现提交(防重校验 + 落申请 + 联动 `auth_status='1'`,`@Transactional`) |
+| `mapper/AccountCertificationApplyMapper.java` | 新增 `insertCertificationApply(AccountCertificationApply)`、`countPendingApplyByUserId(Long userId)` |
+| `resources/mapper/user/AccountCertificationApplyMapper.xml` | 新增上述两条 SQL |
+| `domain/vo/AccountUserInfoVo.java` | 新增 `authStatus` 字段 |
+| `service/impl/AccountUserServiceImpl.java` | `getAccountUserInfo` 填充 `authStatus` |
+| `src/test/.../AccountUserServiceImplTest.java` | 补充 `authStatus` 下发断言 |
+
+### 新增(前端 hugray-user-miniapp)
+
+| 文件 | 说明 |
+| --- | --- |
+| `pages/profile/certification.vue` | 实名认证页(自定义导航栏 + 姓名/身份证号输入 + 底部提交;沿用 `edit.vue` 的导航与按钮风格) |
+
+### 修改(前端 hugray-user-miniapp)
+
+| 文件 | 改动 |
+| --- | --- |
+| `pages.json` | 注册 `pages/profile/certification`(`navigationStyle: custom`) |
+| `api/index.js` | 新增 `submitCertification(realName, idCard)`(→ `/miniapp/account/user/certification`) |
+| `pages/profile/edit.vue` | 「实名认证」行由 `handlePlaceholder` 改为 `uni.navigateTo` 跳认证页;`realNameStatus` 改为按 `/info` 返回的 `authStatus` 映射 |
+
+> 网关无需改动:`/miniapp/account/**` 路由已存在。
+
+---
+
+## 八、边界与异常清单
+
+| # | 场景 | 处理 |
+| --- | --- | --- |
+| 1 | 未登录访问 | 网关 `AuthFilter` 返回 401,前端跳登录页;后端不做分支 |
+| 2 | `user_id` 头缺失/为空 | 服务内兜底判空,抛业务异常(不 NPE) |
+| 3 | 姓名为空 / 纯空格 | DTO `@NotBlank` 拦截,提示「姓名不能为空」 |
+| 4 | 姓名超长(>64) | DTO `@Size` 拦截,提示「姓名过长」 |
+| 5 | 身份证号为空 | DTO `@NotBlank` 拦截,提示「身份证号不能为空」 |
+| 6 | 身份证号格式非法(非 18 位 / 含非法字符 / 非 X 结尾) | DTO `@Pattern` 拦截,提示「身份证号格式不正确」 |
+| 7 | 已有待审核申请再次提交 | 服务层 `countPendingApplyByUserId>0` 时抛「已有实名认证申请正在审核中,请勿重复提交」 |
+| 8 | 落库或状态联动失败 | 同一事务回滚,返回统一错误,不产生半更新 |
+| 9 | 越权 | 会员身份取自令牌注入头,不接受前端传 `userId`,天然防水平越权 |
+| 10 | 三方实名核验 | **本期明确不做**,仅基础格式校验 |
+
+---
+
+## 九、规范符合性说明
+
+- 分层:Controller 只取身份 + 转发,业务在 Service,SQL 在 Mapper;Controller 不直接调 Mapper。
+- 命名:`submitPersonalCertification` / `insertCertificationApply` / `countPendingApplyByUserId` 见名知意;DTO 与 Entity 分离。
+- 注释:DTO / Mapper 方法 / Service 方法具备 Javadoc;核心流程按 `// STEP x:` 分步注释;实体状态字段注明取值。
+- 魔法值:`subject_type`、`audit_status`、`auth_status` 取值抽为常量;错误提示集中。
+- 事务:提交接口 `insert + 联动更新` 同事务,保证一致性。
+- 幂等/防重:以待审核申请为闸门,拒绝重复提交。
+- 数据库:无 DDL;无 `SELECT *`;无 `${}` 拼接;无循环查库。
+- 单测:覆盖提交正常、重复提交拒绝、状态联动;Mockito Mock Mapper,不依赖真实库。
+
+---
+
+## 十、已确认决策
+
+1. **状态回显**:**已定** —— 扩展 `AccountUserInfoVo.authStatus`,「我的资料」页徽标显示真实状态(未认证/认证中/已认证/认证失败)。
+2. **重复提交**:**已定** —— 存在「待审核」申请时**拒绝并提示**,不新增重复单。
+3. **核验深度**:**已定** —— 仅姓名必填 + 身份证号 18 位格式校验,**不做三方实名核验**。
+4. **入口范围**:**已定** —— 本期只做「实名认证」(个人 `subject_type='1'`);「企业/个体户认证」入口维持占位。
+
+---
+
+## 十一、增量需求:认证状态回填与只读(2026-10-13 补充)
+
+### 11.1 需求描述
+
+进入实名认证页时,按当前认证状态**回填**该会员已提交的资料,并区分交互:
+
+| 当前状态 | 姓名 / 身份证号 | 底部按钮(文案 / 可点性) | 驳回原因 |
+| --- | --- | --- | --- |
+| 未认证(无申请) | 空,可编辑 | 「提交」/ 可点 | 不展示 |
+| 认证中(待审核) | 回填,**只读** | 「审核中」/ **置灰不可点** | 不展示 |
+| 认证成功(已通过) | 回填,**只读** | 「已认证」/ **置灰不可点** | 不展示 |
+| 认证失败(已驳回) | 回填,**可编辑** | 「重新提交」/ 可点 | **在身份证号下方展示** |
+
+> 已确认决策:认证成功同样置灰不可点(已认证无需重复提交);认证中 / 认证成功输入框只读不可编辑。
+
+### 11.2 接口设计(新增:查询当前认证状态)
+
+- **外部路径**:`GET /miniapp/account/user/certification`(网关 `StripPrefix=2` → 服务内 `GET /user/certification`)
+- 与既有 `POST /miniapp/account/user/certification`(提交)**同路径不同方法**,无需新增路由。
+- 鉴权:需登录;会员身份取自网关注入的登录态,**不接受前端传 userId**。
+
+取该会员**最近一次**申请(`account_certification_apply` 按 `id desc limit 1`,只取 `del_flag='0'`),出参 `AccountCertificationStatusVo`:
+
+| 字段 | 类型 | 说明 |
+| --- | --- | --- |
+| realName | String | 姓名(回填用) |
+| idCard | String | 身份证号(回填用) |
+| auditStatus | String | 0待审核 1已通过 2已驳回;**为空表示从未提交** |
+| auditRemark | String | 驳回原因(audit_status='2' 时有值) |
+
+无申请时返回**空字段对象**(各字段为 null),前端按「未认证」处理,不返回 404。
+
+### 11.3 前端状态映射
+
+`auditStatus` → 页面状态:`'0'`→认证中、`'1'`→认证成功、`'2'`→认证失败、空/缺省→未认证。
+
+- 只读 & 置灰条件:页面状态 ∈ {认证中, 认证成功};
+- 驳回原因展示条件:页面状态 = 认证失败 且 `auditRemark` 非空;
+- 按钮文案:未认证→「提交」、认证中→「审核中」、认证成功→「已认证」、认证失败→「重新提交」(未匹配兜底「提交」)。
+
+### 11.4 影响范围(增量文件清单)
+
+**新增(ruoyi-user)**
+
+| 文件 | 说明 |
+| --- | --- |
+| `domain/vo/AccountCertificationStatusVo.java` | C 端认证状态回填出参(realName / idCard / auditStatus / auditRemark) |
+
+**修改(ruoyi-user)**
+
+| 文件 | 改动 |
+| --- | --- |
+| `mapper/AccountCertificationApplyMapper.java` | 新增 `selectLatestApplyByUserId(Long userId)` |
+| `resources/mapper/user/AccountCertificationApplyMapper.xml` | 新增上述查询 SQL(取最近一条) |
+| `service/IAccountCertificationApplyService.java` | 新增 `getCertificationStatus(Long userId)` |
+| `service/impl/AccountCertificationApplyServiceImpl.java` | 实现查询:取最近一条申请映射为 VO,无则返回空对象 |
+| `controller/AccountProfileController.java` | 新增 `GET /user/certification` |
+| `src/test/.../AccountCertificationApplyServiceImplTest.java` | 补充查询用例(有申请回填 / 无申请空对象) |
+
+**修改(前端 hugray-user-miniapp)**
+
+| 文件 | 改动 |
+| --- | --- |
+| `api/index.js` | 新增 `getCertificationStatus()`(→ `GET /miniapp/account/user/certification`) |
+| `pages/profile/certification.vue` | 进入页面拉取状态并回填;认证中/成功输入框只读、按钮置灰且文案随状态变化(提交/审核中/已认证/重新提交);认证失败展示驳回原因 |
+
+> 无需 DDL、无需网关改动(`/miniapp/account/**` 路由已存在)。
+
+### 11.5 边界与异常(增量)
+
+| # | 场景 | 处理 |
+| --- | --- | --- |
+| 1 | 从未提交(查询返回空对象) | 页面按未认证处理,空表单可编辑可提交 |
+| 2 | 查询接口异常 | 静默降级为未认证(空表单),不阻塞页面 |
+| 3 | 认证中 / 认证成功 | 输入框 `disabled` 只读,按钮置灰且点击直接 return |
+| 4 | 认证失败但驳回原因为空 | 不展示驳回原因区域(仅可编辑重提) |
+| 5 | 认证失败后重新提交成功 | 回退上一页;再次进入时按新的「待审核」状态回填只读 |
diff --git "a/doc/design/\347\224\250\346\210\267\347\253\257_\346\210\221\347\232\204\350\265\204\346\226\231_\345\244\264\345\203\217\344\270\212\344\274\240_20261013.md" "b/doc/design/\347\224\250\346\210\267\347\253\257_\346\210\221\347\232\204\350\265\204\346\226\231_\345\244\264\345\203\217\344\270\212\344\274\240_20261013.md"
new file mode 100644
index 0000000..2be5b9e
--- /dev/null
+++ "b/doc/design/\347\224\250\346\210\267\347\253\257_\346\210\221\347\232\204\350\265\204\346\226\231_\345\244\264\345\203\217\344\270\212\344\274\240_20261013.md"
@@ -0,0 +1,229 @@
+# 用户端小程序 我的资料 头像上传 开发设计文档
+
+- 模块:ruoyi-user(会员业务)+ ruoyi-file(文件存储)+ ruoyi-gateway(路由/鉴权头注入)
+- 日期:2026-10-13
+- 需求来源:“实现头像上传功能”
+- 关联前端:`hugray-user-miniapp`(`pages/profile/edit.vue`)
+- 关联文档:`doc/design/用户端_个人中心_20261013.md`(个人中心 / 头像字段来源)、`doc/design/用户端_我的资料_20261013.html`(本页 UI 原型)
+- 数据权威源:`192.168.110.111:3306/hugray-account`(实际表结构优先于本文档)
+
+---
+
+## 一、功能概述
+
+「我的资料」页(`pages/profile/edit.vue`)头像行当前点击仅弹占位 toast,本次改为**真实上传**:点击头像 → 选图 → 上传文件 → 写回 `account_user.avatar` → 页面即时回显;个人中心页再次进入时同步展示新头像。
+
+采用**前端直传文件服务 + 会员服务只更新头像地址**两段式链路:
+
+1. 小程序 `uni.uploadFile` 直传网关 `POST /file/upload`(复用 `ruoyi-file` 现成上传能力),拿到图片可访问 URL;
+2. 小程序携 URL 调新增的 C 端接口 `POST /miniapp/account/user/avatar`,`ruoyi-user` **只更新 `avatar` 一列**。
+
+本次只新增**一个 C 端写接口**;文件上传复用既有 `/file/upload`,无需改动。
+
+---
+
+## 二、现状与关键约束
+
+### 2.1 文件服务已就绪,直接复用
+
+`ruoyi-file`(端口 9300)已提供上传入口 `SysFileController#upload`:
+
+- 服务内路径 `POST /upload`;网关路由 `/file/**` → `lb://ruoyi-file`(`StripPrefix=1`),**对外路径 `POST /file/upload`**。
+- 返回 `R<SysFile>{ name, url }`,`url` 即图片可访问地址。
+- 默认存储实现为 `LocalSysFileServiceImpl`(`@Primary`),落盘到 `file.path`,访问前缀由 `file.domain` + `file.prefix` 拼出(配置由 Nacos `ruoyi-file-dev.yml` 下发)。
+
+### 2.2 上传接口需登录态
+
+`/file/upload` **不在**网关白名单(`ignore.whites`)内,网关 `AuthFilter` 会校验令牌并注入 `user_id` 头。故小程序上传时必须携带 `Authorization: Bearer <token>`,与普通接口一致。
+
+### 2.3 前端请求封装仅支持 JSON
+
+`hugray-user-miniapp/utils/request.js` 固定 `Content-Type: application/json`,基于 `uni.request`,**不能直接传文件**。需新增一个基于 `uni.uploadFile` 的封装(单独注入令牌与错误解包),不污染现有 JSON 请求链路。
+
+### 2.4 `ruoyi-user` 不依赖 `ruoyi-api-system`
+
+`ruoyi-user/pom.xml` 未引入 `ruoyi-api-system`,**无法**直接使用 `RemoteFileService`(Feign)做服务端转存。本方案采用「前端直传」,故**无需新增依赖**。
+
+### 2.5 写头像必须走「单列更新语句」
+
+`AccountUserMapper` 既有约定:`insertAccountUser` / `updateAccountUser` 是共享入口、按 `<if>` 动态拼列,**严禁**把「只该由本模块显式改写的列」挂上去(参见 `updateUserStatus` / `updateUserAuthStatus` / `updateUserProviderFlag` 的注释)。因此头像更新**单开** `updateUserAvatar`,只写 `avatar` 一列,避免 `/account/user`(后台编辑)请求体带上 `avatar` 即改他人头像的口子。
+
+### 2.6 字段长度约束
+
+`account_user.avatar` 为 `varchar(255)`,服务端对传入 URL 做长度校验(> 255 直接拒绝),避免截断或 SQL 报错。
+
+---
+
+## 三、接口设计
+
+统一响应体:`R<T>`(`code=200` 成功),与 C 端 `/miniapp/**` 既有约定一致。
+
+### 3.1 上传图片文件(复用既有,无需改动)
+
+- **外部路径**:`POST /file/upload`(网关 `StripPrefix=1` → 服务内 `POST /upload`)
+- **鉴权**:需登录(携带 `Authorization: Bearer <token>`)
+- **请求**:`multipart/form-data`,表单字段名 `file`
+- **出参**:`R<SysFile>`
+
+```json
+{
+  "code": 200,
+  "msg": "操作成功",
+  "data": { "name": "2026/10/13/xxx.png", "url": "https://xxx/file/upload/2026/10/13/xxx.png" }
+}
+```
+
+### 3.2 更新当前会员头像(本次新增)
+
+- **外部路径**:`POST /miniapp/account/user/avatar`(网关 `StripPrefix=2` → 服务内 `POST /user/avatar`)
+- **鉴权**:需登录。路径不在网关白名单,未登录由 `AuthFilter` 直接返回 401,前端 `request.js` 自动跳登录页;**后端不做未登录分支**。
+- **入参**:`{ "avatar": "<图片URL>" }`(会员身份取自网关注入的 `user_id` 头,**不接受前端传 userId**)
+- **出参**:`R<Void>`(成功即 `code=200`)
+
+| 字段 | 类型 | 必填 | 说明 |
+| --- | --- | --- | --- |
+| avatar | String | 是 | 头像地址(来自 `/file/upload` 返回的 `url`);非空、长度 ≤ 255 |
+
+- **错误提示**:`头像地址不能为空` / `头像地址过长` / `更新头像失败`(会员不存在或更新异常)
+
+---
+
+## 四、数据库设计
+
+库:`hugray-account`(权威库 `192.168.110.111:3306`)。
+
+**无 DDL 变更**:`account_user.avatar varchar(255)` 已存在,本次仅写入该列。
+
+---
+
+## 五、流程设计
+
+### 5.1 前端上传链路(`pages/profile/edit.vue` → `handleAvatarChange`)
+
+```
+STEP 1: uni.chooseImage({ count:1, sizeType:['compressed'], sourceType:['album','camera'] }) 选图
+STEP 2: 上传前本地校验:临时文件大小 ≤ 5MB,否则提示「图片不能超过 5MB」并中止
+STEP 3: uploadFile('/file/upload', tempFilePath) 直传文件服务(携带 Bearer 令牌)
+STEP 4: 取响应 data.url;为空则提示「上传失败」
+STEP 5: updateAvatar(url) 调 POST /miniapp/account/user/avatar 写回
+STEP 6: 成功则更新本地 avatar 展示;失败提示且不改动已展示头像
+```
+
+### 5.2 后端更新头像(`AccountUserServiceImpl.updateAvatar`)
+
+```
+STEP 1: 校验 userId 非空(未登录由网关拦截,此处兜底),为空抛「更新头像失败」
+STEP 2: 校验 avatarUrl 非空,为空抛「头像地址不能为空」
+STEP 3: 校验 avatarUrl 长度 ≤ 255,超长抛「头像地址过长」
+STEP 4: 组装 AccountUser(userId + avatar + updateBy)调用 mapper.updateUserAvatar(只更 avatar 列)
+STEP 5: affected == 0(会员不存在/已删除)抛「更新头像失败」;否则返回
+```
+
+Mapper 单列更新语句(沿用 `updateUserStatus` 套路):
+
+```xml
+<!-- 头像更新专用:只动 avatar 一列,避免挂到共享的 updateAccountUser 上被后台编辑接口借道改头像 -->
+<update id="updateUserAvatar" parameterType="AccountUser">
+    update account_user
+    <set>
+        avatar = #{avatar},
+        <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+        update_time = sysdate()
+    </set>
+    where user_id = #{userId} and del_flag = '0'
+</update>
+```
+
+---
+
+## 六、枚举与状态
+
+| 名称 | 取值 | 说明 |
+| --- | --- | --- |
+| 允许图片类型 | `jpg` / `jpeg` / `png` / `webp` | 由文件服务既有校验 + 前端 `chooseImage` 共同约束 |
+| 图片大小上限 | 5 MB | 前端上传前本地校验(文件服务侧上限以其自身配置为准) |
+
+本次不涉及状态流转。
+
+---
+
+## 七、影响范围与文件清单
+
+### 新增(ruoyi-user)
+
+| 文件 | 说明 |
+| --- | --- |
+| `domain/dto/AccountUserAvatarDto.java` | 头像更新入参(`avatar`,含校验注解与字段注释) |
+
+### 修改(ruoyi-user)
+
+| 文件 | 改动 |
+| --- | --- |
+| `controller/AccountProfileController.java` | 新增 `POST /user/avatar` |
+| `service/IAccountUserService.java` | 新增 `updateAvatar(Long userId, String avatarUrl)` |
+| `service/impl/AccountUserServiceImpl.java` | 实现 `updateAvatar`(校验 + 单列更新) |
+| `mapper/AccountUserMapper.java` | 新增 `updateUserAvatar(AccountUser)` |
+| `resources/mapper/user/AccountUserMapper.xml` | 新增 `updateUserAvatar` 语句 |
+
+### 修改(前端 hugray-user-miniapp)
+
+| 文件 | 改动 |
+| --- | --- |
+| `utils/request.js` | 新增 `uploadFile(url, filePath, options)`:基于 `uni.uploadFile`,注入令牌、统一解包与错误提示,复用登录失效处理 |
+| `api/index.js` | 新增 `uploadAvatarFile(filePath)`(→ `/file/upload`)与 `updateAvatar(avatar)`(→ `/miniapp/account/user/avatar`) |
+| `pages/profile/edit.vue` | 头像行 `handlePlaceholder('头像更换')` 改为 `handleAvatarChange`(选图 → 上传 → 写回 → 回显) |
+
+### 修改(文档)
+
+| 文件 | 改动 |
+| --- | --- |
+| `doc/design/用户端_个人中心_20261013.md` | 头像字段「现状」由占位更新为「已支持上传」 |
+
+### 单测(ruoyi-user)
+
+| 文件 | 用例 |
+| --- | --- |
+| `AccountUserServiceImplTest.java` | `updateAvatar`:正常更新 / 空地址 / 超长地址 / 会员不存在(affected=0) |
+
+> 网关无需改动:`/miniapp/account/**`、`/file/**` 路由均已存在。
+
+---
+
+## 八、边界与异常清单
+
+| # | 场景 | 处理 |
+| --- | --- | --- |
+| 1 | 未登录访问 | 网关 `AuthFilter` 返回 401,前端跳登录页;后端不做分支 |
+| 2 | `user_id` 头缺失/为空 | 服务内兜底判空,抛「更新头像失败」(不 NPE) |
+| 3 | 上传的 `avatar` 为空 | 抛「头像地址不能为空」 |
+| 4 | `avatar` 长度 > 255 | 抛「头像地址过长」(列 `varchar(255)`) |
+| 5 | 用户取消选图 | `chooseImage` 成功回调不触发,静默中止(不提示、不请求) |
+| 6 | 图片超过 5MB | 前端本地提示「图片不能超过 5MB」,不发起上传 |
+| 7 | 上传失败(网络/文件服务异常) | 提示「上传失败」,不改动已展示头像 |
+| 8 | 写回失败(接口异常) | 提示后端错误信息,头像保持原值 |
+| 9 | 会员不存在 / 已逻辑删除 | `updateUserAvatar` 命中 `del_flag='0'` 过滤,affected=0 → 抛「更新头像失败」 |
+| 10 | 越权 | 会员身份取自令牌注入头,不接受前端传 `userId`,天然防水平越权 |
+| 11 | 旧头像文件 | **本期不清理**(已确认);后续可加定时清理 |
+| 12 | 并发重复上传 | 写回为整列覆盖,后写生效;无幂等诉求,不做去重 |
+
+---
+
+## 九、规范符合性说明
+
+- 分层:Controller 只做取身份 + 转发,业务在 Service,SQL 在 Mapper;Controller 不直接调 Mapper。
+- 命名:`updateAvatar` / `updateUserAvatar` / `uploadAvatarFile` 见名知意;DTO 与 Entity 分离。
+- 注释:DTO / Mapper 方法 / Service 方法具备 Javadoc;核心流程按 `// STEP x:` 分步注释。
+- 魔法值:图片大小上限等阈值抽为常量;无散落魔法值。
+- 异常:统一 `ServiceException` + 全局处理器,不散落 try-catch 拼返回值。
+- 日志:`@Slf4j` 占位符 + `[业务标记]` 前缀;头像 URL 非敏感,日志不打完整用户隐私。
+- 数据库:单列更新语句;无 DDL;无 `SELECT *`;无 `${}` 拼接;无循环查库。
+- 单测:`updateAvatar` 覆盖正常 / 空值 / 超长 / 会员不存在,Mockito Mock Mapper。
+
+---
+
+## 十、已确认决策
+
+1. **上传链路**:**已定** —— 前端 `uni.uploadFile` 直传 `/file/upload`,`ruoyi-user` 只更新 `avatar`(免加 `ruoyi-api-system` 依赖)。
+2. **存储介质**:**已定** —— 沿用 `ruoyi-file` 本地存储(`LocalSysFileServiceImpl`,`@Primary`)。
+3. **旧头像文件**:**已定** —— 本期不清理,仅覆盖 `avatar` 字段。
+4. **数据库**:**已定** —— 无 DDL 变更,复用既有 `account_user.avatar varchar(255)`。
diff --git "a/doc/design/\347\224\250\346\210\267\347\253\257_\346\210\221\347\232\204\351\202\200\350\257\267_20261013.md" "b/doc/design/\347\224\250\346\210\267\347\253\257_\346\210\221\347\232\204\351\202\200\350\257\267_20261013.md"
new file mode 100644
index 0000000..72918cc
--- /dev/null
+++ "b/doc/design/\347\224\250\346\210\267\347\253\257_\346\210\221\347\232\204\351\202\200\350\257\267_20261013.md"
@@ -0,0 +1,239 @@
+# 用户端小程序 我的邀请 开发设计文档
+
+- 模块:ruoyi-user(会员/邀请业务)+ ruoyi-auth(微信小程序码)+ hugray-user-miniapp(前端)
+- 日期:2026-10-13
+- 需求来源:"点击个人中心-我的邀请,进入我的邀请页面,需要根据pen源文件的设计风格设计ui然后实现页面和后端的业务功能"
+- 原型:3 张 UI 稿(邀请码 / 我邀请的 / 邀请我的)
+- 关联文档:`doc/design/用户端_个人中心_20261013.md`(入口所在页)、`doc/design/用户端_登录注册_20261012.md`(注册链路、扫码回填)
+- 数据权威源:`192.168.110.111:3306/hugray-account`(实际表结构优先于本文档)
+
+---
+
+## 一、功能概述
+
+个人中心「我的邀请码」行当前点击仅弹占位 toast,本次改为**跳转到「我的邀请」页**(`pages/profile/invite.vue`):
+
+1. 新增 C 端页面「我的邀请」,顶部为**三段式 Tab**(邀请码 / 我邀请的 / 邀请我的),自定义导航栏(返回箭头 + 居中标题「我的邀请」);
+2. **邀请码 Tab**:展示本人邀请码(`account_user.my_referral_code`)与**真实微信小程序码**,底部绿色「分享邀请码」按钮;
+3. **我邀请的 Tab**:按**绑定时间倒序**列出直接下级(头像、昵称、等级徽标、脱敏手机号、绑定日、合计消费);
+4. **邀请我的 Tab**:展示本人**上级**(推荐人)信息(大头像、昵称、等级徽标、脱敏手机号、绑定日);无上级时展示空态;
+5. **扫码链路**:小程序码 `scene = 邀请码`,扫码直达注册页并**自动填入邀请码**(补 `register.vue` 的 `onLoad` scene 解析);「分享邀请码」分享注册页链接(`?referralCode=xxx`),同样自动填入。
+
+> 无订单模块,故「合计消费」直接用预留列 `buy_amount_total`(决策见 2.2);
+> 等级徽标直接展示 `system_level` 原值,不扩展 `MemberLevelEnum`(决策见 2.3)。
+
+---
+
+## 二、现状与关键约束
+
+### 2.1 数据模型已预留,无需改表
+
+`account_user` 已具备本期所需列,**无需新增 DDL**:
+
+| 列 | 类型 | 用途 |
+| --- | --- | --- |
+| `referrer_id` | bigint | 上级(推荐人)会员ID,自连接 `account_user.user_id`;已有索引 `idx_account_user_referrer` |
+| `referrer_bind_time` | datetime | 绑定时间,列表/详情「绑定日」来源 |
+| `my_referral_code` | varchar(32) | 本人邀请码,唯一索引 `uk_account_user_referral_code` |
+| `system_level` | char(1) | 等级原值(C / L1~L4),等级徽标来源 |
+| `buy_amount_total` | decimal(16,2) | 购买金额合计(单位:元,现恒为 0,待订单模块回填) |
+
+### 2.2 「合计消费」复用预留列 `buy_amount_total`(已确认)
+
+无订单表,暂用 `account_user.buy_amount_total`(单位:元,与列定义一致,前端加 `¥` 前缀展示)。当前恒为 0,待订单模块上线后回填,**前端无需改动**。
+
+### 2.3 等级徽标直接展示 `system_level` 原值(已确认)
+
+`MemberLevelEnum` 仅到 L4,原型出现 L6(设计稿示意)。本期**不做 normalize**,将 `system_level` 原样下发(C / L1~L4 / 其它历史值),避免把非法值改写。前端空值兜底为 `C`。
+
+### 2.4 小程序码由 ruoyi-auth 生成(已确认「真实小程序码」)
+
+`wechat.miniapp` 配置与 `WeChatMiniappService`(access_token 获取、缓存)**均在 ruoyi-auth**;ruoyi-user 无 wechat 配置、无 `ruoyi-api-system` 依赖。为避免重复实现 access_token 逻辑(DRY),**小程序码生成放在 ruoyi-auth**,新增 C 端接口,入参邀请码、返回 base64 图片。
+
+### 2.5 扫码进入注册页需补 `scene` 解析(关键缺口)
+
+`pages/login/register.vue` **当前无 `onLoad`**(仅 `onUnload`),`form.referralCode` 仅支持手动输入。小程序码扫码后微信会把 `scene` 放入页面 `options`,需补 `onLoad(options)` 解码回填,否则「扫码自动填邀请码」不成立。
+
+### 2.6 邀请码恒定存在,上级可能为空
+
+`my_referral_code` 在注册时生成(`generateUniqueReferralCode`),C 端会员必有,故「邀请码」Tab 无须空态;而 `referrer_id` 可为空(命中通用推荐码注册、或历史数据),故「邀请我的」Tab 必须有空态。
+
+### 2.7 路径规划
+
+- 网关注入 `user_id`;`SecurityUtils.getUserId()` 取会员身份,**C 端接口不接受前端传 userId**(防水平越权)。
+- 会员侧三条接口挂在既有 `AccountProfileController`(`@RequestMapping("/user")`,外部 `/miniapp/account/user/**`),网关路由已存在,**无需改网关**。
+- 小程序码接口挂 ruoyi-auth 的 `AccountAuthController`(外部 `/miniapp/auth/invite-qrcode`),**不在网关白名单**,需登录态。
+
+### 2.8 手机号脱敏
+
+列表/详情中的手机号一律脱敏下发(复用 `AccountUserServiceImpl.maskPhone`,形如 `136****6666`),**不下发明文**。
+
+---
+
+## 三、接口设计
+
+统一响应体:C 端 `R<T>`(成功 `code=200`)。
+
+### 3.1 查询本人邀请码(本次新增 · ruoyi-user)
+
+- **外部路径**:`GET /miniapp/account/user/invite-code`(网关 `StripPrefix=2` → 服务内 `GET /user/invite-code`)
+- **鉴权**:需登录;会员身份取自登录态,不接受前端传 userId。
+- **入参**:无。
+- **出参** `InviteCodeVo`:
+
+| 字段 | 类型 | 说明 |
+| --- | --- | --- |
+| referralCode | String | 本人邀请码(`account_user.my_referral_code`) |
+
+### 3.2 生成微信小程序码(本次新增 · ruoyi-auth)
+
+- **外部路径**:`GET /miniapp/auth/invite-qrcode?code={邀请码}`(服务内 `GET /invite-qrcode`)
+- **鉴权**:需登录(不在网关白名单)。
+- **入参**:
+
+| 参数 | 类型 | 必填 | 校验 | 说明 |
+| --- | --- | --- | --- | --- |
+| code | String | 是 | `@NotBlank` + `@Pattern(^[A-Z0-9]{8}$)` | 邀请码,作为小程序码 `scene` |
+
+- **出参** `InviteQrcodeVo`:
+
+| 字段 | 类型 | 说明 |
+| --- | --- | --- |
+| qrcode | String | 小程序码 PNG 的 base64 DataURI(`data:image/png;base64,...`),前端直接作为 `<image src>` |
+
+- **缓存**:以邀请码为 key 缓存生成结果(Redis,TTL 30 天),避免重复消耗微信 `getUnlimited` 日配额。
+- **错误提示**:「邀请码格式不正确」/「邀请码二维码生成失败,请重试」。
+
+### 3.3 查询我邀请的(本次新增 · ruoyi-user)
+
+- **外部路径**:`GET /miniapp/account/user/invitees`(→ 服务内 `GET /user/invitees`)
+- **鉴权**:需登录;会员身份取自登录态。
+- **入参**:`pageNum`(默认 1)、`pageSize`(默认 10,与 `TableSupport` 默认一致)— 标准分页。
+- **查询口径**:`account_user` 中 `referrer_id = 当前用户ID` 且 `del_flag='0'`,**按 `referrer_bind_time` 倒序**(最新在最前;`referrer_bind_time` 为空的历史行排最后)。
+- **出参**:走统一分页 `TableDataInfo`,即 `{ code, msg, rows, total }`(**数据在顶层 `rows`,非 `data` 包裹**;`total` 为总数)。`rows` 元素 `AccountInviteeVo`:
+
+| 字段 | 类型 | 说明 |
+| --- | --- | --- |
+| userId | Long | 下级会员ID |
+| nickname | String | 昵称(为空前端回退脱敏手机号) |
+| avatar | String | 头像地址(为空前端回退默认图标) |
+| phone | String | 脱敏手机号(如 `136****6666`) |
+| systemLevel | String | 等级原值(C / L1~L4) |
+| bindDate | String | 绑定日(`yyyy-MM-dd`,源自 `referrer_bind_time`;为空不展示) |
+| buyAmountTotal | BigDecimal | 合计消费(元,源自 `buy_amount_total`) |
+
+### 3.4 查询邀请我的(上级信息)(本次新增 · ruoyi-user)
+
+- **外部路径**:`GET /miniapp/account/user/referrer`(→ 服务内 `GET /user/referrer`)
+- **鉴权**:需登录;会员身份取自登录态。
+- **入参**:无。
+- **查询口径**:取本人 `referrer_id` → 查该会员(`del_flag='0'`)。
+- **出参** `AccountReferrerVo`(无上级时各字段为 null,前端渲染空态):
+
+| 字段 | 类型 | 说明 |
+| --- | --- | --- |
+| userId | Long | 上级会员ID |
+| nickname | String | 昵称 |
+| avatar | String | 头像地址 |
+| phone | String | 脱敏手机号 |
+| systemLevel | String | 等级原值(C / L1~L4) |
+| bindDate | String | 绑定日(`yyyy-MM-dd`) |
+
+---
+
+## 四、数据库设计
+
+**本期无 DDL 变更**,仅复用 `account_user` 既有列(见 2.1)。查询 SQL 遵循规范:
+
+- 「我邀请的」按 `referrer_id` 过滤 + `referrer_bind_time` 排序,命中 `idx_account_user_referrer`;**不挂 join**(避免干扰 PageHelper 的 count SQL)。
+- 「邀请我的」按 `user_id` 主键查(复用 `selectAccountUserById`),不额外建方法。
+- 禁止 `SELECT *`:新增查询显式列出所需列。
+
+---
+
+## 五、微信小程序码设计(ruoyi-auth)
+
+### 5.1 复用与新增
+
+- 复用 `WeChatMiniappService.getAccessToken`(access_token 获取 + Redis 缓存 7000s)。当前为 `private`,在**同类内新增小程序码方法**即可调用,无需改可见性。
+- 当前 `doPost` 用 `BodyHandlers.ofString()` 仅适配 JSON;小程序码返回**二进制图片**,新增一个 `doPostForBytes`(`BodyHandlers.ofByteArray()`)。
+
+### 5.2 调用参数(`wxacode.getUnlimited`)
+
+```
+POST https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN
+Body: {
+  "scene": "{邀请码}",              // 8 位 A-Z0-9,符合 scene 字符集与长度限制
+  "page": "pages/login/register",   // 扫码直达注册页
+  "check_path": false,              // 跳过页面存在性校验(体验版/开发版可扫码)
+  "env_version": "release",         // 正式版;体验/开发调试可改 trial/develop
+  "width": 430
+}
+```
+
+### 5.3 响应处理与缓存
+
+- 微信成功返回 PNG 字节流,失败返回 JSON(含 `errcode`/`errmsg`)。先按 `Content-Type` 判断:非图片 → 解析 JSON 记日志并抛业务异常。
+- 成功后将 PNG 转 base64 DataURI,按 `scene` 缓存到 Redis(TTL 30 天),命中缓存直接返回。
+- **运维注意**:`page` 需为已发布页面;正式环境扫码前,小程序需先发布含 `pages/login/register` 的版本。
+
+---
+
+## 六、前端页面设计(`pages/profile/invite.vue`)
+
+### 6.1 视觉令牌(对齐 `琥歌瑞0928.pen`)
+
+| 用途 | 取值 |
+| --- | --- |
+| 页面底色 | `#F8FAFC`(pen 根底色) |
+| 主色(草绿) | `#10B981`(pen 强调色);按钮渐变 `#34D399 → #10B981` |
+| 等级/状态徽标 | 实心绿胶囊:底 `#10B981`、字 `#FFFFFF`,圆角 6rpx(原型 L1/L6 徽标) |
+| 卡片 | 白底 `#FFFFFF`,圆角 16rpx,描边 `#E2E8F0`,阴影 `0 6rpx 16rpx rgba(15,23,42,0.06)` |
+| 主文本 / 次文本 / 弱文本 | `#0F172A` / `#64748B` / `#94A3B8` |
+| 导航 | 自定义,返回箭头黑色 `#0F172A`,标题居中 `#0F172A` 34rpx 加粗 |
+| Tab | 高亮 `#10B981` + 底部 4rpx 绿色指示条;未选中 `#64748B` |
+
+> 说明:个人中心页等级胶囊为金色渐变,本页**按原型用绿色胶囊**(更贴合 pen 的 `#10B981 / #ECFDF5` 状态徽标体系)。
+
+### 6.2 三 Tab 结构
+
+- **邀请码**:居中白色卡片,内为小程序码 `<image>`(base64);下方文案「我的邀请码」+ 邀请码原值(可点击复制);底部整宽绿色胶囊按钮「分享邀请码」(`open-type="share"`)。
+- **我邀请的**:列表,每行左为头像 + (昵称 + 等级徽标)+ 脱敏手机号;右侧为「合计消费:¥xxx」与「绑定日:xxxx-xx-xx」;按绑定时间倒序;空态「还没有邀请好友」。
+- **邀请我的**:居中大号头像 + 昵称 +(等级徽标)+ 脱敏手机号 +「绑定日:xxxx-xx-xx」;无上级时空态「暂无上级信息」。
+
+### 6.3 分享与扫码
+
+- 「分享邀请码」:`onShareAppMessage` 返回 `{ title, path: '/pages/login/register?referralCode={邀请码}', imageUrl: {小程序码} }`,分享小程序注册页。
+- 扫码进入:`wxacode.getUnlimited` 的 `scene` 落到 `pages/login/register` 的 `options.scene`,由 `register.vue` 的 `onLoad` 解码回填。
+
+---
+
+## 七、前端改动清单
+
+| 文件 | 改动 |
+| --- | --- |
+| `pages/profile/invite.vue` | **新增**:我的邀请页(三 Tab + 分享 + 空态) |
+| `pages.json` | **新增**页面注册 `pages/profile/invite`(`navigationStyle: "custom"`) |
+| `pages/profile/index.vue` | `handleMenuTap` 中 `invite` 分支由「敬请期待」改为 `uni.navigateTo({ url: '/pages/profile/invite' })` |
+| `api/index.js` | **新增** `getInviteCode` / `getInviteQrcode(code)` / `getInvitees(params)` / `getReferrer` |
+| `pages/login/register.vue` | **新增** `onLoad(options)`:读 `options.referralCode`(分享链接)与 `options.scene`(扫码,需 `decodeURIComponent`)回填 `form.referralCode` |
+
+---
+
+## 八、影响范围与测试要点
+
+- **影响面**:新增页面与接口,不改动既有表结构与既有接口出参;个人中心仅改一处菜单跳转。
+- **测试要点**:
+  1. 邀请码 Tab:邀请码正确展示;小程序码可正常显示;分享卡片可拉起且 path 带推荐码;
+  2. 我邀请的:按绑定时间倒序;脱敏手机号;合计消费展示(当前为 0);空态;
+  3. 邀请我的:有上级/无上级两种状态;
+  4. 扫码链路:扫描本人小程序码 → 进入注册页且推荐码已自动填入;手动输入仍可用;
+  5. 边界:`referrer_bind_time` 为空的历史行排序不报错;未登录访问由网关 401 拦截。
+
+---
+
+## 九、待确认项
+
+1. 小程序码生成落在 **ruoyi-auth**(复用现成 WeChat 能力),会员侧接口落在 ruoyi-user;如希望统一前缀在 `/miniapp/account/**`,需在 ruoyi-user 补 wechat 配置与 WeChat 客户端(会引入重复实现),请确认取舍。
+2. 「我邀请的」是否**需要分页**(原型仅 4 条示意);本文默认分页(pageNum/pageSize)。
+3. 小程序码 `env_version` / `page` 取值(默认 `release` + `pages/login/register`)。
diff --git a/doc/nacos/dev/ruoyi-file-dev.yml b/doc/nacos/dev/ruoyi-file-dev.yml
new file mode 100644
index 0000000..518e0ec
--- /dev/null
+++ b/doc/nacos/dev/ruoyi-file-dev.yml
@@ -0,0 +1,34 @@
+# spring配置
+spring:
+  data:
+    redis:
+      host: localhost
+      port: 6379
+      password: <脱敏归档,真实值见配置中心>
+
+# 本地文件上传
+file:
+    domain: http://127.0.0.1:9300
+    path: D:/ruoyi/uploadPath
+    prefix: /statics
+
+# FastDFS配置
+fdfs:
+  domain: http://127.0.0.1
+  soTimeout: 3000
+  connectTimeout: 2000
+  trackerList: 127.0.0.1:22122
+
+# Minio配置
+minio:
+  url: http://127.0.0.1:9000
+  accessKey: minioadmin
+  secretKey: minioadmin
+  bucketName: test
+
+  # 防盗链配置
+referer:
+  # 防盗链开关
+  enabled: false
+  # 允许的域名列表
+  allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip
diff --git a/hugray-admin/src/utils/member.js b/hugray-admin/src/utils/member.js
index cf19355..42c6566 100644
--- a/hugray-admin/src/utils/member.js
+++ b/hugray-admin/src/utils/member.js
@@ -14,6 +14,12 @@
   { value: '3', label: '企业' }
 ]
 
+// 有效期类型(营业执照有效期与法人身份证有效期共用一套取值)
+export const VALID_TYPES = [
+  { value: '1', label: '长期' },
+  { value: '2', label: '短期' }
+]
+
 // 认证状态
 export const AUTH_STATUS = [
   { value: '0', label: '未认证' },
diff --git a/hugray-admin/src/views/member/certification/detail.vue b/hugray-admin/src/views/member/certification/detail.vue
index 7a4ae23..4e6afb4 100644
--- a/hugray-admin/src/views/member/certification/detail.vue
+++ b/hugray-admin/src/views/member/certification/detail.vue
@@ -53,17 +53,48 @@
       <el-descriptions :column="4" border>
         <el-descriptions-item label="用户名称">{{ apply.realName || '-' }}</el-descriptions-item>
         <el-descriptions-item label="用户身份证号" :span="3">{{ apply.idCard || '-' }}</el-descriptions-item>
+
+        <!-- 企业信息 -->
+        <el-descriptions-item label="主体类型">{{ labelOf(SUBJECT_TYPES, apply.subjectType) }}</el-descriptions-item>
         <el-descriptions-item label="企业名称">{{ apply.companyName || '-' }}</el-descriptions-item>
         <el-descriptions-item label="信用代码">{{ apply.creditCode || '-' }}</el-descriptions-item>
         <el-descriptions-item label="企业法人">{{ apply.legalPerson || '-' }}</el-descriptions-item>
         <el-descriptions-item label="企业注册地区">{{ apply.registeredRegion || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="企业地址" :span="4">{{ apply.companyAddress || '-' }}</el-descriptions-item>
         <el-descriptions-item label="注册日期">{{ apply.registerDate || '-' }}</el-descriptions-item>
-        <el-descriptions-item label="营业执照有效期间" :span="3">{{ apply.licenseValidPeriod || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="营业执照有效期类型">{{ labelOf(VALID_TYPES, apply.licenseValidType) }}</el-descriptions-item>
+        <el-descriptions-item label="营业执照有效期起止">{{ apply.licenseValidPeriod || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="企业地址" :span="4">{{ apply.companyAddress || '-' }}</el-descriptions-item>
         <el-descriptions-item label="营业执照" :span="4">
           <!-- ImagePreview 是全局组件:内部按 VITE_APP_BASE_API 拼接 /profile 相对路径,
                并自带预览与破图占位,不必在这里手拼地址 -->
           <image-preview v-if="apply.licenseImage" :src="apply.licenseImage" width="120" height="120" />
+          <span v-else>-</span>
+        </el-descriptions-item>
+
+        <!-- 法人信息(营业执照有效期类型/起止已在企业信息段落展示) -->
+        <el-descriptions-item label="法人身份证有效期类型">{{ labelOf(VALID_TYPES, apply.legalIdValidType) }}</el-descriptions-item>
+        <el-descriptions-item label="法人身份证有效期起止">{{ apply.legalIdValidPeriod || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="法人身份证详细地址" :span="2">{{ apply.legalIdAddress || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="法人身份证人像面" :span="2">
+          <image-preview v-if="apply.legalIdCardFront" :src="apply.legalIdCardFront" width="120" height="120" />
+          <span v-else>-</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="法人身份证国徽面" :span="2">
+          <image-preview v-if="apply.legalIdCardBack" :src="apply.legalIdCardBack" width="120" height="120" />
+          <span v-else>-</span>
+        </el-descriptions-item>
+
+        <!-- 负责人信息 -->
+        <el-descriptions-item label="负责人姓名">{{ apply.contactName || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="负责人电话">{{ apply.contactPhone || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="负责人邮箱" :span="2">{{ apply.contactEmail || '-' }}</el-descriptions-item>
+
+        <!-- 收款信息 -->
+        <el-descriptions-item label="收款类型">{{ apply.payeeType || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="银行卡号">{{ apply.bankCardNo || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="开户城市" :span="2">{{ apply.bankCity || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="银行卡照片" :span="4">
+          <image-preview v-if="apply.bankCardImage" :src="apply.bankCardImage" width="120" height="120" />
           <span v-else>-</span>
         </el-descriptions-item>
       </el-descriptions>
@@ -106,6 +137,7 @@
 import { getCertificationApplyDetail, auditCertificationApply } from '@/api/user/certification'
 import {
   SUBJECT_TYPES,
+  VALID_TYPES,
   AUTH_STATUS,
   AUDIT_STATUS,
   ACCOUNT_STATUS,
diff --git a/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/AccountAuthController.java b/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/AccountAuthController.java
index 468e1e7..560ad9a 100644
--- a/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/AccountAuthController.java
+++ b/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/AccountAuthController.java
@@ -2,15 +2,19 @@
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.auth.domain.vo.InviteQrcodeVo;
 import com.ruoyi.auth.form.AccountRegisterBody;
+import com.ruoyi.auth.form.InviteQrcodeQuery;
 import com.ruoyi.auth.form.PhoneCodeLoginBody;
 import com.ruoyi.auth.form.PhoneLoginBody;
 import com.ruoyi.auth.form.SmsCodeBody;
 import com.ruoyi.auth.service.AccountLoginService;
 import com.ruoyi.auth.service.SmsCodeService;
+import com.ruoyi.auth.service.WeChatMiniappService;
 import com.ruoyi.common.core.domain.R;
 
 /**
@@ -28,6 +32,9 @@
 
     @Autowired
     private AccountLoginService accountLoginService;
+
+    @Autowired
+    private WeChatMiniappService weChatMiniappService;
 
     /**
      * 下发短信验证码
@@ -66,4 +73,20 @@
         accountLoginService.accountRegister(body.getPhone(), body.getCode(), body.getReferralCode());
         return R.ok();
     }
+
+    /**
+     * 生成邀请码小程序码
+     *
+     * 以邀请码作为 scene 调用微信 getUnlimited 接口,扫码直达注册页并自动填入邀请码。
+     *
+     * @param query 邀请码入参(code)
+     * @return 小程序码 base64 DataURI
+     */
+    @GetMapping("/invite-qrcode")
+    public R<InviteQrcodeVo> inviteQrcode(@Validated InviteQrcodeQuery query)
+    {
+        InviteQrcodeVo vo = new InviteQrcodeVo();
+        vo.setQrcode(weChatMiniappService.getUnlimitedQrcode(query.getCode()));
+        return R.ok(vo);
+    }
 }
diff --git a/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java b/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java
index 407d131..f5d14b0 100644
--- a/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java
+++ b/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/controller/TokenController.java
@@ -41,9 +41,31 @@
         return R.ok(tokenService.createToken(userInfo));
     }
 
+    /**
+     * 登出(后台管理端使用 DELETE)
+     */
     @DeleteMapping("logout")
     public R<?> logout(HttpServletRequest request)
     {
+        return doLogout(request);
+    }
+
+    /**
+     * 登出(小程序端使用 POST,网关 /miniapp/auth/logout 剥前缀后落到本路径)
+     * 与 DELETE 版本共用同一套登出逻辑,仅方法不同
+     */
+    @PostMapping("logout")
+    public R<?> miniappLogout(HttpServletRequest request)
+    {
+        return doLogout(request);
+    }
+
+    /**
+     * 登出公共逻辑:删除 Redis 登录态缓存并记录退出日志
+     * @param request 当前请求(用于从请求头提取 token)
+     */
+    private R<?> doLogout(HttpServletRequest request)
+    {
         String token = SecurityUtils.getToken(request);
         if (StringUtils.isNotEmpty(token))
         {
diff --git a/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/domain/vo/InviteQrcodeVo.java b/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/domain/vo/InviteQrcodeVo.java
new file mode 100644
index 0000000..5dc28d6
--- /dev/null
+++ b/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/domain/vo/InviteQrcodeVo.java
@@ -0,0 +1,22 @@
+package com.ruoyi.auth.domain.vo;
+
+/**
+ * 邀请码小程序码展示对象
+ *
+ * @author hugray
+ */
+public class InviteQrcodeVo
+{
+    /** 小程序码 PNG 的 base64 DataURI(data:image/png;base64,...),前端直接作为 image src */
+    private String qrcode;
+
+    public String getQrcode()
+    {
+        return qrcode;
+    }
+
+    public void setQrcode(String qrcode)
+    {
+        this.qrcode = qrcode;
+    }
+}
diff --git a/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/form/InviteQrcodeQuery.java b/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/form/InviteQrcodeQuery.java
new file mode 100644
index 0000000..99535af
--- /dev/null
+++ b/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/form/InviteQrcodeQuery.java
@@ -0,0 +1,27 @@
+package com.ruoyi.auth.form;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+
+/**
+ * 邀请码小程序码查询入参
+ *
+ * @author hugray
+ */
+public class InviteQrcodeQuery
+{
+    /** 邀请码,作为小程序码 scene(8 位 A-Z0-9) */
+    @NotBlank(message = "请输入邀请码")
+    @Pattern(regexp = "^[A-Z0-9]{8}$", message = "邀请码格式不正确")
+    private String code;
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public void setCode(String code)
+    {
+        this.code = code;
+    }
+}
diff --git a/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/service/WeChatMiniappService.java b/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/service/WeChatMiniappService.java
index 6e84452..43de14e 100644
--- a/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/service/WeChatMiniappService.java
+++ b/hugray-server/ruoyi-auth/src/main/java/com/ruoyi/auth/service/WeChatMiniappService.java
@@ -4,7 +4,9 @@
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
 import java.time.Duration;
+import java.util.Base64;
 import java.util.concurrent.TimeUnit;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -47,6 +49,21 @@
 
     /** 换取失败统一提示 */
     private static final String PHONE_FAIL_MESSAGE = "微信手机号获取失败,请重试";
+
+    /** 获取不限量小程序码路径 */
+    private static final String QRCODE_PATH = "/wxa/getwxacodeunlimit";
+
+    /** 小程序码扫码直达页面 */
+    private static final String QRCODE_PAGE = "pages/login/register";
+
+    /** 小程序码图片宽度(px) */
+    private static final int QRCODE_WIDTH = 430;
+
+    /** 小程序码缓存时长(秒),30 天,避免重复消耗微信 getUnlimited 日配额 */
+    private static final long QRCODE_CACHE_SECONDS = 30L * 24 * 60 * 60;
+
+    /** 小程序码生成失败统一提示 */
+    private static final String QRCODE_FAIL_MESSAGE = "邀请码二维码生成失败,请重试";
 
     private final HttpClient httpClient = HttpClient.newBuilder()
             .connectTimeout(Duration.ofSeconds(5))
@@ -91,6 +108,97 @@
             throw new ServiceException(PHONE_FAIL_MESSAGE);
         }
         return phoneNumber;
+    }
+
+    /**
+     * 生成不限量小程序码(wxacode.getUnlimited)
+     *
+     * 以邀请码作为 scene,扫码直达注册页并由前端自动回填邀请码。
+     * 结果按 scene 缓存 30 天,命中缓存直接返回,避免重复消耗微信接口日配额。
+     *
+     * @param scene 场景值,即邀请码(8 位 A-Z0-9)
+     * @return PNG 图片的 base64 DataURI(data:image/png;base64,...)
+     */
+    public String getUnlimitedQrcode(String scene)
+    {
+        // STEP 1: 命中缓存直接返回
+        String cacheKey = CacheConstants.WECHAT_QRCODE_KEY + scene;
+        String cached = redisService.getCacheObject(cacheKey);
+        if (StringUtils.isNotEmpty(cached))
+        {
+            return cached;
+        }
+
+        // STEP 2: 校验配置是否完整
+        String appid = weChatMiniappProperties.getAppid();
+        String secret = weChatMiniappProperties.getSecret();
+        if (StringUtils.isEmpty(appid) || StringUtils.isEmpty(secret))
+        {
+            log.error("[WECHAT] miniapp appid/secret 未配置");
+            throw new ServiceException(QRCODE_FAIL_MESSAGE);
+        }
+
+        // STEP 3: 获取全局 access_token
+        String accessToken = getAccessToken(appid, secret);
+
+        // STEP 4: 调用微信生成小程序码(scene 为邀请码,已由入参正则限定为 A-Z0-9,可安全拼接)
+        String url = WECHAT_API_HOST + QRCODE_PATH + "?access_token=" + accessToken;
+        String requestBody = "{\"scene\":\"" + scene + "\",\"page\":\"" + QRCODE_PAGE
+                + "\",\"check_path\":false,\"env_version\":\"release\",\"width\":" + QRCODE_WIDTH + "}";
+        HttpResponse<byte[]> response = doPostForBytes(url, requestBody);
+
+        // STEP 5: 按响应类型解析(图片转 base64,JSON 则说明失败)
+        String dataUri = parseQrcodeResponse(response, scene);
+
+        // STEP 6: 缓存生成结果并返回
+        redisService.setCacheObject(cacheKey, dataUri, QRCODE_CACHE_SECONDS, TimeUnit.SECONDS);
+        return dataUri;
+    }
+
+    /**
+     * 解析小程序码响应
+     *
+     * 微信成功返回 PNG 字节流(Content-Type: image/*),失败返回 JSON(含 errcode/errmsg)。
+     *
+     * @param response 微信响应
+     * @param scene 邀请码(仅用于日志)
+     * @return PNG 的 base64 DataURI
+     */
+    private String parseQrcodeResponse(HttpResponse<byte[]> response, String scene)
+    {
+        String contentType = response.headers().firstValue("Content-Type").orElse("");
+        byte[] body = response.body();
+
+        // 图片响应:PNG 二进制转 base64 DataURI
+        if (contentType.startsWith("image/"))
+        {
+            if (body == null || body.length == 0)
+            {
+                log.error("[WECHAT] getUnlimitedQrcode empty image, scene={}", scene);
+                throw new ServiceException(QRCODE_FAIL_MESSAGE);
+            }
+            return "data:image/png;base64," + Base64.getEncoder().encodeToString(body);
+        }
+
+        // 非图片响应:微信以 JSON 返回错误码,解析并记日志
+        Integer errcode = null;
+        String errmsg = null;
+        try
+        {
+            String text = body == null ? null : new String(body, StandardCharsets.UTF_8);
+            JSONObject json = JSON.parseObject(text);
+            if (StringUtils.isNotNull(json))
+            {
+                errcode = json.getInteger("errcode");
+                errmsg = json.getString("errmsg");
+            }
+        }
+        catch (Exception e)
+        {
+            log.error("[WECHAT] getUnlimitedQrcode parse error, scene={}", scene, e);
+        }
+        log.error("[WECHAT] getUnlimitedQrcode failed, scene={}, errcode={}, errmsg={}", scene, errcode, errmsg);
+        throw new ServiceException(QRCODE_FAIL_MESSAGE);
     }
 
     /**
@@ -177,4 +285,36 @@
             throw new ServiceException(PHONE_FAIL_MESSAGE);
         }
     }
+
+    /**
+     * 发送 HTTP POST 请求并按字节数组接收响应(用于小程序码等二进制接口)
+     *
+     * @param url 请求地址
+     * @param body 请求体 JSON,可为空
+     * @return 原始响应(含状态头与字节体)
+     */
+    private HttpResponse<byte[]> doPostForBytes(String url, String body)
+    {
+        try
+        {
+            HttpRequest.Builder builder = HttpRequest.newBuilder()
+                    .uri(URI.create(url))
+                    .timeout(Duration.ofSeconds(WECHAT_REQUEST_TIMEOUT_SECONDS))
+                    .header("Content-Type", "application/json");
+            if (StringUtils.isEmpty(body))
+            {
+                builder.POST(HttpRequest.BodyPublishers.noBody());
+            }
+            else
+            {
+                builder.POST(HttpRequest.BodyPublishers.ofString(body));
+            }
+            return httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofByteArray());
+        }
+        catch (Exception e)
+        {
+            log.error("[WECHAT] request failed, url={}", url, e);
+            throw new ServiceException(QRCODE_FAIL_MESSAGE);
+        }
+    }
 }
diff --git a/hugray-server/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java b/hugray-server/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
index 2d99829..1493d8d 100644
--- a/hugray-server/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
+++ b/hugray-server/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/constant/CacheConstants.java
@@ -81,4 +81,9 @@
      * 微信小程序全局 access_token redis key
      */
     public static final String WECHAT_ACCESS_TOKEN_KEY = "wechat_miniapp_token:";
+
+    /**
+     * 微信小程序码 redis key(按 scene 缓存生成的 base64 图片)
+     */
+    public static final String WECHAT_QRCODE_KEY = "wechat_miniapp_qrcode:";
 }
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/controller/AccountProfileController.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/controller/AccountProfileController.java
index c2f4ea8..72258a6 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/controller/AccountProfileController.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/controller/AccountProfileController.java
@@ -1,12 +1,27 @@
 package com.ruoyi.user.controller;
 
+import java.util.List;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import com.ruoyi.common.core.domain.R;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.page.TableDataInfo;
 import com.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.user.domain.dto.AccountCertificationApplyDto;
+import com.ruoyi.user.domain.dto.AccountUserAvatarDto;
+import com.ruoyi.user.domain.dto.CompanyCertificationApplyDto;
+import com.ruoyi.user.domain.vo.AccountCertificationStatusVo;
+import com.ruoyi.user.domain.vo.AccountInviteeVo;
+import com.ruoyi.user.domain.vo.AccountReferrerVo;
 import com.ruoyi.user.domain.vo.AccountUserInfoVo;
+import com.ruoyi.user.domain.vo.CompanyCertificationStatusVo;
+import com.ruoyi.user.domain.vo.InviteCodeVo;
+import com.ruoyi.user.service.IAccountCertificationApplyService;
 import com.ruoyi.user.service.IAccountUserService;
 
 /**
@@ -22,10 +37,13 @@
  */
 @RestController
 @RequestMapping("/user")
-public class AccountProfileController
+public class AccountProfileController extends BaseController
 {
     @Autowired
     private IAccountUserService accountUserService;
+
+    @Autowired
+    private IAccountCertificationApplyService accountCertificationApplyService;
 
     /**
      * 查询当前登录会员的个人中心信息
@@ -39,4 +57,128 @@
     {
         return R.ok(accountUserService.getAccountUserInfo(SecurityUtils.getUserId()));
     }
+
+    /**
+     * 更新当前登录会员头像
+     *
+     * 头像文件由小程序直传文件服务(POST /file/upload)后,将返回的 url 通过本接口写回;
+     * 会员身份取自登录态,不接受前端传入 userId。
+     *
+     * @param dto 头像更新入参(仅含 avatar)
+     * @return 操作结果
+     */
+    @PostMapping("/avatar")
+    public R<Void> updateAvatar(@Validated @RequestBody AccountUserAvatarDto dto)
+    {
+        accountUserService.updateAvatar(SecurityUtils.getUserId(), dto.getAvatar());
+        return R.ok();
+    }
+
+    /**
+     * 查询当前登录会员的实名认证状态
+     *
+     * 供实名认证页进入时回填:姓名、身份证号、审核状态(0待审核/1已通过/2已驳回)与驳回原因。
+     * 从未提交过申请时返回各字段为空的对象,前端按「未认证」渲染空表单。
+     * 会员身份取自登录态,不接受前端传入 userId。
+     *
+     * @return 认证状态(realName / idCard / auditStatus / auditRemark)
+     */
+    @GetMapping("/certification")
+    public R<AccountCertificationStatusVo> certification()
+    {
+        return R.ok(accountCertificationApplyService.getCertificationStatus(SecurityUtils.getUserId()));
+    }
+
+    /**
+     * 提交实名认证(个人主体)
+     *
+     * 只做基础的姓名必填与身份证号格式校验(@Validated 拦下非法入参),不做三方实名核验;
+     * 提交后落一条待审核的认证申请,出现在管理后台「主体认证审核」列表。
+     * 会员身份取自登录态,不接受前端传入 userId;已有待审核申请时拒绝重复提交。
+     *
+     * @param dto 认证申请入参(仅含 realName、idCard)
+     * @return 操作结果
+     */
+    @PostMapping("/certification")
+    public R<Void> submitCertification(@Validated @RequestBody AccountCertificationApplyDto dto)
+    {
+        accountCertificationApplyService.submitPersonalCertification(
+            SecurityUtils.getUserId(), dto.getRealName(), dto.getIdCard());
+        return R.ok();
+    }
+
+    /**
+     * 查询当前登录会员的企业/个体认证状态
+     *
+     * 供企业认证向导页进入时回填:整份企业资料 + 审核状态(0待审核/1已通过/2已驳回)与驳回原因。
+     * 从未提交过申请时返回各字段为空的对象,前端按「未认证」渲染空表单。
+     * 会员身份取自登录态,不接受前端传入 userId。
+     *
+     * @return 企业/个体认证状态(整份资料 + auditStatus / auditRemark)
+     */
+    @GetMapping("/company-certification")
+    public R<CompanyCertificationStatusVo> companyCertification()
+    {
+        return R.ok(accountCertificationApplyService.getCompanyCertificationStatus(SecurityUtils.getUserId()));
+    }
+
+    /**
+     * 提交企业/个体工商户认证(主体类型 2/3)
+     *
+     * 只做最基础的必填校验(@Validated 拦下空值与非法枚举),不做格式与三方核验,真实性由后台人工审核把关;
+     * 提交后落一条待审核的认证申请,出现在管理后台「主体认证审核」列表。
+     * 会员身份取自登录态,不接受前端传入 userId;已有待审核的企业申请时拒绝重复提交。
+     *
+     * @param dto 企业认证申请入参
+     * @return 操作结果
+     */
+    @PostMapping("/company-certification")
+    public R<Void> submitCompanyCertification(@Validated @RequestBody CompanyCertificationApplyDto dto)
+    {
+        accountCertificationApplyService.submitCompanyCertification(SecurityUtils.getUserId(), dto);
+        return R.ok();
+    }
+
+    /**
+     * 查询本人邀请码
+     *
+     * 供「我的邀请」页「邀请码」Tab 展示;会员身份取自登录态,不接受前端传入 userId。
+     *
+     * @return 邀请码展示对象
+     */
+    @GetMapping("/invite-code")
+    public R<InviteCodeVo> inviteCode()
+    {
+        return R.ok(accountUserService.getInviteCode(SecurityUtils.getUserId()));
+    }
+
+    /**
+     * 查询本人直接下级(被邀请人)列表
+     *
+     * 分页返回(pageNum 默认 1、pageSize 默认 10),按绑定时间倒序;
+     * 会员身份取自登录态,不接受前端传入 userId。
+     *
+     * @return 分页数据(rows 为 AccountInviteeVo 列表,total 为总数)
+     */
+    @GetMapping("/invitees")
+    public TableDataInfo invitees()
+    {
+        startPage();
+        List<AccountInviteeVo> list = accountUserService.getInvitees(SecurityUtils.getUserId());
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询本人上级(推荐人)信息
+     *
+     * 供「我的邀请」页「邀请我的」Tab 展示;无上级时返回各字段为 null 的对象;
+     * 会员身份取自登录态,不接受前端传入 userId。
+     *
+     * @return 上级展示对象
+     */
+    @GetMapping("/referrer")
+    public R<AccountReferrerVo> referrer()
+    {
+        return R.ok(accountUserService.getReferrer(SecurityUtils.getUserId()));
+    }
 }
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountCertification.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountCertification.java
index d999530..1e53002 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountCertification.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountCertification.java
@@ -52,6 +52,45 @@
     /** 营业执照图片地址 */
     private String licenseImage;
 
+    /** 营业执照有效期类型(1长期 2短期;长期时 licenseValidPeriod 为空) */
+    private String licenseValidType;
+
+    /** 法人身份证人像面图片 */
+    private String legalIdCardFront;
+
+    /** 法人身份证国徽面图片 */
+    private String legalIdCardBack;
+
+    /** 法人身份证有效期类型(1长期 2短期) */
+    private String legalIdValidType;
+
+    /** 法人身份证有效期(存文本,如 2024-01-01~2034-01-01) */
+    private String legalIdValidPeriod;
+
+    /** 法人身份证详细地址 */
+    private String legalIdAddress;
+
+    /** 负责人姓名 */
+    private String contactName;
+
+    /** 负责人联系电话 */
+    private String contactPhone;
+
+    /** 负责人邮箱 */
+    private String contactEmail;
+
+    /** 收款类型 */
+    private String payeeType;
+
+    /** 银行卡号码面照片 */
+    private String bankCardImage;
+
+    /** 银行卡号 */
+    private String bankCardNo;
+
+    /** 开户城市 */
+    private String bankCity;
+
     /** 删除标志(0存在 2删除) */
     private String delFlag;
 
@@ -175,6 +214,136 @@
         this.licenseImage = licenseImage;
     }
 
+    public String getLicenseValidType()
+    {
+        return licenseValidType;
+    }
+
+    public void setLicenseValidType(String licenseValidType)
+    {
+        this.licenseValidType = licenseValidType;
+    }
+
+    public String getLegalIdCardFront()
+    {
+        return legalIdCardFront;
+    }
+
+    public void setLegalIdCardFront(String legalIdCardFront)
+    {
+        this.legalIdCardFront = legalIdCardFront;
+    }
+
+    public String getLegalIdCardBack()
+    {
+        return legalIdCardBack;
+    }
+
+    public void setLegalIdCardBack(String legalIdCardBack)
+    {
+        this.legalIdCardBack = legalIdCardBack;
+    }
+
+    public String getLegalIdValidType()
+    {
+        return legalIdValidType;
+    }
+
+    public void setLegalIdValidType(String legalIdValidType)
+    {
+        this.legalIdValidType = legalIdValidType;
+    }
+
+    public String getLegalIdValidPeriod()
+    {
+        return legalIdValidPeriod;
+    }
+
+    public void setLegalIdValidPeriod(String legalIdValidPeriod)
+    {
+        this.legalIdValidPeriod = legalIdValidPeriod;
+    }
+
+    public String getLegalIdAddress()
+    {
+        return legalIdAddress;
+    }
+
+    public void setLegalIdAddress(String legalIdAddress)
+    {
+        this.legalIdAddress = legalIdAddress;
+    }
+
+    public String getContactName()
+    {
+        return contactName;
+    }
+
+    public void setContactName(String contactName)
+    {
+        this.contactName = contactName;
+    }
+
+    public String getContactPhone()
+    {
+        return contactPhone;
+    }
+
+    public void setContactPhone(String contactPhone)
+    {
+        this.contactPhone = contactPhone;
+    }
+
+    public String getContactEmail()
+    {
+        return contactEmail;
+    }
+
+    public void setContactEmail(String contactEmail)
+    {
+        this.contactEmail = contactEmail;
+    }
+
+    public String getPayeeType()
+    {
+        return payeeType;
+    }
+
+    public void setPayeeType(String payeeType)
+    {
+        this.payeeType = payeeType;
+    }
+
+    public String getBankCardImage()
+    {
+        return bankCardImage;
+    }
+
+    public void setBankCardImage(String bankCardImage)
+    {
+        this.bankCardImage = bankCardImage;
+    }
+
+    public String getBankCardNo()
+    {
+        return bankCardNo;
+    }
+
+    public void setBankCardNo(String bankCardNo)
+    {
+        this.bankCardNo = bankCardNo;
+    }
+
+    public String getBankCity()
+    {
+        return bankCity;
+    }
+
+    public void setBankCity(String bankCity)
+    {
+        this.bankCity = bankCity;
+    }
+
     public String getDelFlag()
     {
         return delFlag;
@@ -200,6 +369,19 @@
             .append("registerDate", getRegisterDate())
             .append("licenseValidPeriod", getLicenseValidPeriod())
             .append("licenseImage", getLicenseImage())
+            .append("licenseValidType", getLicenseValidType())
+            .append("legalIdCardFront", getLegalIdCardFront())
+            .append("legalIdCardBack", getLegalIdCardBack())
+            .append("legalIdValidType", getLegalIdValidType())
+            .append("legalIdValidPeriod", getLegalIdValidPeriod())
+            .append("legalIdAddress", getLegalIdAddress())
+            .append("contactName", getContactName())
+            .append("contactPhone", getContactPhone())
+            .append("contactEmail", getContactEmail())
+            .append("payeeType", getPayeeType())
+            .append("bankCardImage", getBankCardImage())
+            .append("bankCardNo", getBankCardNo())
+            .append("bankCity", getBankCity())
             .append("delFlag", getDelFlag())
             .append("createBy", getCreateBy())
             .append("createTime", getCreateTime())
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountCertificationApply.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountCertificationApply.java
index fa35531..bc8b06c 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountCertificationApply.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountCertificationApply.java
@@ -56,11 +56,50 @@
     @JsonFormat(pattern = "yyyy-MM-dd")
     private Date registerDate;
 
-    /** 营业执照有效期间 */
+    /** 营业执照有效期间(存文本,如 2024-01-01~2034-01-01) */
     private String licenseValidPeriod;
 
     /** 营业执照图片 */
     private String licenseImage;
+
+    /** 营业执照有效期类型(1长期 2短期;长期时 licenseValidPeriod 为空) */
+    private String licenseValidType;
+
+    /** 法人身份证人像面图片 */
+    private String legalIdCardFront;
+
+    /** 法人身份证国徽面图片 */
+    private String legalIdCardBack;
+
+    /** 法人身份证有效期类型(1长期 2短期) */
+    private String legalIdValidType;
+
+    /** 法人身份证有效期(存文本,如 2024-01-01~2034-01-01) */
+    private String legalIdValidPeriod;
+
+    /** 法人身份证详细地址 */
+    private String legalIdAddress;
+
+    /** 负责人姓名 */
+    private String contactName;
+
+    /** 负责人联系电话 */
+    private String contactPhone;
+
+    /** 负责人邮箱 */
+    private String contactEmail;
+
+    /** 收款类型 */
+    private String payeeType;
+
+    /** 银行卡号码面照片 */
+    private String bankCardImage;
+
+    /** 银行卡号 */
+    private String bankCardNo;
+
+    /** 开户城市 */
+    private String bankCity;
 
     /** 审核状态(0待审核 1已通过 2已驳回) */
     private String auditStatus;
@@ -208,6 +247,136 @@
         this.licenseImage = licenseImage;
     }
 
+    public String getLicenseValidType()
+    {
+        return licenseValidType;
+    }
+
+    public void setLicenseValidType(String licenseValidType)
+    {
+        this.licenseValidType = licenseValidType;
+    }
+
+    public String getLegalIdCardFront()
+    {
+        return legalIdCardFront;
+    }
+
+    public void setLegalIdCardFront(String legalIdCardFront)
+    {
+        this.legalIdCardFront = legalIdCardFront;
+    }
+
+    public String getLegalIdCardBack()
+    {
+        return legalIdCardBack;
+    }
+
+    public void setLegalIdCardBack(String legalIdCardBack)
+    {
+        this.legalIdCardBack = legalIdCardBack;
+    }
+
+    public String getLegalIdValidType()
+    {
+        return legalIdValidType;
+    }
+
+    public void setLegalIdValidType(String legalIdValidType)
+    {
+        this.legalIdValidType = legalIdValidType;
+    }
+
+    public String getLegalIdValidPeriod()
+    {
+        return legalIdValidPeriod;
+    }
+
+    public void setLegalIdValidPeriod(String legalIdValidPeriod)
+    {
+        this.legalIdValidPeriod = legalIdValidPeriod;
+    }
+
+    public String getLegalIdAddress()
+    {
+        return legalIdAddress;
+    }
+
+    public void setLegalIdAddress(String legalIdAddress)
+    {
+        this.legalIdAddress = legalIdAddress;
+    }
+
+    public String getContactName()
+    {
+        return contactName;
+    }
+
+    public void setContactName(String contactName)
+    {
+        this.contactName = contactName;
+    }
+
+    public String getContactPhone()
+    {
+        return contactPhone;
+    }
+
+    public void setContactPhone(String contactPhone)
+    {
+        this.contactPhone = contactPhone;
+    }
+
+    public String getContactEmail()
+    {
+        return contactEmail;
+    }
+
+    public void setContactEmail(String contactEmail)
+    {
+        this.contactEmail = contactEmail;
+    }
+
+    public String getPayeeType()
+    {
+        return payeeType;
+    }
+
+    public void setPayeeType(String payeeType)
+    {
+        this.payeeType = payeeType;
+    }
+
+    public String getBankCardImage()
+    {
+        return bankCardImage;
+    }
+
+    public void setBankCardImage(String bankCardImage)
+    {
+        this.bankCardImage = bankCardImage;
+    }
+
+    public String getBankCardNo()
+    {
+        return bankCardNo;
+    }
+
+    public void setBankCardNo(String bankCardNo)
+    {
+        this.bankCardNo = bankCardNo;
+    }
+
+    public String getBankCity()
+    {
+        return bankCity;
+    }
+
+    public void setBankCity(String bankCity)
+    {
+        this.bankCity = bankCity;
+    }
+
     public String getAuditStatus()
     {
         return auditStatus;
@@ -274,6 +443,19 @@
             .append("registerDate", getRegisterDate())
             .append("licenseValidPeriod", getLicenseValidPeriod())
             .append("licenseImage", getLicenseImage())
+            .append("licenseValidType", getLicenseValidType())
+            .append("legalIdCardFront", getLegalIdCardFront())
+            .append("legalIdCardBack", getLegalIdCardBack())
+            .append("legalIdValidType", getLegalIdValidType())
+            .append("legalIdValidPeriod", getLegalIdValidPeriod())
+            .append("legalIdAddress", getLegalIdAddress())
+            .append("contactName", getContactName())
+            .append("contactPhone", getContactPhone())
+            .append("contactEmail", getContactEmail())
+            .append("payeeType", getPayeeType())
+            .append("bankCardImage", getBankCardImage())
+            .append("bankCardNo", getBankCardNo())
+            .append("bankCity", getBankCity())
             .append("auditStatus", getAuditStatus())
             .append("auditBy", getAuditBy())
             .append("auditTime", getAuditTime())
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountUser.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountUser.java
index 67c8676..274d76f 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountUser.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountUser.java
@@ -101,6 +101,16 @@
     @Excel(name = "认证状态", readConverterExp = "0=未认证,1=审核中,2=已认证,3=认证失败")
     private String authStatus;
 
+    /**
+     * 企业/个体认证状态(0未认证 1审核中 2已认证 3认证失败),与个人 authStatus 相互独立。
+     *
+     * 刻意不加 @Excel:避免会员列表导出与个人认证状态混淆。
+     * 也刻意不进 insertAccountUser / updateAccountUser 的动态列——那两个是共享入口,
+     * 挂上去等于请求体里带个 companyAuthStatus 就能自造认证状态,绕过审核。
+     * 该列只能由 updateUserCompanyAuthStatus 改写,唯一调用方是企业认证审核流转。
+     */
+    private String companyAuthStatus;
+
     /** 所属城市 */
     @Excel(name = "所属城市")
     private String city;
@@ -292,6 +302,16 @@
     public void setAuthStatus(String authStatus)
     {
         this.authStatus = authStatus;
+    }
+
+    public String getCompanyAuthStatus()
+    {
+        return companyAuthStatus;
+    }
+
+    public void setCompanyAuthStatus(String companyAuthStatus)
+    {
+        this.companyAuthStatus = companyAuthStatus;
     }
 
     public String getCity()
@@ -535,6 +555,7 @@
             .append("systemLevel", getSystemLevel())
             .append("subjectType", getSubjectType())
             .append("authStatus", getAuthStatus())
+            .append("companyAuthStatus", getCompanyAuthStatus())
             .append("city", getCity())
             .append("referrerId", getReferrerId())
             .append("referrerBindTime", getReferrerBindTime())
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/AccountCertificationApplyDto.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/AccountCertificationApplyDto.java
new file mode 100644
index 0000000..d5381e7
--- /dev/null
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/AccountCertificationApplyDto.java
@@ -0,0 +1,57 @@
+package com.ruoyi.user.domain.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+
+/**
+ * C端会员 主体认证申请提交入参(当前仅个人主体)
+ *
+ * 只承载「姓名」与「身份证号」两个字段:会员身份取自登录态(网关注入的 user_id 头),
+ * 不接受前端传入 userId,天然防水平越权;主体类型由服务端固定为「个人」,
+ * 不接受前端传入,避免越权提交企业主体绕开后续资料要求。
+ *
+ * 本品只做基础格式校验(姓名必填、身份证号结构校验),不做三方实名核验。
+ *
+ * @author hugray
+ */
+public class AccountCertificationApplyDto
+{
+    /** 姓名;必填,长度不超过 64(对应 account_certification_apply.real_name varchar(64)) */
+    @NotBlank(message = "姓名不能为空")
+    @Size(max = 64, message = "姓名过长")
+    private String realName;
+
+    /**
+     * 身份证号;必填,18 位。
+     *
+     * 正则只校验结构:6 位地址码 + 8 位出生日期(年份 18/19/20 开头,月 01~12,日 01~31)
+     * + 3 位顺序码 + 1 位校验位(数字或 X),不校验最后一位校验码的数学正确性,
+     * 也不做真实身份核验(后续由后台人工审核把关)。
+     */
+    @NotBlank(message = "身份证号不能为空")
+    @Size(max = 32, message = "身份证号过长")
+    @Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$",
+             message = "身份证号格式不正确")
+    private String idCard;
+
+    public String getRealName()
+    {
+        return realName;
+    }
+
+    public void setRealName(String realName)
+    {
+        this.realName = realName;
+    }
+
+    public String getIdCard()
+    {
+        return idCard;
+    }
+
+    public void setIdCard(String idCard)
+    {
+        this.idCard = idCard;
+    }
+}
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/AccountUserAvatarDto.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/AccountUserAvatarDto.java
new file mode 100644
index 0000000..62729ac
--- /dev/null
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/AccountUserAvatarDto.java
@@ -0,0 +1,32 @@
+package com.ruoyi.user.domain.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+/**
+ * C端会员 头像更新入参
+ *
+ * 仅承载「头像地址」一个字段:会员身份取自登录态(网关注入的 user_id 头),
+ * 不接受前端传入 userId,天然防水平越权。
+ *
+ * 头像文件由小程序直传文件服务(POST /file/upload)后,将返回的 url 通过本入参写回。
+ *
+ * @author hugray
+ */
+public class AccountUserAvatarDto
+{
+    /** 头像地址(来自文件服务上传结果的 url);必填,长度不超过 255(对应 account_user.avatar varchar(255)) */
+    @NotBlank(message = "头像地址不能为空")
+    @Size(max = 255, message = "头像地址过长")
+    private String avatar;
+
+    public String getAvatar()
+    {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar)
+    {
+        this.avatar = avatar;
+    }
+}
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/CompanyCertificationApplyDto.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/CompanyCertificationApplyDto.java
new file mode 100644
index 0000000..df1af91
--- /dev/null
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/dto/CompanyCertificationApplyDto.java
@@ -0,0 +1,334 @@
+package com.ruoyi.user.domain.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
+
+/**
+ * C端会员 企业/个体工商户认证申请提交入参
+ *
+ * 会员身份取自登录态(网关注入的 user_id 头),不接受前端传入 userId,天然防水平越权。
+ *
+ * 本期只做最基础的必填校验(各字段非空 / 枚举取值合法),不做任何格式核验与三方核验,
+ * 真实性由后台人工审核把关。
+ *
+ * 有效期字段的服务端兜底:「有效期类型」为「长期」时起止日期允许为空;为「短期」时
+ * 服务层会强制要求填写(见 AccountCertificationApplyServiceImpl#submitCompanyCertification)。
+ *
+ * @author hugray
+ */
+public class CompanyCertificationApplyDto
+{
+    /** 主体类型;必填,取 2个体工商户 / 3企业(对应 account_certification_apply.subject_type) */
+    @NotBlank(message = "请选择主体类型")
+    @Pattern(regexp = "^[23]$", message = "主体类型不合法")
+    private String subjectType;
+
+    /** 企业/个体名称;必填 */
+    @NotBlank(message = "请输入企业/个体名称")
+    @Size(max = 128, message = "企业/个体名称过长")
+    private String companyName;
+
+    /** 营业执照图片地址;必填 */
+    @NotBlank(message = "请上传营业执照")
+    @Size(max = 500, message = "营业执照图片地址过长")
+    private String licenseImage;
+
+    /** 统一社会信用代码;必填 */
+    @NotBlank(message = "请输入统一社会信用代码")
+    @Size(max = 32, message = "统一社会信用代码过长")
+    private String creditCode;
+
+    /** 营业执照有效期类型;必填,取 1长期 / 2短期 */
+    @NotBlank(message = "请选择营业执照有效期类型")
+    @Pattern(regexp = "^[12]$", message = "营业执照有效期类型不合法")
+    private String licenseValidType;
+
+    /** 营业执照有效期起止(文本);类型为「短期」时服务层必填 */
+    @Size(max = 32, message = "营业执照有效期过长")
+    private String licenseValidPeriod;
+
+    /** 营业执照注册地区;必填 */
+    @NotBlank(message = "请选择营业执照注册地区")
+    @Size(max = 128, message = "营业执照注册地区过长")
+    private String registeredRegion;
+
+    /** 营业执照详细地址;必填 */
+    @NotBlank(message = "请输入营业执照详细地址")
+    @Size(max = 255, message = "营业执照详细地址过长")
+    private String companyAddress;
+
+    /** 法人姓名;必填 */
+    @NotBlank(message = "请输入法人姓名")
+    @Size(max = 64, message = "法人姓名过长")
+    private String legalPerson;
+
+    /** 法人身份证人像面图片地址;必填 */
+    @NotBlank(message = "请上传法人身份证人像面")
+    @Size(max = 500, message = "法人身份证人像面图片地址过长")
+    private String legalIdCardFront;
+
+    /** 法人身份证国徽面图片地址;必填 */
+    @NotBlank(message = "请上传法人身份证国徽面")
+    @Size(max = 500, message = "法人身份证国徽面图片地址过长")
+    private String legalIdCardBack;
+
+    /** 法人身份证有效期类型;必填,取 1长期 / 2短期 */
+    @NotBlank(message = "请选择法人身份证有效期类型")
+    @Pattern(regexp = "^[12]$", message = "法人身份证有效期类型不合法")
+    private String legalIdValidType;
+
+    /** 法人身份证有效期起止(文本);类型为「短期」时服务层必填 */
+    @Size(max = 32, message = "法人身份证有效期过长")
+    private String legalIdValidPeriod;
+
+    /** 法人身份证详细地址;必填 */
+    @NotBlank(message = "请输入法人身份证详细地址")
+    @Size(max = 255, message = "法人身份证详细地址过长")
+    private String legalIdAddress;
+
+    /** 负责人姓名;必填 */
+    @NotBlank(message = "请输入负责人姓名")
+    @Size(max = 64, message = "负责人姓名过长")
+    private String contactName;
+
+    /** 负责人联系电话;必填(本期只校验非空,不校验格式) */
+    @NotBlank(message = "请输入负责人联系电话")
+    @Size(max = 20, message = "负责人联系电话过长")
+    private String contactPhone;
+
+    /** 负责人邮箱;必填(本期只校验非空,不校验格式) */
+    @NotBlank(message = "请输入负责人邮箱")
+    @Size(max = 128, message = "负责人邮箱过长")
+    private String contactEmail;
+
+    /** 收款类型;必填 */
+    @NotBlank(message = "请选择收款类型")
+    @Size(max = 16, message = "收款类型过长")
+    private String payeeType;
+
+    /** 银行卡号码面照片地址;必填 */
+    @NotBlank(message = "请上传银行卡号码面照片")
+    @Size(max = 500, message = "银行卡号码面照片地址过长")
+    private String bankCardImage;
+
+    /** 银行卡号;必填 */
+    @NotBlank(message = "请输入银行卡号")
+    @Size(max = 32, message = "银行卡号过长")
+    private String bankCardNo;
+
+    /** 开户城市;必填 */
+    @NotBlank(message = "请选择开户城市")
+    @Size(max = 128, message = "开户城市过长")
+    private String bankCity;
+
+    public String getSubjectType()
+    {
+        return subjectType;
+    }
+
+    public void setSubjectType(String subjectType)
+    {
+        this.subjectType = subjectType;
+    }
+
+    public String getCompanyName()
+    {
+        return companyName;
+    }
+
+    public void setCompanyName(String companyName)
+    {
+        this.companyName = companyName;
+    }
+
+    public String getLicenseImage()
+    {
+        return licenseImage;
+    }
+
+    public void setLicenseImage(String licenseImage)
+    {
+        this.licenseImage = licenseImage;
+    }
+
+    public String getCreditCode()
+    {
+        return creditCode;
+    }
+
+    public void setCreditCode(String creditCode)
+    {
+        this.creditCode = creditCode;
+    }
+
+    public String getLicenseValidType()
+    {
+        return licenseValidType;
+    }
+
+    public void setLicenseValidType(String licenseValidType)
+    {
+        this.licenseValidType = licenseValidType;
+    }
+
+    public String getLicenseValidPeriod()
+    {
+        return licenseValidPeriod;
+    }
+
+    public void setLicenseValidPeriod(String licenseValidPeriod)
+    {
+        this.licenseValidPeriod = licenseValidPeriod;
+    }
+
+    public String getRegisteredRegion()
+    {
+        return registeredRegion;
+    }
+
+    public void setRegisteredRegion(String registeredRegion)
+    {
+        this.registeredRegion = registeredRegion;
+    }
+
+    public String getCompanyAddress()
+    {
+        return companyAddress;
+    }
+
+    public void setCompanyAddress(String companyAddress)
+    {
+        this.companyAddress = companyAddress;
+    }
+
+    public String getLegalPerson()
+    {
+        return legalPerson;
+    }
+
+    public void setLegalPerson(String legalPerson)
+    {
+        this.legalPerson = legalPerson;
+    }
+
+    public String getLegalIdCardFront()
+    {
+        return legalIdCardFront;
+    }
+
+    public void setLegalIdCardFront(String legalIdCardFront)
+    {
+        this.legalIdCardFront = legalIdCardFront;
+    }
+
+    public String getLegalIdCardBack()
+    {
+        return legalIdCardBack;
+    }
+
+    public void setLegalIdCardBack(String legalIdCardBack)
+    {
+        this.legalIdCardBack = legalIdCardBack;
+    }
+
+    public String getLegalIdValidType()
+    {
+        return legalIdValidType;
+    }
+
+    public void setLegalIdValidType(String legalIdValidType)
+    {
+        this.legalIdValidType = legalIdValidType;
+    }
+
+    public String getLegalIdValidPeriod()
+    {
+        return legalIdValidPeriod;
+    }
+
+    public void setLegalIdValidPeriod(String legalIdValidPeriod)
+    {
+        this.legalIdValidPeriod = legalIdValidPeriod;
+    }
+
+    public String getLegalIdAddress()
+    {
+        return legalIdAddress;
+    }
+
+    public void setLegalIdAddress(String legalIdAddress)
+    {
+        this.legalIdAddress = legalIdAddress;
+    }
+
+    public String getContactName()
+    {
+        return contactName;
+    }
+
+    public void setContactName(String contactName)
+    {
+        this.contactName = contactName;
+    }
+
+    public String getContactPhone()
+    {
+        return contactPhone;
+    }
+
+    public void setContactPhone(String contactPhone)
+    {
+        this.contactPhone = contactPhone;
+    }
+
+    public String getContactEmail()
+    {
+        return contactEmail;
+    }
+
+    public void setContactEmail(String contactEmail)
+    {
+        this.contactEmail = contactEmail;
+    }
+
+    public String getPayeeType()
+    {
+        return payeeType;
+    }
+
+    public void setPayeeType(String payeeType)
+    {
+        this.payeeType = payeeType;
+    }
+
+    public String getBankCardImage()
+    {
+        return bankCardImage;
+    }
+
+    public void setBankCardImage(String bankCardImage)
+    {
+        this.bankCardImage = bankCardImage;
+    }
+
+    public String getBankCardNo()
+    {
+        return bankCardNo;
+    }
+
+    public void setBankCardNo(String bankCardNo)
+    {
+        this.bankCardNo = bankCardNo;
+    }
+
+    public String getBankCity()
+    {
+        return bankCity;
+    }
+
+    public void setBankCity(String bankCity)
+    {
+        this.bankCity = bankCity;
+    }
+}
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountCertificationStatusVo.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountCertificationStatusVo.java
new file mode 100644
index 0000000..7849194
--- /dev/null
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountCertificationStatusVo.java
@@ -0,0 +1,66 @@
+package com.ruoyi.user.domain.vo;
+
+/**
+ * C端 实名认证状态回填 展示对象
+ *
+ * 取该会员最近一次认证申请的快照,供实名认证页进入时回填与交互:
+ * 认证中 / 认证成功时姓名与身份证号只读、提交按钮置灰;认证失败时在身份证号下方展示驳回原因。
+ *
+ * auditStatus 为空表示该会员从未提交过认证申请,前端按「未认证」处理(空表单可编辑)。
+ *
+ * @author hugray
+ */
+public class AccountCertificationStatusVo
+{
+    /** 姓名(回填用) */
+    private String realName;
+
+    /** 身份证号(回填用) */
+    private String idCard;
+
+    /** 审核状态(0待审核 1已通过 2已驳回;为空表示从未提交) */
+    private String auditStatus;
+
+    /** 驳回原因(审核状态为「已驳回」时有值) */
+    private String auditRemark;
+
+    public String getRealName()
+    {
+        return realName;
+    }
+
+    public void setRealName(String realName)
+    {
+        this.realName = realName;
+    }
+
+    public String getIdCard()
+    {
+        return idCard;
+    }
+
+    public void setIdCard(String idCard)
+    {
+        this.idCard = idCard;
+    }
+
+    public String getAuditStatus()
+    {
+        return auditStatus;
+    }
+
+    public void setAuditStatus(String auditStatus)
+    {
+        this.auditStatus = auditStatus;
+    }
+
+    public String getAuditRemark()
+    {
+        return auditRemark;
+    }
+
+    public void setAuditRemark(String auditRemark)
+    {
+        this.auditRemark = auditRemark;
+    }
+}
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountInviteeVo.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountInviteeVo.java
new file mode 100644
index 0000000..f55875e
--- /dev/null
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountInviteeVo.java
@@ -0,0 +1,104 @@
+package com.ruoyi.user.domain.vo;
+
+import java.math.BigDecimal;
+
+/**
+ * 被邀请人 VO(我的邀请页「我邀请的」列表元素)
+ *
+ * 仅暴露页面所需字段;手机号仅下发脱敏值(如 136****6666),不下发明文。
+ *
+ * @author hugray
+ */
+public class AccountInviteeVo
+{
+    /** 下级会员ID */
+    private Long userId;
+
+    /** 昵称(注册未采集,可为空;为空时前端回退展示脱敏手机号) */
+    private String nickname;
+
+    /** 头像地址(可为空,为空时前端回退默认图标) */
+    private String avatar;
+
+    /** 手机号(脱敏,形如 136****6666;仅供展示,不下发明文) */
+    private String phone;
+
+    /** 等级原值(C / L1~L4,源自 account_user.system_level;本期不做 normalize,原样下发) */
+    private String systemLevel;
+
+    /** 绑定日(yyyy-MM-dd,源自 referrer_bind_time;为空表示未记录,前端不展示) */
+    private String bindDate;
+
+    /** 合计消费(单位:元,源自 account_user.buy_amount_total;现恒为 0,待订单模块回填) */
+    private BigDecimal buyAmountTotal;
+
+    public Long getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public String getNickname()
+    {
+        return nickname;
+    }
+
+    public void setNickname(String nickname)
+    {
+        this.nickname = nickname;
+    }
+
+    public String getAvatar()
+    {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar)
+    {
+        this.avatar = avatar;
+    }
+
+    public String getPhone()
+    {
+        return phone;
+    }
+
+    public void setPhone(String phone)
+    {
+        this.phone = phone;
+    }
+
+    public String getSystemLevel()
+    {
+        return systemLevel;
+    }
+
+    public void setSystemLevel(String systemLevel)
+    {
+        this.systemLevel = systemLevel;
+    }
+
+    public String getBindDate()
+    {
+        return bindDate;
+    }
+
+    public void setBindDate(String bindDate)
+    {
+        this.bindDate = bindDate;
+    }
+
+    public BigDecimal getBuyAmountTotal()
+    {
+        return buyAmountTotal;
+    }
+
+    public void setBuyAmountTotal(BigDecimal buyAmountTotal)
+    {
+        this.buyAmountTotal = buyAmountTotal;
+    }
+}
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountReferrerVo.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountReferrerVo.java
new file mode 100644
index 0000000..edbd005
--- /dev/null
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountReferrerVo.java
@@ -0,0 +1,89 @@
+package com.ruoyi.user.domain.vo;
+
+/**
+ * 上级(推荐人)VO(我的邀请页「邀请我的」Tab 展示用)
+ *
+ * 无上级时各字段均为 null,由前端渲染空态;手机号仅下发脱敏值,不下发明文。
+ *
+ * @author hugray
+ */
+public class AccountReferrerVo
+{
+    /** 上级会员ID */
+    private Long userId;
+
+    /** 昵称(可为空) */
+    private String nickname;
+
+    /** 头像地址(可为空,为空时前端回退默认图标) */
+    private String avatar;
+
+    /** 手机号(脱敏,形如 136****6666;仅供展示,不下发明文) */
+    private String phone;
+
+    /** 等级原值(C / L1~L4,源自 account_user.system_level;本期不做 normalize,原样下发) */
+    private String systemLevel;
+
+    /** 绑定日(yyyy-MM-dd,源自 referrer_bind_time;为空表示未记录,前端不展示) */
+    private String bindDate;
+
+    public Long getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public String getNickname()
+    {
+        return nickname;
+    }
+
+    public void setNickname(String nickname)
+    {
+        this.nickname = nickname;
+    }
+
+    public String getAvatar()
+    {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar)
+    {
+        this.avatar = avatar;
+    }
+
+    public String getPhone()
+    {
+        return phone;
+    }
+
+    public void setPhone(String phone)
+    {
+        this.phone = phone;
+    }
+
+    public String getSystemLevel()
+    {
+        return systemLevel;
+    }
+
+    public void setSystemLevel(String systemLevel)
+    {
+        this.systemLevel = systemLevel;
+    }
+
+    public String getBindDate()
+    {
+        return bindDate;
+    }
+
+    public void setBindDate(String bindDate)
+    {
+        this.bindDate = bindDate;
+    }
+}
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountUserInfoVo.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountUserInfoVo.java
index 25bd635..eaf30ab 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountUserInfoVo.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/AccountUserInfoVo.java
@@ -25,6 +25,15 @@
     /** 会员等级(对外展示字段,取值 C普通用户 / L1~L4会员,源自 account_user.system_level) */
     private String memberLevel;
 
+    /** 认证状态(0未认证 1审核中 2已认证 3认证失败,源自 account_user.auth_status;用于个人中心「实名认证」行徽标回显) */
+    private String authStatus;
+
+    /**
+     * 企业/个体认证状态(0未认证 1审核中 2已认证 3认证失败,源自 account_user.company_auth_status),
+     * 与 authStatus 相互独立;用于个人中心「企业/个体户认证」行徽标回显。
+     */
+    private String companyAuthStatus;
+
     /** 收益汇总(今日 / 本月 / 累计,单位:分) */
     private EarningSummaryVo earnings;
 
@@ -78,6 +87,26 @@
         this.memberLevel = memberLevel;
     }
 
+    public String getAuthStatus()
+    {
+        return authStatus;
+    }
+
+    public void setAuthStatus(String authStatus)
+    {
+        this.authStatus = authStatus;
+    }
+
+    public String getCompanyAuthStatus()
+    {
+        return companyAuthStatus;
+    }
+
+    public void setCompanyAuthStatus(String companyAuthStatus)
+    {
+        this.companyAuthStatus = companyAuthStatus;
+    }
+
     public EarningSummaryVo getEarnings()
     {
         return earnings;
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/CompanyCertificationStatusVo.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/CompanyCertificationStatusVo.java
new file mode 100644
index 0000000..2e6c4f8
--- /dev/null
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/CompanyCertificationStatusVo.java
@@ -0,0 +1,314 @@
+package com.ruoyi.user.domain.vo;
+
+/**
+ * C端 企业/个体认证状态回填 展示对象
+ *
+ * 取该会员最近一次企业/个体认证申请(subject_type ∈ {2,3})的快照,供企业认证向导页进入时回填:
+ * 已提交的整份资料 + 审核状态 + 驳回原因。审核中时前端直接进入「资料审核中」占位页,
+ * 已驳回时回填资料并展示驳回原因,可修改重提。
+ *
+ * auditStatus 为空表示该会员从未提交过企业/个体认证申请,前端按「未认证」处理(空表单可编辑)。
+ *
+ * @author hugray
+ */
+public class CompanyCertificationStatusVo
+{
+    /** 主体类型(2个体工商户 3企业;回填用) */
+    private String subjectType;
+
+    /** 企业/个体名称(回填用) */
+    private String companyName;
+
+    /** 营业执照图片地址(回填用) */
+    private String licenseImage;
+
+    /** 统一社会信用代码(回填用) */
+    private String creditCode;
+
+    /** 营业执照有效期类型(1长期 2短期) */
+    private String licenseValidType;
+
+    /** 营业执照有效期起止(文本) */
+    private String licenseValidPeriod;
+
+    /** 营业执照注册地区(回填用) */
+    private String registeredRegion;
+
+    /** 营业执照详细地址(回填用) */
+    private String companyAddress;
+
+    /** 法人姓名(回填用) */
+    private String legalPerson;
+
+    /** 法人身份证人像面图片地址(回填用) */
+    private String legalIdCardFront;
+
+    /** 法人身份证国徽面图片地址(回填用) */
+    private String legalIdCardBack;
+
+    /** 法人身份证有效期类型(1长期 2短期) */
+    private String legalIdValidType;
+
+    /** 法人身份证有效期起止(文本) */
+    private String legalIdValidPeriod;
+
+    /** 法人身份证详细地址(回填用) */
+    private String legalIdAddress;
+
+    /** 负责人姓名(回填用) */
+    private String contactName;
+
+    /** 负责人联系电话(回填用) */
+    private String contactPhone;
+
+    /** 负责人邮箱(回填用) */
+    private String contactEmail;
+
+    /** 收款类型(回填用) */
+    private String payeeType;
+
+    /** 银行卡号码面照片地址(回填用) */
+    private String bankCardImage;
+
+    /** 银行卡号(回填用) */
+    private String bankCardNo;
+
+    /** 开户城市(回填用) */
+    private String bankCity;
+
+    /** 审核状态(0待审核 1已通过 2已驳回;为空表示从未提交) */
+    private String auditStatus;
+
+    /** 驳回原因(审核状态为「已驳回」时有值) */
+    private String auditRemark;
+
+    public String getSubjectType()
+    {
+        return subjectType;
+    }
+
+    public void setSubjectType(String subjectType)
+    {
+        this.subjectType = subjectType;
+    }
+
+    public String getCompanyName()
+    {
+        return companyName;
+    }
+
+    public void setCompanyName(String companyName)
+    {
+        this.companyName = companyName;
+    }
+
+    public String getLicenseImage()
+    {
+        return licenseImage;
+    }
+
+    public void setLicenseImage(String licenseImage)
+    {
+        this.licenseImage = licenseImage;
+    }
+
+    public String getCreditCode()
+    {
+        return creditCode;
+    }
+
+    public void setCreditCode(String creditCode)
+    {
+        this.creditCode = creditCode;
+    }
+
+    public String getLicenseValidType()
+    {
+        return licenseValidType;
+    }
+
+    public void setLicenseValidType(String licenseValidType)
+    {
+        this.licenseValidType = licenseValidType;
+    }
+
+    public String getLicenseValidPeriod()
+    {
+        return licenseValidPeriod;
+    }
+
+    public void setLicenseValidPeriod(String licenseValidPeriod)
+    {
+        this.licenseValidPeriod = licenseValidPeriod;
+    }
+
+    public String getRegisteredRegion()
+    {
+        return registeredRegion;
+    }
+
+    public void setRegisteredRegion(String registeredRegion)
+    {
+        this.registeredRegion = registeredRegion;
+    }
+
+    public String getCompanyAddress()
+    {
+        return companyAddress;
+    }
+
+    public void setCompanyAddress(String companyAddress)
+    {
+        this.companyAddress = companyAddress;
+    }
+
+    public String getLegalPerson()
+    {
+        return legalPerson;
+    }
+
+    public void setLegalPerson(String legalPerson)
+    {
+        this.legalPerson = legalPerson;
+    }
+
+    public String getLegalIdCardFront()
+    {
+        return legalIdCardFront;
+    }
+
+    public void setLegalIdCardFront(String legalIdCardFront)
+    {
+        this.legalIdCardFront = legalIdCardFront;
+    }
+
+    public String getLegalIdCardBack()
+    {
+        return legalIdCardBack;
+    }
+
+    public void setLegalIdCardBack(String legalIdCardBack)
+    {
+        this.legalIdCardBack = legalIdCardBack;
+    }
+
+    public String getLegalIdValidType()
+    {
+        return legalIdValidType;
+    }
+
+    public void setLegalIdValidType(String legalIdValidType)
+    {
+        this.legalIdValidType = legalIdValidType;
+    }
+
+    public String getLegalIdValidPeriod()
+    {
+        return legalIdValidPeriod;
+    }
+
+    public void setLegalIdValidPeriod(String legalIdValidPeriod)
+    {
+        this.legalIdValidPeriod = legalIdValidPeriod;
+    }
+
+    public String getLegalIdAddress()
+    {
+        return legalIdAddress;
+    }
+
+    public void setLegalIdAddress(String legalIdAddress)
+    {
+        this.legalIdAddress = legalIdAddress;
+    }
+
+    public String getContactName()
+    {
+        return contactName;
+    }
+
+    public void setContactName(String contactName)
+    {
+        this.contactName = contactName;
+    }
+
+    public String getContactPhone()
+    {
+        return contactPhone;
+    }
+
+    public void setContactPhone(String contactPhone)
+    {
+        this.contactPhone = contactPhone;
+    }
+
+    public String getContactEmail()
+    {
+        return contactEmail;
+    }
+
+    public void setContactEmail(String contactEmail)
+    {
+        this.contactEmail = contactEmail;
+    }
+
+    public String getPayeeType()
+    {
+        return payeeType;
+    }
+
+    public void setPayeeType(String payeeType)
+    {
+        this.payeeType = payeeType;
+    }
+
+    public String getBankCardImage()
+    {
+        return bankCardImage;
+    }
+
+    public void setBankCardImage(String bankCardImage)
+    {
+        this.bankCardImage = bankCardImage;
+    }
+
+    public String getBankCardNo()
+    {
+        return bankCardNo;
+    }
+
+    public void setBankCardNo(String bankCardNo)
+    {
+        this.bankCardNo = bankCardNo;
+    }
+
+    public String getBankCity()
+    {
+        return bankCity;
+    }
+
+    public void setBankCity(String bankCity)
+    {
+        this.bankCity = bankCity;
+    }
+
+    public String getAuditStatus()
+    {
+        return auditStatus;
+    }
+
+    public void setAuditStatus(String auditStatus)
+    {
+        this.auditStatus = auditStatus;
+    }
+
+    public String getAuditRemark()
+    {
+        return auditRemark;
+    }
+
+    public void setAuditRemark(String auditRemark)
+    {
+        this.auditRemark = auditRemark;
+    }
+}
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/InviteCodeVo.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/InviteCodeVo.java
new file mode 100644
index 0000000..223c7ec
--- /dev/null
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/InviteCodeVo.java
@@ -0,0 +1,24 @@
+package com.ruoyi.user.domain.vo;
+
+/**
+ * 邀请码 VO(我的邀请页「邀请码」Tab 展示用)
+ *
+ * 仅下发本人邀请码原值;会员身份由服务端从登录态解析,不接受前端传入。
+ *
+ * @author hugray
+ */
+public class InviteCodeVo
+{
+    /** 本人邀请码(源自 account_user.my_referral_code,注册时生成,C 端会员必有) */
+    private String referralCode;
+
+    public String getReferralCode()
+    {
+        return referralCode;
+    }
+
+    public void setReferralCode(String referralCode)
+    {
+        this.referralCode = referralCode;
+    }
+}
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountCertificationApplyMapper.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountCertificationApplyMapper.java
index 12411ef..d2ad5e7 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountCertificationApplyMapper.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountCertificationApplyMapper.java
@@ -25,4 +25,37 @@
      * 审核(只更新审核结果 4 列,条件带 audit_status='0' 作并发闸门)
      */
     public int auditCertificationApply(AccountCertificationApply apply);
+
+    /**
+     * 新增认证申请(C端会员提交,回填自增主键作为申请号)
+     */
+    public int insertCertificationApply(AccountCertificationApply apply);
+
+    /**
+     * 统计该会员待审核的「个人」认证申请数(>0 即视为重复提交,用于提交前防重)
+     *
+     * 限 subject_type='1':个人与企业认证各自独立防重,互不干扰。
+     */
+    public int countPendingPersonalApplyByUserId(Long userId);
+
+    /**
+     * 统计该会员待审核的「企业/个体」认证申请数(>0 即视为重复提交,用于提交前防重)
+     *
+     * 限 subject_type in ('2','3')。
+     */
+    public int countPendingCompanyApplyByUserId(Long userId);
+
+    /**
+     * 查询该会员最近一次「个人」认证申请(C端进入认证页时回填资料与状态;无申请返回 null)
+     *
+     * 限 subject_type='1'。
+     */
+    public AccountCertificationApply selectLatestPersonalApplyByUserId(Long userId);
+
+    /**
+     * 查询该会员最近一次「企业/个体」认证申请(C端进入企业认证向导页时回填;无申请返回 null)
+     *
+     * 限 subject_type in ('2','3')。
+     */
+    public AccountCertificationApply selectLatestCompanyApplyByUserId(Long userId);
 }
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountUserMapper.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountUserMapper.java
index 0ccb430..37ca413 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountUserMapper.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountUserMapper.java
@@ -79,6 +79,24 @@
     public int updateUserAuthStatus(AccountUser accountUser);
 
     /**
+     * 修改企业/个体认证状态(只更新 company_auth_status 列,企业认证审核通过/驳回时回写)
+     *
+     * 与 updateUserAuthStatus 的区别:个人认证写 auth_status,企业/个体认证写 company_auth_status,
+     * 两者互不覆盖。审核通过时一并把 subject_type 同步为 2/3(由 XML 的 &lt;if&gt; 控制),
+     * 驳回则不动 subject_type——避免审核未通过就把会员标成企业主体。
+     */
+    public int updateUserCompanyAuthStatus(AccountUser accountUser);
+
+    /**
+     * 修改会员头像(只更新 avatar 一列)
+     *
+     * 刻意单开:avatar 若挂到共享的 updateAccountUser 上,后台编辑接口(/account/user)
+     * 的请求体带个 avatar 就能改任意会员头像。与 updateUserStatus / updateUserAuthStatus 同一套路,
+     * 头像只能由会员本人在 C 端接口里改写。
+     */
+    public int updateUserAvatar(AccountUser accountUser);
+
+    /**
      * 修改服务商标识(只更新 provider_flag 列,服务商建档时置 '1')
      *
      * 刻意不做进 insertAccountUser / updateAccountUser 的动态列:那两个是共享入口,
@@ -88,6 +106,14 @@
     public int updateUserProviderFlag(AccountUser accountUser);
 
     /**
+     * 查询某会员的直接下级(被邀请人)列表
+     *
+     * 按 referrer_id 过滤(命中 idx_account_user_referrer)+ referrer_bind_time 倒序;
+     * 不挂 join,避免干扰 PageHelper 的 count SQL。
+     */
+    public List<AccountUser> selectInvitees(Long referrerId);
+
+    /**
      * 搜索可绑定为城市公司档案关联用户的会员(限 L4,最多 20 条)
      *
      * keyword 与 keywordId 由服务层同时算好:keywordId 仅在 keyword 为纯数字时非空,
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountCertificationApplyService.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountCertificationApplyService.java
index 0340896..cdd2a59 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountCertificationApplyService.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountCertificationApplyService.java
@@ -1,8 +1,11 @@
 package com.ruoyi.user.service;
 
 import java.util.List;
+import com.ruoyi.user.domain.dto.CompanyCertificationApplyDto;
 import com.ruoyi.user.domain.vo.AccountCertificationApplyDetailVo;
 import com.ruoyi.user.domain.vo.AccountCertificationApplyVo;
+import com.ruoyi.user.domain.vo.AccountCertificationStatusVo;
+import com.ruoyi.user.domain.vo.CompanyCertificationStatusVo;
 
 /**
  * 会员主体认证申请 业务层
@@ -30,4 +33,48 @@
      * @return 影响行数
      */
     public int auditCertificationApply(Long id, String auditStatus, String auditRemark);
+
+    /**
+     * C端会员提交个人主体认证申请
+     *
+     * 只做基础校验(姓名非空、身份证号非空),不做三方实名核验;提交后落一条
+     * account_certification_apply(个人主体、待审核),并把会员认证状态联动为「审核中」。
+     *
+     * @param userId   会员ID(取自登录态,非前端传入)
+     * @param realName 姓名
+     * @param idCard   身份证号
+     */
+    public void submitPersonalCertification(Long userId, String realName, String idCard);
+
+    /**
+     * 查询该会员当前认证状态(C端进入认证页回填用)
+     *
+     * 取最近一次申请快照映射为出参;从未提交过申请时返回各字段为空的出参对象(不返回 null)。
+     *
+     * @param userId 会员ID(取自登录态,非前端传入)
+     * @return 认证状态出参(realName / idCard / auditStatus / auditRemark)
+     */
+    public AccountCertificationStatusVo getCertificationStatus(Long userId);
+
+    /**
+     * C端会员提交企业/个体工商户认证申请
+     *
+     * 只做基础必填校验(各字段非空 / 枚举取值合法),不做格式与三方核验;提交后落一条
+     * account_certification_apply(主体类型取入参 2/3、待审核),并把会员企业认证状态联动为「审核中」。
+     *
+     * @param userId 会员ID(取自登录态,非前端传入)
+     * @param dto    企业/个体认证资料(含基础必填校验)
+     */
+    public void submitCompanyCertification(Long userId, CompanyCertificationApplyDto dto);
+
+    /**
+     * 查询该会员当前企业/个体认证状态(C端进入向导页回填用)
+     *
+     * 取最近一次企业/个体申请(subject_type ∈ {2,3})快照映射为出参;从未提交过时
+     * 返回各字段为空的出参对象(不返回 null),前端据此渲染空表单。
+     *
+     * @param userId 会员ID(取自登录态,非前端传入)
+     * @return 企业/个体认证状态出参(整份资料 + auditStatus + auditRemark)
+     */
+    public CompanyCertificationStatusVo getCompanyCertificationStatus(Long userId);
 }
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountUserService.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountUserService.java
index 9691fa2..aacd34d 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountUserService.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountUserService.java
@@ -5,7 +5,10 @@
 import com.ruoyi.user.domain.AccountUser;
 import com.ruoyi.user.domain.vo.AccountUserDetailVo;
 import com.ruoyi.user.domain.vo.MemberOptionVo;
+import com.ruoyi.user.domain.vo.AccountInviteeVo;
+import com.ruoyi.user.domain.vo.AccountReferrerVo;
 import com.ruoyi.user.domain.vo.AccountUserInfoVo;
+import com.ruoyi.user.domain.vo.InviteCodeVo;
 
 /**
  * 会员用户 业务层
@@ -114,4 +117,41 @@
      * @return 会员信息展示对象;会员不存在时抛业务异常
      */
     public AccountUserInfoVo getAccountUserInfo(Long userId);
+
+    /**
+     * 更新C端会员头像
+     *
+     * 头像文件由小程序直传文件服务后,将返回的 url 写回 account_user.avatar;
+     * 只更新 avatar 一列,不触碰其它资料。
+     *
+     * @param userId 会员ID(取自登录态,非前端传入)
+     * @param avatarUrl 头像地址(来自文件服务上传结果)
+     */
+    public void updateAvatar(Long userId, String avatarUrl);
+
+    /**
+     * 查询本人邀请码(我的邀请页「邀请码」Tab)
+     *
+     * @param userId 会员ID(取自登录态,非前端传入)
+     * @return 邀请码展示对象;会员不存在时抛业务异常
+     */
+    public InviteCodeVo getInviteCode(Long userId);
+
+    /**
+     * 查询本人直接下级(被邀请人)列表(我的邀请页「我邀请的」Tab)
+     *
+     * 按绑定时间倒序(最新在前);手机号脱敏下发;配合 Controller 的分页生效。
+     *
+     * @param userId 会员ID(取自登录态,非前端传入)
+     * @return 被邀请人列表(无下级时返回空集合)
+     */
+    public List<AccountInviteeVo> getInvitees(Long userId);
+
+    /**
+     * 查询本人上级(推荐人)信息(我的邀请页「邀请我的」Tab)
+     *
+     * @param userId 会员ID(取自登录态,非前端传入)
+     * @return 上级展示对象;无上级时返回各字段为 null 的对象(前端渲染空态)
+     */
+    public AccountReferrerVo getReferrer(Long userId);
 }
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountCertificationApplyServiceImpl.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountCertificationApplyServiceImpl.java
index ed932dd..379c627 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountCertificationApplyServiceImpl.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountCertificationApplyServiceImpl.java
@@ -10,8 +10,11 @@
 import com.ruoyi.user.domain.AccountCertification;
 import com.ruoyi.user.domain.AccountCertificationApply;
 import com.ruoyi.user.domain.AccountUser;
+import com.ruoyi.user.domain.dto.CompanyCertificationApplyDto;
 import com.ruoyi.user.domain.vo.AccountCertificationApplyDetailVo;
 import com.ruoyi.user.domain.vo.AccountCertificationApplyVo;
+import com.ruoyi.user.domain.vo.AccountCertificationStatusVo;
+import com.ruoyi.user.domain.vo.CompanyCertificationStatusVo;
 import com.ruoyi.user.mapper.AccountCertificationApplyMapper;
 import com.ruoyi.user.mapper.AccountCertificationMapper;
 import com.ruoyi.user.mapper.AccountUserMapper;
@@ -31,11 +34,32 @@
     /** 审核结果:驳回 */
     private static final String AUDIT_REJECT = "2";
 
+    /** 审核状态:待审核(提交后的初始状态) */
+    private static final String AUDIT_PENDING = "0";
+
+    /** account_user.auth_status:审核中 */
+    private static final String AUTH_AUDITING = "1";
+
     /** account_user.auth_status:已认证 */
     private static final String AUTH_VERIFIED = "2";
 
     /** account_user.auth_status:认证失败 */
     private static final String AUTH_FAILED = "3";
+
+    /** 主体类型:个人(C端实名认证固定为该类型) */
+    private static final String SUBJECT_PERSONAL = "1";
+
+    /** 主体类型:个体工商户 */
+    private static final String SUBJECT_INDIVIDUAL = "2";
+
+    /** 主体类型:企业 */
+    private static final String SUBJECT_COMPANY = "3";
+
+    /** 有效期类型:长期(此时起止日期可空) */
+    private static final String VALID_TYPE_LONG_TERM = "1";
+
+    /** 有效期类型:短期(此时起止日期必填) */
+    private static final String VALID_TYPE_SHORT_TERM = "2";
 
     @Autowired
     private AccountCertificationApplyMapper certificationApplyMapper;
@@ -102,18 +126,221 @@
             throw new ServiceException("该申请已审核,不能重复处理");
         }
 
-        // 同步到 account_user.auth_status:会员列表的「认证状态」列取的就是它
+        // 同步认证状态到 account_user:个人写 auth_status,企业/个体写 company_auth_status,两列互不覆盖。
         AccountUser user = new AccountUser();
         user.setUserId(apply.getUserId());
-        user.setAuthStatus(pass ? AUTH_VERIFIED : AUTH_FAILED);
         user.setUpdateBy(operator);
-        accountUserMapper.updateUserAuthStatus(user);
+        if (isCompanySubject(apply.getSubjectType()))
+        {
+            user.setCompanyAuthStatus(pass ? AUTH_VERIFIED : AUTH_FAILED);
+            // 审核通过时才把主体类型落为 2/3;驳回不动,避免未通过审核就被标成企业主体
+            if (pass)
+            {
+                user.setSubjectType(apply.getSubjectType());
+            }
+            accountUserMapper.updateUserCompanyAuthStatus(user);
+        }
+        else
+        {
+            user.setAuthStatus(pass ? AUTH_VERIFIED : AUTH_FAILED);
+            accountUserMapper.updateUserAuthStatus(user);
+        }
 
         if (pass)
         {
             saveCertificationSnapshot(apply, operator);
         }
         return 1;
+    }
+
+    @Override
+    @Transactional
+    public void submitPersonalCertification(Long userId, String realName, String idCard)
+    {
+        // STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属写入)
+        if (StringUtils.isNull(userId))
+        {
+            throw new ServiceException("提交实名认证失败");
+        }
+        // STEP 2: 姓名/身份证号二次校验:Controller 已用 @Validated 拦过一遍,
+        //         这里是服务层兜底,防止绕过 Controller 直接调用服务时落脏数据
+        if (StringUtils.isEmpty(realName) || StringUtils.isEmpty(idCard))
+        {
+            throw new ServiceException("姓名与身份证号不能为空");
+        }
+        // STEP 3: 防重闸门——该会员已有待审核申请时拒绝,避免后台审核列表堆积重复单
+        if (certificationApplyMapper.countPendingPersonalApplyByUserId(userId) > 0)
+        {
+            throw new ServiceException("已有实名认证申请正在审核中,请勿重复提交");
+        }
+        // STEP 4: 组装申请快照并落库(主体类型固定「个人」;审核状态置「待审核」;
+        //         身份证号统一转大写,兼容末位小写 x)
+        String operator = SecurityUtils.getUsername();
+        AccountCertificationApply apply = new AccountCertificationApply();
+        apply.setUserId(userId);
+        apply.setSubjectType(SUBJECT_PERSONAL);
+        apply.setRealName(realName.trim());
+        apply.setIdCard(idCard.trim().toUpperCase());
+        apply.setAuditStatus(AUDIT_PENDING);
+        apply.setCreateBy(operator);
+        if (certificationApplyMapper.insertCertificationApply(apply) <= 0)
+        {
+            throw new ServiceException("提交实名认证失败,请稍后重试");
+        }
+        // STEP 5: 联动 account_user.auth_status 为「审核中」,个人中心与会员列表据此显示
+        AccountUser user = new AccountUser();
+        user.setUserId(userId);
+        user.setAuthStatus(AUTH_AUDITING);
+        user.setUpdateBy(operator);
+        accountUserMapper.updateUserAuthStatus(user);
+    }
+
+    @Override
+    public AccountCertificationStatusVo getCertificationStatus(Long userId)
+    {
+        AccountCertificationStatusVo vo = new AccountCertificationStatusVo();
+        // 登录态缺失视为从未提交:返回空对象,前端按「未认证」渲染空表单(不返回 null)
+        if (StringUtils.isNull(userId))
+        {
+            return vo;
+        }
+        AccountCertificationApply apply = certificationApplyMapper.selectLatestPersonalApplyByUserId(userId);
+        if (StringUtils.isNull(apply))
+        {
+            return vo;
+        }
+        // 回填最近一次申请的资料与状态:认证失败时前端据 auditRemark 展示驳回原因
+        vo.setRealName(apply.getRealName());
+        vo.setIdCard(apply.getIdCard());
+        vo.setAuditStatus(apply.getAuditStatus());
+        vo.setAuditRemark(apply.getAuditRemark());
+        return vo;
+    }
+
+    @Override
+    @Transactional
+    public void submitCompanyCertification(Long userId, CompanyCertificationApplyDto dto)
+    {
+        // STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属写入)
+        if (StringUtils.isNull(userId))
+        {
+            throw new ServiceException("提交企业认证失败");
+        }
+        // STEP 2: 入参兜底:Controller 已用 @Validated 拦过,这里防止绕过 Controller 直接落脏数据
+        if (StringUtils.isNull(dto))
+        {
+            throw new ServiceException("认证资料不能为空");
+        }
+        // STEP 3: 有效期必填兜底——「短期」必须填起止日期,「长期」允许为空(前端此时隐藏该输入)
+        checkValidPeriod(dto.getLicenseValidType(), dto.getLicenseValidPeriod(), "营业执照");
+        checkValidPeriod(dto.getLegalIdValidType(), dto.getLegalIdValidPeriod(), "法人身份证");
+        // STEP 4: 防重闸门——该会员已有待审核的企业申请时拒绝,避免后台审核列表堆积重复单
+        if (certificationApplyMapper.countPendingCompanyApplyByUserId(userId) > 0)
+        {
+            throw new ServiceException("已有企业认证申请正在审核中,请勿重复提交");
+        }
+        // STEP 5: 组装申请快照(主体类型取入参 2/3;审核状态置「待审核」)
+        String operator = SecurityUtils.getUsername();
+        AccountCertificationApply apply = new AccountCertificationApply();
+        apply.setUserId(userId);
+        apply.setSubjectType(dto.getSubjectType());
+        apply.setCompanyName(dto.getCompanyName().trim());
+        apply.setLicenseImage(dto.getLicenseImage());
+        apply.setCreditCode(dto.getCreditCode().trim());
+        apply.setLicenseValidType(dto.getLicenseValidType());
+        apply.setLicenseValidPeriod(dto.getLicenseValidPeriod());
+        apply.setRegisteredRegion(dto.getRegisteredRegion());
+        apply.setCompanyAddress(dto.getCompanyAddress());
+        apply.setLegalPerson(dto.getLegalPerson().trim());
+        apply.setLegalIdCardFront(dto.getLegalIdCardFront());
+        apply.setLegalIdCardBack(dto.getLegalIdCardBack());
+        apply.setLegalIdValidType(dto.getLegalIdValidType());
+        apply.setLegalIdValidPeriod(dto.getLegalIdValidPeriod());
+        apply.setLegalIdAddress(dto.getLegalIdAddress());
+        apply.setContactName(dto.getContactName().trim());
+        apply.setContactPhone(dto.getContactPhone().trim());
+        apply.setContactEmail(dto.getContactEmail().trim());
+        apply.setPayeeType(dto.getPayeeType());
+        apply.setBankCardImage(dto.getBankCardImage());
+        apply.setBankCardNo(dto.getBankCardNo().trim());
+        apply.setBankCity(dto.getBankCity());
+        apply.setAuditStatus(AUDIT_PENDING);
+        apply.setCreateBy(operator);
+        // STEP 6: 落库
+        if (certificationApplyMapper.insertCertificationApply(apply) <= 0)
+        {
+            throw new ServiceException("提交企业认证失败,请稍后重试");
+        }
+        // STEP 7: 联动 account_user.company_auth_status 为「审核中」(与个人 auth_status 相互独立)
+        AccountUser user = new AccountUser();
+        user.setUserId(userId);
+        user.setCompanyAuthStatus(AUTH_AUDITING);
+        user.setUpdateBy(operator);
+        accountUserMapper.updateUserCompanyAuthStatus(user);
+    }
+
+    @Override
+    public CompanyCertificationStatusVo getCompanyCertificationStatus(Long userId)
+    {
+        CompanyCertificationStatusVo vo = new CompanyCertificationStatusVo();
+        // 登录态缺失视为从未提交:返回空对象,前端按「未认证」渲染空表单(不返回 null)
+        if (StringUtils.isNull(userId))
+        {
+            return vo;
+        }
+        AccountCertificationApply apply = certificationApplyMapper.selectLatestCompanyApplyByUserId(userId);
+        if (StringUtils.isNull(apply))
+        {
+            return vo;
+        }
+        // 一次性回填整份资料与审核状态:审核中进占位页、已驳回展示原因并允许修改重提
+        vo.setSubjectType(apply.getSubjectType());
+        vo.setCompanyName(apply.getCompanyName());
+        vo.setLicenseImage(apply.getLicenseImage());
+        vo.setCreditCode(apply.getCreditCode());
+        vo.setLicenseValidType(apply.getLicenseValidType());
+        vo.setLicenseValidPeriod(apply.getLicenseValidPeriod());
+        vo.setRegisteredRegion(apply.getRegisteredRegion());
+        vo.setCompanyAddress(apply.getCompanyAddress());
+        vo.setLegalPerson(apply.getLegalPerson());
+        vo.setLegalIdCardFront(apply.getLegalIdCardFront());
+        vo.setLegalIdCardBack(apply.getLegalIdCardBack());
+        vo.setLegalIdValidType(apply.getLegalIdValidType());
+        vo.setLegalIdValidPeriod(apply.getLegalIdValidPeriod());
+        vo.setLegalIdAddress(apply.getLegalIdAddress());
+        vo.setContactName(apply.getContactName());
+        vo.setContactPhone(apply.getContactPhone());
+        vo.setContactEmail(apply.getContactEmail());
+        vo.setPayeeType(apply.getPayeeType());
+        vo.setBankCardImage(apply.getBankCardImage());
+        vo.setBankCardNo(apply.getBankCardNo());
+        vo.setBankCity(apply.getBankCity());
+        vo.setAuditStatus(apply.getAuditStatus());
+        vo.setAuditRemark(apply.getAuditRemark());
+        return vo;
+    }
+
+    /**
+     * 判定主体类型是否为企业/个体工商户(subject_type ∈ {2,3})。
+     */
+    private boolean isCompanySubject(String subjectType)
+    {
+        return SUBJECT_INDIVIDUAL.equals(subjectType) || SUBJECT_COMPANY.equals(subjectType);
+    }
+
+    /**
+     * 有效期必填兜底:类型为「短期」时,起止日期不得为空。
+     *
+     * @param validType   有效期类型(1长期 2短期)
+     * @param validPeriod 有效期起止(文本)
+     * @param label       字段中文名,用于拼接错误提示
+     */
+    private void checkValidPeriod(String validType, String validPeriod, String label)
+    {
+        if (VALID_TYPE_SHORT_TERM.equals(validType) && StringUtils.isEmpty(validPeriod))
+        {
+            throw new ServiceException("请选择" + label + "有效期起止日期");
+        }
     }
 
     /**
@@ -144,6 +371,19 @@
         certification.setRegisterDate(apply.getRegisterDate());
         certification.setLicenseValidPeriod(apply.getLicenseValidPeriod());
         certification.setLicenseImage(apply.getLicenseImage());
+        certification.setLicenseValidType(apply.getLicenseValidType());
+        certification.setLegalIdCardFront(apply.getLegalIdCardFront());
+        certification.setLegalIdCardBack(apply.getLegalIdCardBack());
+        certification.setLegalIdValidType(apply.getLegalIdValidType());
+        certification.setLegalIdValidPeriod(apply.getLegalIdValidPeriod());
+        certification.setLegalIdAddress(apply.getLegalIdAddress());
+        certification.setContactName(apply.getContactName());
+        certification.setContactPhone(apply.getContactPhone());
+        certification.setContactEmail(apply.getContactEmail());
+        certification.setPayeeType(apply.getPayeeType());
+        certification.setBankCardImage(apply.getBankCardImage());
+        certification.setBankCardNo(apply.getBankCardNo());
+        certification.setBankCity(apply.getBankCity());
         certification.setCreateBy(operator);
         certification.setRemark("由认证申请(申请号 " + apply.getId() + ")审核通过生成");
         certificationMapper.insertCertification(certification);
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java
index 47b2cec..f939d2e 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java
@@ -1,5 +1,6 @@
 package com.ruoyi.user.service.impl;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.security.SecureRandom;
@@ -13,14 +14,16 @@
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.core.exception.ServiceException;
 import com.ruoyi.common.core.utils.DateUtils;
-import com.ruoyi.common.core.exception.ServiceException;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.security.utils.SecurityUtils;
 import com.ruoyi.user.domain.AccountAddress;
 import com.ruoyi.user.config.UniversalCodeProperties;
 import com.ruoyi.user.constant.ReferralConstants;
 import com.ruoyi.user.domain.AccountUser;
+import com.ruoyi.user.domain.vo.AccountInviteeVo;
+import com.ruoyi.user.domain.vo.AccountReferrerVo;
 import com.ruoyi.user.domain.vo.AccountUserDetailVo;
+import com.ruoyi.user.domain.vo.InviteCodeVo;
 import com.ruoyi.user.domain.vo.MemberOptionVo;
 import com.ruoyi.user.mapper.AccountAddressMapper;
 import com.ruoyi.user.mapper.AccountAgreementMapper;
@@ -66,6 +69,12 @@
 
     /** 推荐码随机源(SecureRandom 线程安全,避免每次创建) */
     private static final SecureRandom RANDOM = new SecureRandom();
+
+    /** 头像地址最大长度(对应 account_user.avatar varchar(255),超长直接拒绝避免截断) */
+    private static final int AVATAR_MAX_LENGTH = 255;
+
+    /** 认证状态:未认证(auth_status 为空时的兜底值) */
+    private static final String AUTH_STATUS_NONE = "0";
 
     @Override
     public AccountUser selectAccountUserById(Long userId)
@@ -364,11 +373,142 @@
         vo.setPhone(maskPhone(accountUser.getPhone()));
         vo.setAvatar(accountUser.getAvatar());
         vo.setMemberLevel(MemberLevelEnum.normalize(accountUser.getSystemLevel()));
+        // 认证状态为空(历史数据)时兜底为「未认证」,避免前端徽标无值可判
+        vo.setAuthStatus(StringUtils.isEmpty(accountUser.getAuthStatus())
+            ? AUTH_STATUS_NONE : accountUser.getAuthStatus());
+        // 企业/个体认证状态同样兜底为「未认证」(与个人认证状态相互独立)
+        vo.setCompanyAuthStatus(StringUtils.isEmpty(accountUser.getCompanyAuthStatus())
+            ? AUTH_STATUS_NONE : accountUser.getCompanyAuthStatus());
         vo.setEarnings(earnings);
         // STEP 5: 返回个人中心展示数据
         return vo;
     }
 
+    @Override
+    public void updateAvatar(Long userId, String avatarUrl)
+    {
+        // STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属更新)
+        if (StringUtils.isNull(userId))
+        {
+            throw new ServiceException("更新头像失败");
+        }
+        // STEP 2: 校验头像地址非空
+        if (StringUtils.isEmpty(avatarUrl))
+        {
+            throw new ServiceException("头像地址不能为空");
+        }
+        // STEP 3: 校验长度,超出列长直接拒绝,避免落库截断
+        if (avatarUrl.length() > AVATAR_MAX_LENGTH)
+        {
+            throw new ServiceException("头像地址过长");
+        }
+        // STEP 4: 只更新 avatar 一列(单列语句,不触碰其它资料)
+        AccountUser update = new AccountUser();
+        update.setUserId(userId);
+        update.setAvatar(avatarUrl);
+        update.setUpdateBy(SecurityUtils.getUsername());
+        // STEP 5: 影响行数为 0 说明会员不存在或已删除(SQL 自带 del_flag='0' 过滤)
+        if (accountUserMapper.updateUserAvatar(update) == 0)
+        {
+            throw new ServiceException("更新头像失败");
+        }
+    }
+
+    @Override
+    public InviteCodeVo getInviteCode(Long userId)
+    {
+        // STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属查询)
+        if (StringUtils.isNull(userId))
+        {
+            throw new ServiceException("获取邀请码失败");
+        }
+        // STEP 2: 按会员ID查询会员本人(逻辑删除已在 SQL 中过滤)
+        AccountUser accountUser = accountUserMapper.selectAccountUserById(userId);
+        if (StringUtils.isNull(accountUser))
+        {
+            throw new ServiceException("获取邀请码失败");
+        }
+        // STEP 3: 组装展示对象(邀请码注册时生成,C 端会员必有)
+        InviteCodeVo vo = new InviteCodeVo();
+        vo.setReferralCode(accountUser.getMyReferralCode());
+        return vo;
+    }
+
+    @Override
+    public List<AccountInviteeVo> getInvitees(Long userId)
+    {
+        // STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属查询)
+        if (StringUtils.isNull(userId))
+        {
+            throw new ServiceException("获取邀请列表失败");
+        }
+        // STEP 2: 按 referrer_id 查询直接下级,SQL 已按绑定时间倒序(配合 Controller 分页)
+        List<AccountUser> invitees = accountUserMapper.selectInvitees(userId);
+        // STEP 3: 逐条组装展示对象;手机号脱敏、绑定日格式化,等级原值直出
+        List<AccountInviteeVo> result = new ArrayList<>(invitees.size());
+        for (AccountUser invitee : invitees)
+        {
+            AccountInviteeVo vo = new AccountInviteeVo();
+            vo.setUserId(invitee.getUserId());
+            vo.setNickname(invitee.getNickname());
+            vo.setAvatar(invitee.getAvatar());
+            vo.setPhone(maskPhone(invitee.getPhone()));
+            vo.setSystemLevel(invitee.getSystemLevel());
+            vo.setBindDate(formatBindDate(invitee.getReferrerBindTime()));
+            vo.setBuyAmountTotal(invitee.getBuyAmountTotal());
+            result.add(vo);
+        }
+        return result;
+    }
+
+    @Override
+    public AccountReferrerVo getReferrer(Long userId)
+    {
+        // STEP 1: 校验会员ID(登录态缺失时直接失败,避免无归属查询)
+        if (StringUtils.isNull(userId))
+        {
+            throw new ServiceException("获取上级信息失败");
+        }
+        // STEP 2: 查询会员本人,取其 referrer_id 与绑定时间
+        AccountUser self = accountUserMapper.selectAccountUserById(userId);
+        if (StringUtils.isNull(self))
+        {
+            throw new ServiceException("获取上级信息失败");
+        }
+        AccountReferrerVo vo = new AccountReferrerVo();
+        Long referrerId = self.getReferrerId();
+        // STEP 3: 无上级(命中通用推荐码注册或历史数据)时返回空对象,前端渲染空态
+        if (StringUtils.isNull(referrerId))
+        {
+            return vo;
+        }
+        // STEP 4: 按主键查上级;上级已被逻辑删除时同样返回空对象
+        AccountUser referrer = accountUserMapper.selectAccountUserById(referrerId);
+        if (StringUtils.isNull(referrer))
+        {
+            return vo;
+        }
+        // STEP 5: 组装展示对象;绑定时间取本人记录上的 referrer_bind_time
+        vo.setUserId(referrer.getUserId());
+        vo.setNickname(referrer.getNickname());
+        vo.setAvatar(referrer.getAvatar());
+        vo.setPhone(maskPhone(referrer.getPhone()));
+        vo.setSystemLevel(referrer.getSystemLevel());
+        vo.setBindDate(formatBindDate(self.getReferrerBindTime()));
+        return vo;
+    }
+
+    /**
+     * 绑定时间格式化(yyyy-MM-dd)
+     *
+     * @param bindTime 绑定时间,可为空
+     * @return 格式化后的日期字符串;入参为空时返回 null
+     */
+    private String formatBindDate(Date bindTime)
+    {
+        return StringUtils.isNull(bindTime) ? null : DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, bindTime);
+    }
+
     /**
      * 判定是否为通用推荐码
      *
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountCertificationApplyMapper.xml b/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountCertificationApplyMapper.xml
index 951a55c..0734737 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountCertificationApplyMapper.xml
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountCertificationApplyMapper.xml
@@ -18,6 +18,19 @@
         <result property="registerDate"       column="register_date"        />
         <result property="licenseValidPeriod" column="license_valid_period" />
         <result property="licenseImage"       column="license_image"        />
+        <result property="licenseValidType"   column="license_valid_type"   />
+        <result property="legalIdCardFront"   column="legal_id_card_front"  />
+        <result property="legalIdCardBack"    column="legal_id_card_back"   />
+        <result property="legalIdValidType"   column="legal_id_valid_type"  />
+        <result property="legalIdValidPeriod" column="legal_id_valid_period"/>
+        <result property="legalIdAddress"     column="legal_id_address"     />
+        <result property="contactName"        column="contact_name"         />
+        <result property="contactPhone"       column="contact_phone"        />
+        <result property="contactEmail"       column="contact_email"        />
+        <result property="payeeType"          column="payee_type"           />
+        <result property="bankCardImage"      column="bank_card_image"      />
+        <result property="bankCardNo"         column="bank_card_no"         />
+        <result property="bankCity"           column="bank_city"            />
         <result property="auditStatus"        column="audit_status"         />
         <result property="auditBy"            column="audit_by"             />
         <result property="auditTime"          column="audit_time"           />
@@ -38,6 +51,9 @@
     <sql id="selectCertificationApplyVo">
         select id, user_id, subject_type, real_name, id_card, company_name, credit_code, legal_person,
                registered_region, company_address, register_date, license_valid_period, license_image,
+               license_valid_type, legal_id_card_front, legal_id_card_back, legal_id_valid_type,
+               legal_id_valid_period, legal_id_address, contact_name, contact_phone, contact_email,
+               payee_type, bank_card_image, bank_card_no, bank_city,
                audit_status, audit_by, audit_time, audit_remark,
                del_flag, create_by, create_time, update_by, update_time, remark
         from account_certification_apply
@@ -53,6 +69,9 @@
         select a.id, a.user_id, a.subject_type, a.real_name, a.id_card, a.company_name, a.credit_code,
                a.legal_person, a.registered_region, a.company_address, a.register_date,
                a.license_valid_period, a.license_image,
+               a.license_valid_type, a.legal_id_card_front, a.legal_id_card_back, a.legal_id_valid_type,
+               a.legal_id_valid_period, a.legal_id_address, a.contact_name, a.contact_phone, a.contact_email,
+               a.payee_type, a.bank_card_image, a.bank_card_no, a.bank_city,
                a.audit_status, a.audit_by, a.audit_time, a.audit_remark,
                a.del_flag, a.create_by, a.create_time, a.update_by, a.update_time, a.remark,
                u.phone, u.system_level
@@ -113,4 +132,111 @@
         where id = #{id} and del_flag = '0' and audit_status = '0'
     </update>
 
+    <!--
+        C端会员提交认证申请:把资料整份快照落库,审核结果/审核人/审核时间此时为空,
+        由后台审核时回写。id 为自增主键,用 useGeneratedKeys 回填后作为申请号,
+        后台列表「申请号」列展示的就是它。
+        各列均带 <if>:个人主体只填姓名与身份证号,企业字段留空即不落列。
+    -->
+    <insert id="insertCertificationApply" parameterType="AccountCertificationApply" useGeneratedKeys="true" keyProperty="id">
+        insert into account_certification_apply(
+            <if test="userId != null">user_id,</if>
+            <if test="subjectType != null">subject_type,</if>
+            <if test="realName != null">real_name,</if>
+            <if test="idCard != null">id_card,</if>
+            <if test="companyName != null">company_name,</if>
+            <if test="creditCode != null">credit_code,</if>
+            <if test="legalPerson != null">legal_person,</if>
+            <if test="registeredRegion != null">registered_region,</if>
+            <if test="companyAddress != null">company_address,</if>
+            <if test="registerDate != null">register_date,</if>
+            <if test="licenseValidPeriod != null">license_valid_period,</if>
+            <if test="licenseImage != null">license_image,</if>
+            <if test="licenseValidType != null">license_valid_type,</if>
+            <if test="legalIdCardFront != null">legal_id_card_front,</if>
+            <if test="legalIdCardBack != null">legal_id_card_back,</if>
+            <if test="legalIdValidType != null">legal_id_valid_type,</if>
+            <if test="legalIdValidPeriod != null">legal_id_valid_period,</if>
+            <if test="legalIdAddress != null">legal_id_address,</if>
+            <if test="contactName != null">contact_name,</if>
+            <if test="contactPhone != null">contact_phone,</if>
+            <if test="contactEmail != null">contact_email,</if>
+            <if test="payeeType != null">payee_type,</if>
+            <if test="bankCardImage != null">bank_card_image,</if>
+            <if test="bankCardNo != null">bank_card_no,</if>
+            <if test="bankCity != null">bank_city,</if>
+            <if test="auditStatus != null">audit_status,</if>
+            <if test="remark != null">remark,</if>
+            <if test="createBy != null and createBy != ''">create_by,</if>
+            create_time
+        )values(
+            <if test="userId != null">#{userId},</if>
+            <if test="subjectType != null">#{subjectType},</if>
+            <if test="realName != null">#{realName},</if>
+            <if test="idCard != null">#{idCard},</if>
+            <if test="companyName != null">#{companyName},</if>
+            <if test="creditCode != null">#{creditCode},</if>
+            <if test="legalPerson != null">#{legalPerson},</if>
+            <if test="registeredRegion != null">#{registeredRegion},</if>
+            <if test="companyAddress != null">#{companyAddress},</if>
+            <if test="registerDate != null">#{registerDate},</if>
+            <if test="licenseValidPeriod != null">#{licenseValidPeriod},</if>
+            <if test="licenseImage != null">#{licenseImage},</if>
+            <if test="licenseValidType != null">#{licenseValidType},</if>
+            <if test="legalIdCardFront != null">#{legalIdCardFront},</if>
+            <if test="legalIdCardBack != null">#{legalIdCardBack},</if>
+            <if test="legalIdValidType != null">#{legalIdValidType},</if>
+            <if test="legalIdValidPeriod != null">#{legalIdValidPeriod},</if>
+            <if test="legalIdAddress != null">#{legalIdAddress},</if>
+            <if test="contactName != null">#{contactName},</if>
+            <if test="contactPhone != null">#{contactPhone},</if>
+            <if test="contactEmail != null">#{contactEmail},</if>
+            <if test="payeeType != null">#{payeeType},</if>
+            <if test="bankCardImage != null">#{bankCardImage},</if>
+            <if test="bankCardNo != null">#{bankCardNo},</if>
+            <if test="bankCity != null">#{bankCity},</if>
+            <if test="auditStatus != null">#{auditStatus},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="createBy != null and createBy != ''">#{createBy},</if>
+            sysdate()
+        )
+    </insert>
+
+    <!--
+        统计该会员「待审核」的申请数:C端提交前用它做防重闸门。
+        只数 audit_status='0' 的行——已通过(auth_status 已是 '2')或已驳回
+        (会员要改资料重提)都不该拦住新的提交,否则会员被驳回一次就再也交不了。
+        个人与企业/个体按主体类型隔离:subject_type='1' 为个人,个人与企业的待审互不干扰。
+    -->
+    <select id="countPendingPersonalApplyByUserId" parameterType="Long" resultType="int">
+        select count(1) from account_certification_apply
+        where del_flag = '0' and audit_status = '0' and subject_type = '1' and user_id = #{userId}
+    </select>
+
+    <!-- 企业/个体待审核申请数:subject_type in ('2','3') -->
+    <select id="countPendingCompanyApplyByUserId" parameterType="Long" resultType="int">
+        select count(1) from account_certification_apply
+        where del_flag = '0' and audit_status = '0' and subject_type in ('2','3') and user_id = #{userId}
+    </select>
+
+    <!--
+        C端进入认证页时回填:取该会员最近一次个人认证申请(按主键倒序取一条)。
+        驳回后重提会多一条,最近一条即当前应展示的状态与资料;
+        此处不过滤 audit_status——未提交/待审/通过/驳回都需要能回填。
+    -->
+    <select id="selectLatestPersonalApplyByUserId" parameterType="Long" resultMap="AccountCertificationApplyResult">
+        <include refid="selectCertificationApplyVo"/>
+        where user_id = #{userId} and subject_type = '1' and del_flag = '0'
+        order by id desc
+        limit 1
+    </select>
+
+    <!-- 企业/个体最近一次认证申请:subject_type in ('2','3') -->
+    <select id="selectLatestCompanyApplyByUserId" parameterType="Long" resultMap="AccountCertificationApplyResult">
+        <include refid="selectCertificationApplyVo"/>
+        where user_id = #{userId} and subject_type in ('2','3') and del_flag = '0'
+        order by id desc
+        limit 1
+    </select>
+
 </mapper>
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountCertificationMapper.xml b/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountCertificationMapper.xml
index 82b0ef7..94efa62 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountCertificationMapper.xml
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountCertificationMapper.xml
@@ -17,6 +17,19 @@
         <result property="registerDate"       column="register_date"        />
         <result property="licenseValidPeriod" column="license_valid_period" />
         <result property="licenseImage"       column="license_image"        />
+        <result property="licenseValidType"   column="license_valid_type"   />
+        <result property="legalIdCardFront"   column="legal_id_card_front"  />
+        <result property="legalIdCardBack"    column="legal_id_card_back"   />
+        <result property="legalIdValidType"   column="legal_id_valid_type"  />
+        <result property="legalIdValidPeriod" column="legal_id_valid_period"/>
+        <result property="legalIdAddress"     column="legal_id_address"     />
+        <result property="contactName"        column="contact_name"         />
+        <result property="contactPhone"       column="contact_phone"        />
+        <result property="contactEmail"       column="contact_email"        />
+        <result property="payeeType"          column="payee_type"           />
+        <result property="bankCardImage"      column="bank_card_image"      />
+        <result property="bankCardNo"         column="bank_card_no"         />
+        <result property="bankCity"           column="bank_city"            />
         <result property="delFlag"            column="del_flag"             />
         <result property="createBy"           column="create_by"            />
         <result property="createTime"         column="create_time"          />
@@ -28,6 +41,9 @@
     <sql id="selectCertificationVo">
         select id, user_id, real_name, id_card, company_name, credit_code, legal_person,
                registered_region, company_address, register_date, license_valid_period, license_image,
+               license_valid_type, legal_id_card_front, legal_id_card_back, legal_id_valid_type,
+               legal_id_valid_period, legal_id_address, contact_name, contact_phone, contact_email,
+               payee_type, bank_card_image, bank_card_no, bank_city,
                del_flag, create_by, create_time, update_by, update_time, remark
         from account_certification
     </sql>
@@ -55,6 +71,19 @@
             <if test="registerDate != null">register_date,</if>
             <if test="licenseValidPeriod != null">license_valid_period,</if>
             <if test="licenseImage != null">license_image,</if>
+            <if test="licenseValidType != null">license_valid_type,</if>
+            <if test="legalIdCardFront != null">legal_id_card_front,</if>
+            <if test="legalIdCardBack != null">legal_id_card_back,</if>
+            <if test="legalIdValidType != null">legal_id_valid_type,</if>
+            <if test="legalIdValidPeriod != null">legal_id_valid_period,</if>
+            <if test="legalIdAddress != null">legal_id_address,</if>
+            <if test="contactName != null">contact_name,</if>
+            <if test="contactPhone != null">contact_phone,</if>
+            <if test="contactEmail != null">contact_email,</if>
+            <if test="payeeType != null">payee_type,</if>
+            <if test="bankCardImage != null">bank_card_image,</if>
+            <if test="bankCardNo != null">bank_card_no,</if>
+            <if test="bankCity != null">bank_city,</if>
             <if test="remark != null">remark,</if>
             <if test="createBy != null and createBy != ''">create_by,</if>
             create_time
@@ -70,6 +99,19 @@
             <if test="registerDate != null">#{registerDate},</if>
             <if test="licenseValidPeriod != null">#{licenseValidPeriod},</if>
             <if test="licenseImage != null">#{licenseImage},</if>
+            <if test="licenseValidType != null">#{licenseValidType},</if>
+            <if test="legalIdCardFront != null">#{legalIdCardFront},</if>
+            <if test="legalIdCardBack != null">#{legalIdCardBack},</if>
+            <if test="legalIdValidType != null">#{legalIdValidType},</if>
+            <if test="legalIdValidPeriod != null">#{legalIdValidPeriod},</if>
+            <if test="legalIdAddress != null">#{legalIdAddress},</if>
+            <if test="contactName != null">#{contactName},</if>
+            <if test="contactPhone != null">#{contactPhone},</if>
+            <if test="contactEmail != null">#{contactEmail},</if>
+            <if test="payeeType != null">#{payeeType},</if>
+            <if test="bankCardImage != null">#{bankCardImage},</if>
+            <if test="bankCardNo != null">#{bankCardNo},</if>
+            <if test="bankCity != null">#{bankCity},</if>
             <if test="remark != null">#{remark},</if>
             <if test="createBy != null and createBy != ''">#{createBy},</if>
             sysdate()
@@ -89,6 +131,19 @@
             <if test="registerDate != null">register_date = #{registerDate},</if>
             <if test="licenseValidPeriod != null">license_valid_period = #{licenseValidPeriod},</if>
             <if test="licenseImage != null">license_image = #{licenseImage},</if>
+            <if test="licenseValidType != null">license_valid_type = #{licenseValidType},</if>
+            <if test="legalIdCardFront != null">legal_id_card_front = #{legalIdCardFront},</if>
+            <if test="legalIdCardBack != null">legal_id_card_back = #{legalIdCardBack},</if>
+            <if test="legalIdValidType != null">legal_id_valid_type = #{legalIdValidType},</if>
+            <if test="legalIdValidPeriod != null">legal_id_valid_period = #{legalIdValidPeriod},</if>
+            <if test="legalIdAddress != null">legal_id_address = #{legalIdAddress},</if>
+            <if test="contactName != null">contact_name = #{contactName},</if>
+            <if test="contactPhone != null">contact_phone = #{contactPhone},</if>
+            <if test="contactEmail != null">contact_email = #{contactEmail},</if>
+            <if test="payeeType != null">payee_type = #{payeeType},</if>
+            <if test="bankCardImage != null">bank_card_image = #{bankCardImage},</if>
+            <if test="bankCardNo != null">bank_card_no = #{bankCardNo},</if>
+            <if test="bankCity != null">bank_city = #{bankCity},</if>
             <if test="remark != null">remark = #{remark},</if>
             <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
             update_time = sysdate()
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountUserMapper.xml b/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountUserMapper.xml
index 12f7ab4..be38ef6 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountUserMapper.xml
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountUserMapper.xml
@@ -17,6 +17,7 @@
         <result property="systemLevel"     column="system_level"      />
         <result property="subjectType"     column="subject_type"      />
         <result property="authStatus"      column="auth_status"       />
+        <result property="companyAuthStatus" column="company_auth_status" />
         <result property="city"            column="city"              />
         <result property="referrerId"      column="referrer_id"       />
         <result property="referrerBindTime" column="referrer_bind_time" />
@@ -53,7 +54,7 @@
 
     <sql id="selectAccountUserVo">
         select user_id, username, phone, password, nickname, avatar, gender, birthday, status,
-               system_level, subject_type, auth_status, city,
+               system_level, subject_type, auth_status, company_auth_status, city,
                referrer_id, referrer_bind_time, supplier_id, supplier_bind_time,
                buy_order_count, buy_amount_total, sell_order_count, sell_amount_total,
                purchase_order_count, purchase_amount_total, sell_profit_total,
@@ -245,6 +246,38 @@
     </update>
 
     <!--
+        企业/个体认证审核通过/驳回后回写,只动 company_auth_status 一列;
+        与 updateUserAuthStatus 分开是因为企业认证状态与个人认证状态是两列,不能互相覆盖。
+        审核通过时服务层把 subjectType 一起带上(=2/3),此处 <if> 才写 subject_type;
+        驳回时服务层不传 subjectType,列保持不变——否则会把未通过审核的会员标成企业主体。
+    -->
+    <update id="updateUserCompanyAuthStatus" parameterType="AccountUser">
+        update account_user
+        <set>
+            company_auth_status = #{companyAuthStatus},
+            <if test="subjectType != null and subjectType != ''">subject_type = #{subjectType},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            update_time = sysdate()
+        </set>
+        where user_id = #{userId} and del_flag = '0'
+    </update>
+
+    <!--
+        头像更新专用:只动 avatar 一列。绝不能挂到共享的 updateAccountUser 上——
+        那是后台编辑接口(/account/user)的入口,挂上去等于请求体带个 avatar 就能改任意会员头像。
+        与 updateUserStatus / updateUserAuthStatus 同一套路,头像只能由会员本人在 C 端接口里改写。
+    -->
+    <update id="updateUserAvatar" parameterType="AccountUser">
+        update account_user
+        <set>
+            avatar = #{avatar},
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            update_time = sysdate()
+        </set>
+        where user_id = #{userId} and del_flag = '0'
+    </update>
+
+    <!--
         服务商标识专用:与 updateUserAuthStatus 同一个套路——别的模块要给会员打标识,
         就单开一条只写该列的语句,绝不挂到共享的 insertAccountUser / updateAccountUser 上
         (那两个按 <if> 拼列,挂上去等于 /account/user 的请求体里带个 providerFlag 就能自己打标)。
@@ -272,6 +305,18 @@
         </foreach>
     </update>
 
+    <!--
+        「我邀请的」列表:按 referrer_id 过滤 + referrer_bind_time 倒序,命中 idx_account_user_referrer。
+        显式列出所需列(禁 SELECT *);不挂 join,避免干扰 PageHelper 的 count SQL。
+        referrer_bind_time 为空的历史行,MySQL 中 NULL 视为最小值,倒序时自然排在最后。
+    -->
+    <select id="selectInvitees" parameterType="Long" resultMap="AccountUserResult">
+        select user_id, nickname, avatar, phone, system_level, referrer_bind_time, buy_amount_total
+        from account_user
+        where del_flag = '0' and referrer_id = #{referrerId}
+        order by referrer_bind_time desc
+    </select>
+
     <resultMap type="MemberOptionVo" id="MemberOptionVoResult">
         <id     property="userId"      column="user_id"      />
         <result property="memberName"  column="member_name"  />
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/test/java/com/ruoyi/user/service/impl/AccountCertificationApplyServiceImplTest.java b/hugray-server/ruoyi-modules/ruoyi-user/src/test/java/com/ruoyi/user/service/impl/AccountCertificationApplyServiceImplTest.java
new file mode 100644
index 0000000..afc1492
--- /dev/null
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/test/java/com/ruoyi/user/service/impl/AccountCertificationApplyServiceImplTest.java
@@ -0,0 +1,292 @@
+package com.ruoyi.user.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.user.domain.AccountCertificationApply;
+import com.ruoyi.user.domain.AccountUser;
+import com.ruoyi.user.domain.dto.CompanyCertificationApplyDto;
+import com.ruoyi.user.domain.vo.AccountCertificationStatusVo;
+import com.ruoyi.user.domain.vo.CompanyCertificationStatusVo;
+import com.ruoyi.user.mapper.AccountCertificationApplyMapper;
+import com.ruoyi.user.mapper.AccountCertificationMapper;
+import com.ruoyi.user.mapper.AccountUserMapper;
+
+/**
+ * 会员主体认证申请 服务单元测试
+ *
+ * 覆盖 C 端个人主体认证提交:正常提交(落待审核申请 + 联动认证状态为审核中)、
+ * 待审核申请存在时防重拒绝、以及会员ID/姓名/身份证号缺失时的入参校验;
+ * 另覆盖认证状态查询:有申请时回填资料与驳回原因、无申请/无登录态时返回空对象。
+ *
+ * @author hugray
+ */
+@ExtendWith(MockitoExtension.class)
+class AccountCertificationApplyServiceImplTest
+{
+    private static final Long USER_ID = 66L;
+
+    private static final String REAL_NAME = "张三";
+
+    private static final String ID_CARD = "110101199003071234";
+
+    @Mock
+    private AccountCertificationApplyMapper certificationApplyMapper;
+
+    @Mock
+    private AccountCertificationMapper certificationMapper;
+
+    @Mock
+    private AccountUserMapper accountUserMapper;
+
+    @InjectMocks
+    private AccountCertificationApplyServiceImpl certificationApplyService;
+
+    @Test
+    @DisplayName("正常提交应落一条待审核的个人申请并把认证状态置为审核中")
+    void submitPersonalCertification_shouldInsertPendingApplyAndSetAuditing()
+    {
+        when(certificationApplyMapper.countPendingPersonalApplyByUserId(USER_ID)).thenReturn(0);
+        when(certificationApplyMapper.insertCertificationApply(any(AccountCertificationApply.class))).thenReturn(1);
+
+        certificationApplyService.submitPersonalCertification(USER_ID, "  张三  ", "11010119900307123x");
+
+        // 申请快照:个人主体、待审核、姓名去空白、身份证号末位小写 x 统一转大写
+        ArgumentCaptor<AccountCertificationApply> applyCaptor = ArgumentCaptor.forClass(AccountCertificationApply.class);
+        verify(certificationApplyMapper).insertCertificationApply(applyCaptor.capture());
+        AccountCertificationApply saved = applyCaptor.getValue();
+        assertEquals(USER_ID, saved.getUserId());
+        assertEquals("1", saved.getSubjectType());
+        assertEquals("0", saved.getAuditStatus());
+        assertEquals(REAL_NAME, saved.getRealName());
+        assertEquals("11010119900307123X", saved.getIdCard());
+
+        // 状态联动:account_user.auth_status = '1'(审核中)
+        ArgumentCaptor<AccountUser> userCaptor = ArgumentCaptor.forClass(AccountUser.class);
+        verify(accountUserMapper).updateUserAuthStatus(userCaptor.capture());
+        assertEquals(USER_ID, userCaptor.getValue().getUserId());
+        assertEquals("1", userCaptor.getValue().getAuthStatus());
+    }
+
+    @Test
+    @DisplayName("已有待审核申请时应拒绝重复提交且不落库")
+    void submitPersonalCertification_shouldThrow_whenPendingApplyExists()
+    {
+        when(certificationApplyMapper.countPendingPersonalApplyByUserId(USER_ID)).thenReturn(1);
+
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> certificationApplyService.submitPersonalCertification(USER_ID, REAL_NAME, ID_CARD));
+
+        assertEquals("已有实名认证申请正在审核中,请勿重复提交", ex.getMessage());
+        verify(certificationApplyMapper, never()).insertCertificationApply(any(AccountCertificationApply.class));
+    }
+
+    @Test
+    @DisplayName("会员ID为空时应抛出提交失败")
+    void submitPersonalCertification_shouldThrow_whenUserIdNull()
+    {
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> certificationApplyService.submitPersonalCertification(null, REAL_NAME, ID_CARD));
+
+        assertEquals("提交实名认证失败", ex.getMessage());
+        verify(certificationApplyMapper, never()).insertCertificationApply(any(AccountCertificationApply.class));
+    }
+
+    @Test
+    @DisplayName("姓名为空时应抛出提示且不落库")
+    void submitPersonalCertification_shouldThrow_whenRealNameBlank()
+    {
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> certificationApplyService.submitPersonalCertification(USER_ID, "", ID_CARD));
+
+        assertEquals("姓名与身份证号不能为空", ex.getMessage());
+        verify(certificationApplyMapper, never()).insertCertificationApply(any(AccountCertificationApply.class));
+    }
+
+    @Test
+    @DisplayName("身份证号为空时应抛出提示且不落库")
+    void submitPersonalCertification_shouldThrow_whenIdCardBlank()
+    {
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> certificationApplyService.submitPersonalCertification(USER_ID, REAL_NAME, null));
+
+        assertEquals("姓名与身份证号不能为空", ex.getMessage());
+        verify(certificationApplyMapper, never()).insertCertificationApply(any(AccountCertificationApply.class));
+    }
+
+    @Test
+    @DisplayName("查询认证状态应回填最近一次申请的资料与驳回原因")
+    void getCertificationStatus_shouldFillFromLatestApply()
+    {
+        AccountCertificationApply apply = new AccountCertificationApply();
+        apply.setRealName(REAL_NAME);
+        apply.setIdCard(ID_CARD);
+        apply.setAuditStatus("2");
+        apply.setAuditRemark("身份证照片不清晰,请重新提交");
+        when(certificationApplyMapper.selectLatestPersonalApplyByUserId(USER_ID)).thenReturn(apply);
+
+        AccountCertificationStatusVo vo = certificationApplyService.getCertificationStatus(USER_ID);
+
+        assertEquals(REAL_NAME, vo.getRealName());
+        assertEquals(ID_CARD, vo.getIdCard());
+        assertEquals("2", vo.getAuditStatus());
+        assertEquals("身份证照片不清晰,请重新提交", vo.getAuditRemark());
+    }
+
+    @Test
+    @DisplayName("从未提交时查询认证状态应返回空对象而非 null")
+    void getCertificationStatus_shouldReturnEmpty_whenNoApply()
+    {
+        when(certificationApplyMapper.selectLatestPersonalApplyByUserId(USER_ID)).thenReturn(null);
+
+        AccountCertificationStatusVo vo = certificationApplyService.getCertificationStatus(USER_ID);
+
+        assertNull(vo.getRealName());
+        assertNull(vo.getIdCard());
+        assertNull(vo.getAuditStatus());
+        assertNull(vo.getAuditRemark());
+    }
+
+    @Test
+    @DisplayName("会员ID为空时查询认证状态应返回空对象且不查库")
+    void getCertificationStatus_shouldReturnEmpty_whenUserIdNull()
+    {
+        AccountCertificationStatusVo vo = certificationApplyService.getCertificationStatus(null);
+
+        assertNull(vo.getAuditStatus());
+        verify(certificationApplyMapper, never()).selectLatestPersonalApplyByUserId(any());
+    }
+
+    @Test
+    @DisplayName("正常提交应落一条待审核的企业申请并把企业认证状态置为审核中")
+    void submitCompanyCertification_shouldInsertPendingApplyAndSetCompanyAuditing()
+    {
+        when(certificationApplyMapper.countPendingCompanyApplyByUserId(USER_ID)).thenReturn(0);
+        when(certificationApplyMapper.insertCertificationApply(any(AccountCertificationApply.class))).thenReturn(1);
+
+        certificationApplyService.submitCompanyCertification(USER_ID, buildCompanyDto("3", "1", null));
+
+        // 申请快照:企业主体(3)、待审核、无有效期(长期)
+        ArgumentCaptor<AccountCertificationApply> applyCaptor = ArgumentCaptor.forClass(AccountCertificationApply.class);
+        verify(certificationApplyMapper).insertCertificationApply(applyCaptor.capture());
+        AccountCertificationApply saved = applyCaptor.getValue();
+        assertEquals(USER_ID, saved.getUserId());
+        assertEquals("3", saved.getSubjectType());
+        assertEquals("0", saved.getAuditStatus());
+        assertEquals("测试企业有限公司", saved.getCompanyName());
+
+        // 状态联动:account_user.company_auth_status = '1'(审核中),且不动个人 auth_status
+        ArgumentCaptor<AccountUser> userCaptor = ArgumentCaptor.forClass(AccountUser.class);
+        verify(accountUserMapper).updateUserCompanyAuthStatus(userCaptor.capture());
+        assertEquals("1", userCaptor.getValue().getCompanyAuthStatus());
+        verify(accountUserMapper, never()).updateUserAuthStatus(any(AccountUser.class));
+    }
+
+    @Test
+    @DisplayName("有效期选「短期」但未填起止日期时应抛出提示且不落库")
+    void submitCompanyCertification_shouldThrow_whenShortTermPeriodMissing()
+    {
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> certificationApplyService.submitCompanyCertification(USER_ID, buildCompanyDto("3", "2", null)));
+
+        assertEquals("请选择营业执照有效期起止日期", ex.getMessage());
+        verify(certificationApplyMapper, never()).insertCertificationApply(any(AccountCertificationApply.class));
+    }
+
+    @Test
+    @DisplayName("已有待审核的企业申请时应拒绝重复提交且不落库")
+    void submitCompanyCertification_shouldThrow_whenPendingApplyExists()
+    {
+        when(certificationApplyMapper.countPendingCompanyApplyByUserId(USER_ID)).thenReturn(1);
+
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> certificationApplyService.submitCompanyCertification(USER_ID, buildCompanyDto("2", "1", null)));
+
+        assertEquals("已有企业认证申请正在审核中,请勿重复提交", ex.getMessage());
+        verify(certificationApplyMapper, never()).insertCertificationApply(any(AccountCertificationApply.class));
+    }
+
+    @Test
+    @DisplayName("会员ID为空时提交企业认证应抛出提交失败")
+    void submitCompanyCertification_shouldThrow_whenUserIdNull()
+    {
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> certificationApplyService.submitCompanyCertification(null, buildCompanyDto("3", "1", null)));
+
+        assertEquals("提交企业认证失败", ex.getMessage());
+        verify(certificationApplyMapper, never()).insertCertificationApply(any(AccountCertificationApply.class));
+    }
+
+    @Test
+    @DisplayName("查询企业认证状态应回填最近一次申请的资料与驳回原因")
+    void getCompanyCertificationStatus_shouldFillFromLatestApply()
+    {
+        AccountCertificationApply apply = new AccountCertificationApply();
+        apply.setSubjectType("3");
+        apply.setCompanyName("测试企业有限公司");
+        apply.setAuditStatus("2");
+        apply.setAuditRemark("营业执照照片不清晰,请重新提交");
+        when(certificationApplyMapper.selectLatestCompanyApplyByUserId(USER_ID)).thenReturn(apply);
+
+        CompanyCertificationStatusVo vo = certificationApplyService.getCompanyCertificationStatus(USER_ID);
+
+        assertEquals("3", vo.getSubjectType());
+        assertEquals("测试企业有限公司", vo.getCompanyName());
+        assertEquals("2", vo.getAuditStatus());
+        assertEquals("营业执照照片不清晰,请重新提交", vo.getAuditRemark());
+    }
+
+    @Test
+    @DisplayName("从未提交企业认证时查询状态应返回空对象而非 null")
+    void getCompanyCertificationStatus_shouldReturnEmpty_whenNoApply()
+    {
+        when(certificationApplyMapper.selectLatestCompanyApplyByUserId(USER_ID)).thenReturn(null);
+
+        CompanyCertificationStatusVo vo = certificationApplyService.getCompanyCertificationStatus(USER_ID);
+
+        assertNull(vo.getCompanyName());
+        assertNull(vo.getAuditStatus());
+        assertNull(vo.getAuditRemark());
+    }
+
+    /** 构造一份字段齐全的企业认证入参:validType 控制营业执照有效期类型,licensePeriod 为起止日期 */
+    private CompanyCertificationApplyDto buildCompanyDto(String subjectType, String validType, String licensePeriod)
+    {
+        CompanyCertificationApplyDto dto = new CompanyCertificationApplyDto();
+        dto.setSubjectType(subjectType);
+        dto.setCompanyName("测试企业有限公司");
+        dto.setLicenseImage("/upload/license.png");
+        dto.setCreditCode("91310000MA1FL0XXXX");
+        dto.setLicenseValidType(validType);
+        dto.setLicenseValidPeriod(licensePeriod);
+        dto.setRegisteredRegion("上海市 上海市 浦东新区");
+        dto.setCompanyAddress("浦东新区世纪大道 1 号");
+        dto.setLegalPerson("李四");
+        dto.setLegalIdCardFront("/upload/id-front.png");
+        dto.setLegalIdCardBack("/upload/id-back.png");
+        dto.setLegalIdValidType("1");
+        dto.setLegalIdValidPeriod(null);
+        dto.setLegalIdAddress("浦东新区世纪大道 1 号");
+        dto.setContactName("王五");
+        dto.setContactPhone("13800000000");
+        dto.setContactEmail("test@example.com");
+        dto.setPayeeType("1");
+        dto.setBankCardImage("/upload/bank.png");
+        dto.setBankCardNo("6222020000000000000");
+        dto.setBankCity("上海市");
+        return dto;
+    }
+}
diff --git a/hugray-server/ruoyi-modules/ruoyi-user/src/test/java/com/ruoyi/user/service/impl/AccountUserServiceImplTest.java b/hugray-server/ruoyi-modules/ruoyi-user/src/test/java/com/ruoyi/user/service/impl/AccountUserServiceImplTest.java
index e638e85..5aff6da 100644
--- a/hugray-server/ruoyi-modules/ruoyi-user/src/test/java/com/ruoyi/user/service/impl/AccountUserServiceImplTest.java
+++ b/hugray-server/ruoyi-modules/ruoyi-user/src/test/java/com/ruoyi/user/service/impl/AccountUserServiceImplTest.java
@@ -140,6 +140,7 @@
         member.setPhone("13355555555");
         member.setAvatar("https://cdn.example.com/a.png");
         member.setSystemLevel(MemberLevelEnum.L3.getCode());
+        member.setAuthStatus("2");
         EarningSummaryVo summary = new EarningSummaryVo(1000L, 40000L, 12800000L);
         when(accountUserMapper.selectAccountUserById(66L)).thenReturn(member);
         when(accountEarningRecordMapper.selectEarningSummary(66L)).thenReturn(summary);
@@ -151,6 +152,8 @@
         assertEquals("133****5555", vo.getPhone());
         assertEquals("https://cdn.example.com/a.png", vo.getAvatar());
         assertEquals(MemberLevelEnum.L3.getCode(), vo.getMemberLevel());
+        // 认证状态原样下发,供个人中心「实名认证」行徽标回显
+        assertEquals("2", vo.getAuthStatus());
         assertEquals(1000L, vo.getEarnings().getToday());
         assertEquals(40000L, vo.getEarnings().getMonth());
         assertEquals(12800000L, vo.getEarnings().getTotal());
@@ -209,6 +212,21 @@
         assertFallbackLevel("L9", MemberLevelEnum.C.getCode());
     }
 
+    @Test
+    @DisplayName("认证状态为空时应兜底为未认证(0)")
+    void getAccountUserInfo_shouldFallbackNoneAuthStatus_whenBlank()
+    {
+        AccountUser member = new AccountUser();
+        member.setUserId(66L);
+        member.setSystemLevel(MemberLevelEnum.C.getCode());
+        when(accountUserMapper.selectAccountUserById(66L)).thenReturn(member);
+        when(accountEarningRecordMapper.selectEarningSummary(66L)).thenReturn(EarningSummaryVo.zero());
+
+        AccountUserInfoVo vo = accountUserService.getAccountUserInfo(66L);
+
+        assertEquals("0", vo.getAuthStatus());
+    }
+
     /**
      * 校验不同等级取值下的等级兜底结果
      *
@@ -227,4 +245,64 @@
 
         assertEquals(expectedLevel, vo.getMemberLevel());
     }
+
+    @Test
+    @DisplayName("更新头像时应只写入 avatar 列")
+    void updateAvatar_shouldUpdateAvatar_whenValid()
+    {
+        when(accountUserMapper.updateUserAvatar(any(AccountUser.class))).thenReturn(1);
+
+        accountUserService.updateAvatar(66L, "https://cdn.example.com/new.png");
+
+        ArgumentCaptor<AccountUser> captor = ArgumentCaptor.forClass(AccountUser.class);
+        verify(accountUserMapper).updateUserAvatar(captor.capture());
+        assertEquals(66L, captor.getValue().getUserId());
+        assertEquals("https://cdn.example.com/new.png", captor.getValue().getAvatar());
+    }
+
+    @Test
+    @DisplayName("头像地址为空时应抛出提示且不落库")
+    void updateAvatar_shouldThrow_whenAvatarBlank()
+    {
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> accountUserService.updateAvatar(66L, ""));
+
+        assertEquals("头像地址不能为空", ex.getMessage());
+        verify(accountUserMapper, never()).updateUserAvatar(any(AccountUser.class));
+    }
+
+    @Test
+    @DisplayName("头像地址超长时应抛出提示且不落库")
+    void updateAvatar_shouldThrow_whenAvatarTooLong()
+    {
+        String longUrl = "a".repeat(256);
+
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> accountUserService.updateAvatar(66L, longUrl));
+
+        assertEquals("头像地址过长", ex.getMessage());
+        verify(accountUserMapper, never()).updateUserAvatar(any(AccountUser.class));
+    }
+
+    @Test
+    @DisplayName("会员不存在(影响行数为 0)时应抛出更新失败")
+    void updateAvatar_shouldThrow_whenMemberNotFound()
+    {
+        when(accountUserMapper.updateUserAvatar(any(AccountUser.class))).thenReturn(0);
+
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> accountUserService.updateAvatar(66L, "https://cdn.example.com/new.png"));
+
+        assertEquals("更新头像失败", ex.getMessage());
+    }
+
+    @Test
+    @DisplayName("会员ID为空时应抛出更新失败")
+    void updateAvatar_shouldThrow_whenUserIdNull()
+    {
+        ServiceException ex = assertThrows(ServiceException.class,
+            () -> accountUserService.updateAvatar(null, "https://cdn.example.com/new.png"));
+
+        assertEquals("更新头像失败", ex.getMessage());
+    }
 }
diff --git a/hugray-server/sql/hugray_account_company_certification_20261013.sql b/hugray-server/sql/hugray_account_company_certification_20261013.sql
new file mode 100644
index 0000000..eeac27f
--- /dev/null
+++ b/hugray-server/sql/hugray_account_company_certification_20261013.sql
@@ -0,0 +1,113 @@
+-- ----------------------------
+-- 企业/个体工商户认证:认证资料表扩充
+--
+-- 目标库 hugray-account(库名含连字符,任何引用都必须用反引号)。
+-- 本脚本为扩充脚本,两张认证表已有数据,一律用 ALTER,绝不 DROP。
+--
+-- 背景:原型「企业/个体认证」四步向导(企业信息 / 法人信息 / 收款信息 / 提交审核)
+-- 所需字段中,营业执照有效期类型、法人身份证正反面、法人身份证有效期、法人身份证详细地址、
+-- 负责人姓名/电话/邮箱、收款类型、银行卡照片、银行卡号、开户城市共 13 列缺失,本次补齐。
+--
+-- 涉及表:
+--   account_certification_apply  存"每次提交的申请",提交企业认证时 subject_type 取 2/3
+--   account_certification        存"当前生效"的认证资料,审核通过时落一份
+--   account_user                 新增 company_auth_status,企业/个体认证状态独立于 auth_status
+--
+-- 【枚举取值】
+--   主体类型 subject_type            :1个人 2个体工商户 3企业
+--   有效期类型 license_valid_type     :1长期 2短期
+--   有效期类型 legal_id_valid_type    :1长期 2短期
+--   企业认证状态 company_auth_status  :0未认证 1审核中 2已认证 3认证失败
+--
+-- 【幂等说明】复用 hugray_add_column 临时存储过程做存在性判断,脚本可安全重复执行。
+--
+-- 【回滚语句】(如需回滚,在 hugray-account 库执行)
+--   ALTER TABLE `account_certification_apply` DROP COLUMN `license_valid_type`,
+--     DROP COLUMN `legal_id_card_front`, DROP COLUMN `legal_id_card_back`,
+--     DROP COLUMN `legal_id_valid_type`, DROP COLUMN `legal_id_valid_period`,
+--     DROP COLUMN `legal_id_address`, DROP COLUMN `contact_name`, DROP COLUMN `contact_phone`,
+--     DROP COLUMN `contact_email`, DROP COLUMN `payee_type`, DROP COLUMN `bank_card_image`,
+--     DROP COLUMN `bank_card_no`, DROP COLUMN `bank_city`;
+--   ALTER TABLE `account_certification` DROP COLUMN `license_valid_type`,
+--     DROP COLUMN `legal_id_card_front`, DROP COLUMN `legal_id_card_back`,
+--     DROP COLUMN `legal_id_valid_type`, DROP COLUMN `legal_id_valid_period`,
+--     DROP COLUMN `legal_id_address`, DROP COLUMN `contact_name`, DROP COLUMN `contact_phone`,
+--     DROP COLUMN `contact_email`, DROP COLUMN `payee_type`, DROP COLUMN `bank_card_image`,
+--     DROP COLUMN `bank_card_no`, DROP COLUMN `bank_city`;
+--   ALTER TABLE `account_user` DROP COLUMN `company_auth_status`;
+-- ----------------------------
+
+USE `hugray-account`;
+
+-- ---------- 幂等辅助过程 ----------
+DROP PROCEDURE IF EXISTS `hugray_add_column`;
+DROP PROCEDURE IF EXISTS `hugray_add_index`;
+
+DELIMITER $$
+CREATE PROCEDURE `hugray_add_column`(IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_definition TEXT)
+BEGIN
+  IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
+                 WHERE TABLE_SCHEMA = DATABASE()
+                   AND TABLE_NAME   = p_table
+                   AND COLUMN_NAME  = p_column) THEN
+    SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition);
+    PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+  END IF;
+END$$
+
+CREATE PROCEDURE `hugray_add_index`(IN p_table VARCHAR(64), IN p_index VARCHAR(64), IN p_columns TEXT)
+BEGIN
+  IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.STATISTICS
+                 WHERE TABLE_SCHEMA = DATABASE()
+                   AND TABLE_NAME   = p_table
+                   AND INDEX_NAME   = p_index) THEN
+    SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD INDEX `', p_index, '` (', p_columns, ')');
+    PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+  END IF;
+END$$
+DELIMITER ;
+
+-- ---------- 1. account_certification_apply:企业/个体认证字段 ----------
+CALL `hugray_add_column`('account_certification_apply', 'license_valid_type',   "varchar(16)  DEFAULT NULL COMMENT '营业执照有效期类型(1长期 2短期)'");
+CALL `hugray_add_column`('account_certification_apply', 'legal_id_card_front',  "varchar(500) DEFAULT NULL COMMENT '法人身份证人像面图片'");
+CALL `hugray_add_column`('account_certification_apply', 'legal_id_card_back',   "varchar(500) DEFAULT NULL COMMENT '法人身份证国徽面图片'");
+CALL `hugray_add_column`('account_certification_apply', 'legal_id_valid_type',  "varchar(16)  DEFAULT NULL COMMENT '法人身份证有效期类型(1长期 2短期)'");
+CALL `hugray_add_column`('account_certification_apply', 'legal_id_valid_period',"varchar(32)  DEFAULT NULL COMMENT '法人身份证有效期(存文本,如 2024-01-01~2034-01-01)'");
+CALL `hugray_add_column`('account_certification_apply', 'legal_id_address',     "varchar(255) DEFAULT NULL COMMENT '法人身份证详细地址'");
+CALL `hugray_add_column`('account_certification_apply', 'contact_name',         "varchar(64)  DEFAULT NULL COMMENT '负责人姓名'");
+CALL `hugray_add_column`('account_certification_apply', 'contact_phone',        "varchar(20)  DEFAULT NULL COMMENT '负责人联系电话'");
+CALL `hugray_add_column`('account_certification_apply', 'contact_email',        "varchar(128) DEFAULT NULL COMMENT '负责人邮箱'");
+CALL `hugray_add_column`('account_certification_apply', 'payee_type',           "varchar(16)  DEFAULT NULL COMMENT '收款类型'");
+CALL `hugray_add_column`('account_certification_apply', 'bank_card_image',      "varchar(500) DEFAULT NULL COMMENT '银行卡号码面照片'");
+CALL `hugray_add_column`('account_certification_apply', 'bank_card_no',         "varchar(32)  DEFAULT NULL COMMENT '银行卡号'");
+CALL `hugray_add_column`('account_certification_apply', 'bank_city',            "varchar(128) DEFAULT NULL COMMENT '开户城市'");
+
+-- ---------- 2. account_certification:企业/个体认证字段(与申请表同构) ----------
+CALL `hugray_add_column`('account_certification', 'license_valid_type',   "varchar(16)  DEFAULT NULL COMMENT '营业执照有效期类型(1长期 2短期)'");
+CALL `hugray_add_column`('account_certification', 'legal_id_card_front',  "varchar(500) DEFAULT NULL COMMENT '法人身份证人像面图片'");
+CALL `hugray_add_column`('account_certification', 'legal_id_card_back',   "varchar(500) DEFAULT NULL COMMENT '法人身份证国徽面图片'");
+CALL `hugray_add_column`('account_certification', 'legal_id_valid_type',  "varchar(16)  DEFAULT NULL COMMENT '法人身份证有效期类型(1长期 2短期)'");
+CALL `hugray_add_column`('account_certification', 'legal_id_valid_period',"varchar(32)  DEFAULT NULL COMMENT '法人身份证有效期(存文本,如 2024-01-01~2034-01-01)'");
+CALL `hugray_add_column`('account_certification', 'legal_id_address',     "varchar(255) DEFAULT NULL COMMENT '法人身份证详细地址'");
+CALL `hugray_add_column`('account_certification', 'contact_name',         "varchar(64)  DEFAULT NULL COMMENT '负责人姓名'");
+CALL `hugray_add_column`('account_certification', 'contact_phone',        "varchar(20)  DEFAULT NULL COMMENT '负责人联系电话'");
+CALL `hugray_add_column`('account_certification', 'contact_email',        "varchar(128) DEFAULT NULL COMMENT '负责人邮箱'");
+CALL `hugray_add_column`('account_certification', 'payee_type',           "varchar(16)  DEFAULT NULL COMMENT '收款类型'");
+CALL `hugray_add_column`('account_certification', 'bank_card_image',      "varchar(500) DEFAULT NULL COMMENT '银行卡号码面照片'");
+CALL `hugray_add_column`('account_certification', 'bank_card_no',         "varchar(32)  DEFAULT NULL COMMENT '银行卡号'");
+CALL `hugray_add_column`('account_certification', 'bank_city',            "varchar(128) DEFAULT NULL COMMENT '开户城市'");
+
+-- ---------- 3. account_user:企业/个体认证状态(独立于 auth_status) ----------
+-- 个人中心需两行独立徽标(实名认证 / 企业个体户认证),故企业状态单独一列,互不覆盖。
+CALL `hugray_add_column`('account_user', 'company_auth_status', "char(1) DEFAULT '0' COMMENT '企业/个体认证状态(0未认证 1审核中 2已认证 3认证失败)'");
+CALL `hugray_add_index`('account_user', 'idx_account_user_company_auth', '`company_auth_status`');
+
+-- ---------- 4. 清理辅助过程 ----------
+DROP PROCEDURE IF EXISTS `hugray_add_column`;
+DROP PROCEDURE IF EXISTS `hugray_add_index`;
+
+-- ---------- 5. 结果核对 ----------
+SELECT `COLUMN_NAME`, `COLUMN_TYPE`, `COLUMN_DEFAULT`, `COLUMN_COMMENT`
+FROM INFORMATION_SCHEMA.COLUMNS
+WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'account_certification_apply'
+ORDER BY `ORDINAL_POSITION`;
diff --git a/hugray-user-miniapp/api/index.js b/hugray-user-miniapp/api/index.js
index cc7e5ec..372c062 100644
--- a/hugray-user-miniapp/api/index.js
+++ b/hugray-user-miniapp/api/index.js
@@ -7,7 +7,7 @@
  *   /miniapp/product/** -> ruoyi-product 商品/订单
  *   /miniapp/system/**  -> ruoyi-system  系统信息
  */
-import { get, post } from '@/utils/request.js'
+import { get, post, uploadFile } from '@/utils/request.js'
 
 // ======================== 认证相关 ========================
 
@@ -109,6 +109,96 @@
     return get('/miniapp/account/user/stock')
 }
 
+/**
+ * 上传头像文件到文件服务,返回 { data: { name, url } }
+ * 说明:走网关 /file/** -> ruoyi-file,需登录态(网关白名单不含 /file/upload)
+ * @param {String} filePath 本地临时文件路径(uni.chooseImage 返回)
+ */
+export function uploadAvatarFile(filePath) {
+    return uploadFile('/file/upload', filePath)
+}
+
+/**
+ * 更新当前登录会员头像(需登录态,userId 由网关注入,前端只传头像地址)
+ * @param {String} avatar 头像地址(来自 uploadAvatarFile 返回的 url)
+ */
+export function updateAvatar(avatar) {
+    return post('/miniapp/account/user/avatar', { avatar })
+}
+
+/**
+ * 查询当前会员实名认证状态(进入认证页回填资料与状态用)
+ * 返回 { realName, idCard, auditStatus, auditRemark }:
+ *   auditStatus 0待审核 1已通过 2已驳回;为空表示从未提交
+ */
+export function getCertificationStatus() {
+    return get('/miniapp/account/user/certification')
+}
+
+/**
+ * 提交实名认证(个人主体,只做基础格式校验,不做三方实名核验)
+ * 说明:会员身份由网关注入,前端只传姓名与身份证号;提交后进入后台「主体认证审核」待审
+ * @param {String} realName 姓名
+ * @param {String} idCard 身份证号
+ */
+export function submitCertification(realName, idCard) {
+    return post('/miniapp/account/user/certification', { realName, idCard })
+}
+
+/**
+ * 查询当前会员企业/个体认证状态与资料(进入向导页回填用)
+ * 返回最近一次企业/个体申请快照(subject_type ∈ {2,3}):
+ *   21 个资料字段 + auditStatus(0待审核 1已通过 2已驳回,为空表示从未提交)+ auditRemark(驳回原因)
+ */
+export function getCompanyCertificationStatus() {
+    return get('/miniapp/account/user/company-certification')
+}
+
+/**
+ * 提交企业/个体认证(只做基础必填校验,不做格式/三方核验)
+ * 说明:会员身份由网关注入,前端不传 userId;提交后进入后台「主体认证审核」待审队列
+ * @param {Object} data 21 个字段(见 CompanyCertificationApplyDto),如 subjectType/companyName/licenseImage 等
+ */
+export function submitCompanyCertification(data) {
+    return post('/miniapp/account/user/company-certification', data)
+}
+
+// ======================== 我的邀请 ========================
+
+/**
+ * 查询本人邀请码(「我的邀请」页邀请码 Tab)
+ * 返回 { referralCode }:邀请码注册时生成,C 端会员必有
+ */
+export function getInviteCode() {
+    return get('/miniapp/account/user/invite-code')
+}
+
+/**
+ * 查询本人邀请码小程序码(扫码直达注册页并自动填入邀请码)
+ * @param {String} code 邀请码(8 位 A-Z0-9),作为小程序码 scene
+ * 返回 { qrcode }:PNG 的 base64 DataURI(data:image/png;base64,...),可直接作为 image src
+ */
+export function getInviteQrcode(code) {
+    return get('/miniapp/auth/invite-qrcode', { code })
+}
+
+/**
+ * 查询我邀请的(直接下级)列表(分页,按绑定时间倒序)
+ * @param {Object} params { pageNum, pageSize }
+ * 说明:后端返回 TableDataInfo,数据在顶层 rows(非 data 包裹),总数为 total
+ */
+export function getInvitees(params) {
+    return get('/miniapp/account/user/invitees', params)
+}
+
+/**
+ * 查询邀请我的(上级/推荐人)信息
+ * 返回 { userId, nickname, avatar, phone, systemLevel, bindDate };无上级时各字段为 null
+ */
+export function getReferrer() {
+    return get('/miniapp/account/user/referrer')
+}
+
 // ======================== 订单相关 ========================
 
 /**
diff --git a/hugray-user-miniapp/pages.json b/hugray-user-miniapp/pages.json
index fdb3bde..a117e14 100644
--- a/hugray-user-miniapp/pages.json
+++ b/hugray-user-miniapp/pages.json
@@ -37,6 +37,27 @@
 			}
 		},
 		{
+			"path": "pages/profile/certification",
+			// 导航栏含更多 / 帮助图标,需自定义导航栏承载
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/profile/company-certification",
+			// 顶部为四段式步骤条,需自定义导航栏承载
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/profile/invite",
+			// 顶部为三段式 Tab + 居中标题,需自定义导航栏承载
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
 			"path": "pages/contact/index",
 			// 顶部为深蓝底品牌区,与白底原生导航冲突,故使用自定义导航栏
 			"style": {
diff --git a/hugray-user-miniapp/pages/login/register.vue b/hugray-user-miniapp/pages/login/register.vue
index c02f96d..62353d8 100644
--- a/hugray-user-miniapp/pages/login/register.vue
+++ b/hugray-user-miniapp/pages/login/register.vue
@@ -100,7 +100,7 @@
 
 <script setup>
 import { ref, reactive, computed } from 'vue'
-import { onUnload } from '@dcloudio/uni-app'
+import { onLoad, onUnload } from '@dcloudio/uni-app'
 import { sendSmsCode, register } from '@/api/index.js'
 
 // ======================== 数据定义区域 ========================
@@ -211,6 +211,23 @@
     uni.navigateBack()
 }
 
+// 页面加载时解析邀请码并回填:
+//   1)分享链接进入 -> options.referralCode
+//   2)扫描小程序码进入 -> options.scene(微信下发的 scene 为 URL 编码,需解码)
+// 统一转大写以匹配后端邀请码字符集(8 位 A-Z0-9),用户后续仍可手动修改
+onLoad((options) => {
+    const raw = (options && (options.referralCode || options.scene)) || ''
+    if (!raw) return
+    let code = raw
+    try {
+        code = decodeURIComponent(raw)
+    } catch (e) {
+        // 非法编码时按原值处理,不阻塞页面
+        console.error('[REGISTER] 邀请码解码失败', e)
+    }
+    form.referralCode = code.trim().toUpperCase()
+})
+
 // 页面卸载时清理倒计时,避免定时器泄漏
 onUnload(() => {
     if (countdownTimer) clearInterval(countdownTimer)
diff --git a/hugray-user-miniapp/pages/profile/certification.vue b/hugray-user-miniapp/pages/profile/certification.vue
new file mode 100644
index 0000000..2688d4e
--- /dev/null
+++ b/hugray-user-miniapp/pages/profile/certification.vue
@@ -0,0 +1,390 @@
+<template>
+    <view class="page">
+        <!-- 自定义导航栏:状态栏留白由行内 paddingTop 撑开 -->
+        <view class="nav" :style="{ paddingTop: statusBarHeight + 'px' }">
+            <view class="nav-bar">
+                <!-- 返回 -->
+                <view class="nav-back" @tap="handleBack">
+                    <view class="icon-back"></view>
+                </view>
+                <text class="nav-title">实名认证</text>
+                <!-- 更多 / 帮助(本期占位) -->
+                <view class="nav-actions">
+                    <view class="nav-action" @tap="handlePlaceholder('更多')">
+                        <view class="icon-more">
+                            <view class="dot"></view>
+                            <view class="dot"></view>
+                            <view class="dot"></view>
+                        </view>
+                    </view>
+                    <view class="nav-action" @tap="handlePlaceholder('帮助')">
+                        <view class="icon-help"><text class="icon-help-text">?</text></view>
+                    </view>
+                </view>
+            </view>
+        </view>
+
+        <view class="body">
+            <!-- 姓名 -->
+            <view class="field">
+                <text class="field-label">姓名</text>
+                <input
+                    class="field-input"
+                    :class="{ 'field-input--readonly': readonly }"
+                    v-model="realName"
+                    type="text"
+                    placeholder="请输入姓名"
+                    placeholder-class="field-placeholder"
+                    maxlength="64"
+                    :disabled="readonly"
+                />
+            </view>
+
+            <!-- 身份证号 -->
+            <view class="field">
+                <text class="field-label">身份证号</text>
+                <input
+                    class="field-input"
+                    :class="{ 'field-input--readonly': readonly }"
+                    v-model="idCard"
+                    type="idcard"
+                    placeholder="请输入身份证号"
+                    placeholder-class="field-placeholder"
+                    maxlength="18"
+                    :disabled="readonly"
+                />
+                <!-- 认证失败:紧贴身份证号下方展示驳回原因 -->
+                <view v-if="showRejectReason" class="reject-tip">
+                    <text class="reject-tip-text">驳回原因:{{ rejectReason }}</text>
+                </view>
+            </view>
+        </view>
+
+        <!-- 底部提交 -->
+        <view class="footer">
+            <view class="btn-submit" :class="{ 'btn-submit--disabled': submitDisabled }" @tap="handleSubmit">
+                <text class="btn-submit-text">{{ submitText }}</text>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
+import { ensureLogin } from '@/utils/request.js'
+import { getCertificationStatus, submitCertification } from '@/api/index.js'
+
+/** 状态栏高度:自定义导航栏下用于顶部留白 */
+const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
+
+/**
+ * 身份证号格式(与后端 AccountCertificationApplyDto 保持一致)
+ * 只校验结构:6 位地址码 + 8 位出生日期 + 3 位顺序码 + 1 位校验位(数字或 X),
+ * 不校验末位校验码,也不做三方实名核验。
+ */
+const ID_CARD_PATTERN = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/
+
+/** 姓名 */
+const realName = ref('')
+
+/** 身份证号 */
+const idCard = ref('')
+
+/** 提交中标记:防止重复点击造成重复提交 */
+const submitting = ref(false)
+
+/**
+ * 认证审核状态(取自最近一次申请,进入页面时回填)
+ * '0' 待审核 / '1' 已通过 / '2' 已驳回 / '' 从未提交(未认证)
+ */
+const auditStatus = ref('')
+
+/** 驳回原因:仅审核状态为「已驳回」时有值 */
+const rejectReason = ref('')
+
+/** 表单只读:认证中 / 认证成功时姓名与身份证号不可编辑 */
+const readonly = computed(() => auditStatus.value === '0' || auditStatus.value === '1')
+
+/** 提交按钮置灰:只读状态或正在提交时不可点 */
+const submitDisabled = computed(() => readonly.value || submitting.value)
+
+/** 是否展示驳回原因:认证失败且后端返回了驳回原因 */
+const showRejectReason = computed(() => auditStatus.value === '2' && !!rejectReason.value)
+
+/**
+ * 按钮文案字典:按审核状态区分
+ * '' 未认证→提交 / '0' 认证中→审核中 / '1' 认证成功→已认证 / '2' 认证失败→重新提交
+ */
+const SUBMIT_TEXT_MAP = {
+    '': '提交',
+    '0': '审核中',
+    '1': '已认证',
+    '2': '重新提交'
+}
+
+/** 底部按钮文案:随认证状态变化,未匹配时兜底「提交」 */
+const submitText = computed(() => SUBMIT_TEXT_MAP[auditStatus.value] || '提交')
+
+/** 返回上一页:无上级页面(如直接进入)时回到个人中心 */
+const handleBack = () => {
+    const pages = getCurrentPages()
+    if (pages.length > 1) {
+        uni.navigateBack()
+        return
+    }
+    uni.switchTab({ url: '/pages/profile/index' })
+}
+
+/** 占位交互:更多 / 帮助本期不接后端 */
+const handlePlaceholder = (name) => {
+    uni.showToast({ title: `${name}功能开发中`, icon: 'none' })
+}
+
+/**
+ * 拉取当前认证状态并回填表单
+ * 认证中 / 认证成功回填姓名与身份证号(只读);认证失败回填资料并展示驳回原因;
+ * 从未提交(auditStatus 为空)保持空表单可编辑。查询失败静默降级为「未认证」,不阻断用户填报。
+ */
+const loadCertificationStatus = async () => {
+    try {
+        // request 返回完整响应体 { code, msg, data },业务数据在 data 字段
+        const res = await getCertificationStatus()
+        const data = res.data || {}
+        // 从未提交时后端返回空字段对象,此处保持空值即为「未认证」
+        auditStatus.value = data.auditStatus || ''
+        realName.value = data.realName || ''
+        idCard.value = data.idCard || ''
+        rejectReason.value = data.auditRemark || ''
+    } catch (error) {
+        console.error('[CERTIFICATION] 查询认证状态失败', error)
+        auditStatus.value = ''
+        rejectReason.value = ''
+    }
+}
+
+/**
+ * 提交实名认证:本地基础校验(姓名必填、身份证号格式)通过后调接口
+ * 成功提示等待审核并返回上一页;失败展示后端返回的业务提示
+ */
+const handleSubmit = async () => {
+    // STEP 0: 只读 / 提交中状态直接拦截,避免重复提交
+    if (submitDisabled.value) return
+    const name = realName.value.trim()
+    const card = idCard.value.trim()
+    // STEP 1: 姓名必填校验
+    if (!name) {
+        uni.showToast({ title: '请输入姓名', icon: 'none' })
+        return
+    }
+    // STEP 2: 身份证号格式校验
+    if (!ID_CARD_PATTERN.test(card)) {
+        uni.showToast({ title: '身份证号格式不正确', icon: 'none' })
+        return
+    }
+    // STEP 3: 锁定提交态并提交到后端,落后台「主体认证审核」待审队列
+    submitting.value = true
+    uni.showLoading({ title: '提交中', mask: true })
+    try {
+        await submitCertification(name, card)
+        uni.hideLoading()
+        uni.showToast({ title: '提交成功,等待审核', icon: 'none' })
+        setTimeout(() => uni.navigateBack(), 1500)
+    } catch (error) {
+        uni.hideLoading()
+        console.error('[CERTIFICATION] 提交实名认证失败', error)
+        uni.showToast({ title: (error && error.message) || '提交失败,请重试', icon: 'none' })
+    } finally {
+        submitting.value = false
+    }
+}
+
+// 进入页面校验登录态并拉取认证状态回填(未登录跳登录页)
+onShow(() => {
+    if (!ensureLogin()) return
+    loadCertificationStatus()
+})
+</script>
+
+<style scoped lang="scss">
+.page {
+    min-height: 100vh;
+    display: flex;
+    flex-direction: column;
+    box-sizing: border-box;
+    background-color: #ffffff;
+}
+
+/* ==================== 自定义导航栏 ==================== */
+
+.nav {
+    flex-shrink: 0;
+    background-color: #ffffff;
+}
+
+.nav-bar {
+    position: relative;
+    height: 88rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.nav-back {
+    position: absolute;
+    left: 16rpx;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 64rpx;
+    height: 64rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+/* 纯 CSS 左向箭头:左 + 下边框旋转 45° */
+.icon-back {
+    width: 22rpx;
+    height: 22rpx;
+    border-left: 4rpx solid #1f2937;
+    border-bottom: 4rpx solid #1f2937;
+    transform: rotate(45deg);
+}
+
+.nav-title {
+    font-size: 34rpx;
+    font-weight: 700;
+    line-height: 1;
+    letter-spacing: 1rpx;
+    color: #0f172a;
+}
+
+.nav-actions {
+    position: absolute;
+    right: 28rpx;
+    top: 50%;
+    transform: translateY(-50%);
+    display: flex;
+    align-items: center;
+    gap: 32rpx;
+}
+
+.nav-action {
+    width: 56rpx;
+    height: 56rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+/* 更多:三点 */
+.icon-more {
+    display: flex;
+    align-items: center;
+    gap: 6rpx;
+}
+
+.icon-more .dot {
+    width: 8rpx;
+    height: 8rpx;
+    border-radius: 50%;
+    background: #334155;
+}
+
+/* 帮助:圆圈问号 */
+.icon-help {
+    width: 36rpx;
+    height: 36rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-sizing: border-box;
+    border: 3rpx solid #334155;
+    border-radius: 50%;
+}
+
+.icon-help-text {
+    font-size: 22rpx;
+    font-weight: 700;
+    line-height: 1;
+    color: #334155;
+}
+
+/* ==================== 表单 ==================== */
+
+.body {
+    flex: 1;
+    padding: 24rpx 32rpx 0;
+}
+
+.field {
+    margin-bottom: 40rpx;
+}
+
+.field-label {
+    display: block;
+    font-size: 30rpx;
+    line-height: 1;
+    color: #1f2937;
+}
+
+.field-input {
+    height: 88rpx;
+    margin-top: 8rpx;
+    font-size: 30rpx;
+    color: #1f2937;
+    border-bottom: 2rpx solid #e2e8f0;
+}
+
+.field-placeholder {
+    font-size: 30rpx;
+    color: #cbd5e1;
+}
+
+/* 只读态:认证中 / 认证成功时置灰文字,视觉上表达不可编辑 */
+.field-input--readonly {
+    color: #9ca3af;
+}
+
+/* ==================== 驳回原因 ==================== */
+
+.reject-tip {
+    margin-top: 12rpx;
+}
+
+.reject-tip-text {
+    font-size: 26rpx;
+    line-height: 1.4;
+    color: #ef4444;
+}
+
+/* ==================== 底部提交 ==================== */
+
+.footer {
+    /* 内容不足一屏时按钮自动贴底 */
+    margin-top: auto;
+    padding: 32rpx 48rpx calc(44rpx + env(safe-area-inset-bottom));
+}
+
+.btn-submit {
+    width: 100%;
+    height: 96rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 12rpx;
+    background: linear-gradient(90deg, #82c236 0%, #639c1f 100%);
+    box-shadow: 0 16rpx 40rpx rgba(99, 156, 31, 0.28);
+}
+
+.btn-submit--disabled {
+    opacity: 0.6;
+}
+
+.btn-submit-text {
+    font-size: 32rpx;
+    font-weight: 600;
+    line-height: 1;
+    color: #ffffff;
+}
+</style>
diff --git a/hugray-user-miniapp/pages/profile/company-certification.vue b/hugray-user-miniapp/pages/profile/company-certification.vue
new file mode 100644
index 0000000..62c1b8a
--- /dev/null
+++ b/hugray-user-miniapp/pages/profile/company-certification.vue
@@ -0,0 +1,1038 @@
+<template>
+    <view class="page">
+        <!-- 自定义导航栏:状态栏留白由行内 paddingTop 撑开 -->
+        <view class="nav" :style="{ paddingTop: statusBarHeight + 'px' }">
+            <view class="nav-bar">
+                <!-- 返回 -->
+                <view class="nav-back" @tap="handleBack">
+                    <view class="icon-back"></view>
+                </view>
+                <text class="nav-title">企业/个体户认证</text>
+            </view>
+        </view>
+
+        <!-- 四段式步骤条:已完成打勾、当前高亮(提交后锁定不可点) -->
+        <view class="steps">
+            <view v-for="(label, idx) in STEP_LABELS" :key="idx" class="step" @tap="handleStepTap(idx)">
+                <view class="step-dot" :class="{ 'step-dot--done': idx < step, 'step-dot--active': idx === step }">
+                    <text v-if="idx < step" class="step-dot-check">✓</text>
+                    <text v-else class="step-dot-num">{{ idx + 1 }}</text>
+                </view>
+                <text class="step-label" :class="{ 'step-label--done': idx < step, 'step-label--active': idx === step }">{{ label }}</text>
+                <view v-if="idx < STEP_LABELS.length - 1" class="step-line" :class="{ 'step-line--done': idx < step }"></view>
+            </view>
+        </view>
+
+        <scroll-view class="body" scroll-y>
+            <!-- 第 4 步:提交审核 / 结果占位 -->
+            <view v-if="step === 3" class="result">
+                <view class="result-icon" :class="resultState === 'passed' ? 'result-icon--done' : 'result-icon--audit'">
+                    <text class="result-icon-text">{{ resultState === 'passed' ? '✓' : '···' }}</text>
+                </view>
+                <text class="result-title">{{ resultInfo.title }}</text>
+                <text class="result-tip">{{ resultInfo.tip }}</text>
+            </view>
+
+            <!-- 第 1 步:企业信息 -->
+            <view v-else-if="step === 0" class="form">
+                <!-- 认证失败:展示驳回原因,可修改后重提 -->
+                <view v-if="showRejectReason" class="reject-tip">
+                    <text class="reject-tip-text">驳回原因:{{ rejectReason }}</text>
+                </view>
+
+                <!-- 主体类型 -->
+                <view class="field">
+                    <text class="field-label">主体类型</text>
+                    <picker :range="SUBJECT_TYPE_LABELS" @change="(e) => handleSelectChange('subjectType', SUBJECT_TYPE_VALUES, e)">
+                        <view class="field-select">
+                            <text :class="form.subjectType ? 'field-select-text' : 'field-select-placeholder'">{{ subjectTypeLabel || '请选择主体类型' }}</text>
+                            <view class="icon-chevron"></view>
+                        </view>
+                    </picker>
+                </view>
+
+                <!-- 企业/个体名称 -->
+                <view class="field">
+                    <text class="field-label">企业/个体名称</text>
+                    <input class="field-input" v-model="form.companyName" type="text" placeholder="请输入企业/个体名称" :placeholder-style="PLACEHOLDER_STYLE" maxlength="128" />
+                </view>
+
+                <!-- 营业执照 -->
+                <view class="field">
+                    <text class="field-label">营业执照</text>
+                    <view class="upload" @tap="handleChooseImage('licenseImage', '营业执照')">
+                        <image v-if="form.licenseImage" class="upload-img" :src="form.licenseImage" mode="aspectFill" />
+                        <view v-else class="upload-empty">
+                            <view class="icon-camera"></view>
+                            <text class="upload-empty-text">上传营业执照</text>
+                        </view>
+                    </view>
+                </view>
+
+                <!-- 统一社会信用代码 -->
+                <view class="field">
+                    <text class="field-label">统一社会信用代码</text>
+                    <input class="field-input" v-model="form.creditCode" type="text" placeholder="请输入统一社会信用代码" :placeholder-style="PLACEHOLDER_STYLE" maxlength="32" />
+                </view>
+
+                <!-- 营业执照有效期类型:长期时隐藏起止日期 -->
+                <view class="field">
+                    <text class="field-label">营业执照有效期类型</text>
+                    <picker :range="VALID_TYPE_LABELS" @change="(e) => handleSelectChange('licenseValidType', VALID_TYPE_VALUES, e)">
+                        <view class="field-select">
+                            <text :class="form.licenseValidType ? 'field-select-text' : 'field-select-placeholder'">{{ labelOf(VALID_TYPE_VALUES, VALID_TYPE_LABELS, form.licenseValidType) || '请选择有效期类型' }}</text>
+                            <view class="icon-chevron"></view>
+                        </view>
+                    </picker>
+                </view>
+
+                <!-- 营业执照有效期起止:仅短期展示且必填 -->
+                <view v-if="form.licenseValidType === '2'" class="field">
+                    <text class="field-label">营业执照有效期</text>
+                    <view class="date-range">
+                        <picker mode="date" :value="form.licenseValidStart" @change="(e) => handleDateChange('licenseValidStart', e)">
+                            <view class="date-value" :class="{ 'date-value--placeholder': !form.licenseValidStart }">{{ form.licenseValidStart || '开始日期' }}</view>
+                        </picker>
+                        <text class="date-sep">~</text>
+                        <picker mode="date" :value="form.licenseValidEnd" @change="(e) => handleDateChange('licenseValidEnd', e)">
+                            <view class="date-value" :class="{ 'date-value--placeholder': !form.licenseValidEnd }">{{ form.licenseValidEnd || '截止日期' }}</view>
+                        </picker>
+                    </view>
+                </view>
+
+                <!-- 营业执照注册地区 -->
+                <view class="field">
+                    <text class="field-label">营业执照注册地区</text>
+                    <picker mode="region" :value="companyRegionValue" @change="(e) => handleRegionChange('registeredRegion', 'company', e)">
+                        <view class="field-select">
+                            <text :class="form.registeredRegion ? 'field-select-text' : 'field-select-placeholder'">{{ form.registeredRegion || '请选择注册地区' }}</text>
+                            <view class="icon-chevron"></view>
+                        </view>
+                    </picker>
+                </view>
+
+                <!-- 营业执照详细地址 -->
+                <view class="field">
+                    <text class="field-label">营业执照详细地址</text>
+                    <input class="field-input" v-model="form.companyAddress" type="text" placeholder="请输入营业执照详细地址" :placeholder-style="PLACEHOLDER_STYLE" maxlength="255" />
+                </view>
+            </view>
+
+            <!-- 第 2 步:法人信息 -->
+            <view v-else-if="step === 1" class="form">
+                <!-- 法人姓名 -->
+                <view class="field">
+                    <text class="field-label">法人姓名</text>
+                    <input class="field-input" v-model="form.legalPerson" type="text" placeholder="请输入法人姓名" :placeholder-style="PLACEHOLDER_STYLE" maxlength="64" />
+                </view>
+
+                <!-- 法人身份证人像面 / 国徽面 -->
+                <view class="field">
+                    <text class="field-label">法人身份证</text>
+                    <view class="upload-row">
+                        <view class="upload upload--half" @tap="handleChooseImage('legalIdCardFront', '法人身份证人像面')">
+                            <image v-if="form.legalIdCardFront" class="upload-img" :src="form.legalIdCardFront" mode="aspectFill" />
+                            <view v-else class="upload-empty">
+                                <view class="icon-camera"></view>
+                                <text class="upload-empty-text">人像面</text>
+                            </view>
+                        </view>
+                        <view class="upload upload--half" @tap="handleChooseImage('legalIdCardBack', '法人身份证国徽面')">
+                            <image v-if="form.legalIdCardBack" class="upload-img" :src="form.legalIdCardBack" mode="aspectFill" />
+                            <view v-else class="upload-empty">
+                                <view class="icon-camera"></view>
+                                <text class="upload-empty-text">国徽面</text>
+                            </view>
+                        </view>
+                    </view>
+                </view>
+
+                <!-- 法人身份证有效期类型 -->
+                <view class="field">
+                    <text class="field-label">法人身份证有效期类型</text>
+                    <picker :range="VALID_TYPE_LABELS" @change="(e) => handleSelectChange('legalIdValidType', VALID_TYPE_VALUES, e)">
+                        <view class="field-select">
+                            <text :class="form.legalIdValidType ? 'field-select-text' : 'field-select-placeholder'">{{ labelOf(VALID_TYPE_VALUES, VALID_TYPE_LABELS, form.legalIdValidType) || '请选择有效期类型' }}</text>
+                            <view class="icon-chevron"></view>
+                        </view>
+                    </picker>
+                </view>
+
+                <!-- 法人身份证有效期起止:仅短期展示且必填 -->
+                <view v-if="form.legalIdValidType === '2'" class="field">
+                    <text class="field-label">法人身份证有效期</text>
+                    <view class="date-range">
+                        <picker mode="date" :value="form.legalIdValidStart" @change="(e) => handleDateChange('legalIdValidStart', e)">
+                            <view class="date-value" :class="{ 'date-value--placeholder': !form.legalIdValidStart }">{{ form.legalIdValidStart || '开始日期' }}</view>
+                        </picker>
+                        <text class="date-sep">~</text>
+                        <picker mode="date" :value="form.legalIdValidEnd" @change="(e) => handleDateChange('legalIdValidEnd', e)">
+                            <view class="date-value" :class="{ 'date-value--placeholder': !form.legalIdValidEnd }">{{ form.legalIdValidEnd || '截止日期' }}</view>
+                        </picker>
+                    </view>
+                </view>
+
+                <!-- 法人身份证详细地址 -->
+                <view class="field">
+                    <text class="field-label">法人身份证详细地址</text>
+                    <input class="field-input" v-model="form.legalIdAddress" type="text" placeholder="请输入法人身份证详细地址" :placeholder-style="PLACEHOLDER_STYLE" maxlength="255" />
+                </view>
+
+                <!-- 负责人姓名 -->
+                <view class="field">
+                    <text class="field-label">负责人姓名</text>
+                    <input class="field-input" v-model="form.contactName" type="text" placeholder="请输入负责人姓名" :placeholder-style="PLACEHOLDER_STYLE" maxlength="64" />
+                </view>
+
+                <!-- 负责人联系电话 -->
+                <view class="field">
+                    <text class="field-label">负责人联系电话</text>
+                    <input class="field-input" v-model="form.contactPhone" type="number" placeholder="请输入负责人联系电话" :placeholder-style="PLACEHOLDER_STYLE" maxlength="20" />
+                </view>
+
+                <!-- 负责人邮箱 -->
+                <view class="field">
+                    <text class="field-label">负责人邮箱</text>
+                    <input class="field-input" v-model="form.contactEmail" type="text" placeholder="请输入负责人邮箱" :placeholder-style="PLACEHOLDER_STYLE" maxlength="128" />
+                </view>
+            </view>
+
+            <!-- 第 3 步:收款信息 -->
+            <view v-else class="form">
+                <!-- 收款类型 -->
+                <view class="field">
+                    <text class="field-label">收款类型</text>
+                    <picker :range="PAYEE_TYPE_OPTIONS" @change="(e) => handlePayeeTypeChange(e)">
+                        <view class="field-select">
+                            <text :class="form.payeeType ? 'field-select-text' : 'field-select-placeholder'">{{ form.payeeType || '请选择收款类型' }}</text>
+                            <view class="icon-chevron"></view>
+                        </view>
+                    </picker>
+                </view>
+
+                <!-- 银行卡号码面照片 -->
+                <view class="field">
+                    <text class="field-label">银行卡号码面照片</text>
+                    <view class="upload" @tap="handleChooseImage('bankCardImage', '银行卡号码面照片')">
+                        <image v-if="form.bankCardImage" class="upload-img" :src="form.bankCardImage" mode="aspectFill" />
+                        <view v-else class="upload-empty">
+                            <view class="icon-camera"></view>
+                            <text class="upload-empty-text">上传银行卡照片</text>
+                        </view>
+                    </view>
+                </view>
+
+                <!-- 银行卡号 -->
+                <view class="field">
+                    <text class="field-label">银行卡号</text>
+                    <input class="field-input" v-model="form.bankCardNo" type="number" placeholder="请输入银行卡号" :placeholder-style="PLACEHOLDER_STYLE" maxlength="32" />
+                </view>
+
+                <!-- 开户城市 -->
+                <view class="field">
+                    <text class="field-label">开户城市</text>
+                    <picker mode="region" :value="bankRegionValue" @change="(e) => handleRegionChange('bankCity', 'bank', e)">
+                        <view class="field-select">
+                            <text :class="form.bankCity ? 'field-select-text' : 'field-select-placeholder'">{{ form.bankCity || '请选择开户城市' }}</text>
+                            <view class="icon-chevron"></view>
+                        </view>
+                    </picker>
+                </view>
+            </view>
+        </scroll-view>
+
+        <!-- 底部按钮:前两步「下一步」,第 3 步「提交资料」;结果页隐藏 -->
+        <view v-if="step < 3" class="footer">
+            <view class="btn-submit" :class="{ 'btn-submit--disabled': submitting }" @tap="handlePrimary">
+                <text class="btn-submit-text">{{ step === 2 ? '提交资料' : '下一步' }}</text>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { computed, reactive, ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
+import { ensureLogin } from '@/utils/request.js'
+import { getCompanyCertificationStatus, submitCompanyCertification, uploadAvatarFile } from '@/api/index.js'
+
+/** 状态栏高度:自定义导航栏下用于顶部留白 */
+const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
+
+/** 图片大小上限:5MB(选图后前置校验,超限直接拒绝,避免无效上传) */
+const IMAGE_MAX_SIZE = 5 * 1024 * 1024
+
+/** 输入框占位色:与 picker/日期占位色统一(小程序 scoped 下 placeholder-class 不生效,改用行内样式) */
+const PLACEHOLDER_STYLE = 'color:#94a3b8;font-size:30rpx'
+
+/** 步骤标题:与原型四步一一对应 */
+const STEP_LABELS = ['企业信息', '法人信息', '收款信息', '提交审核']
+
+/** 主体类型:值 2个体工商户 / 3企业(与后端 subject_type 语义一致) */
+const SUBJECT_TYPE_VALUES = ['2', '3']
+const SUBJECT_TYPE_LABELS = ['个体工商户', '企业']
+
+/** 有效期类型:值 1长期 / 2短期;长期隐藏起止日期且不校验 */
+const VALID_TYPE_VALUES = ['1', '2']
+const VALID_TYPE_LABELS = ['长期', '短期']
+
+/** 收款类型选项:本期仅基础必填,直接存中文文本 */
+const PAYEE_TYPE_OPTIONS = ['对公账户', '对私账户']
+
+/** 结果页文案:审核中 / 已通过 */
+const RESULT_MAP = {
+    auditing: { title: '资料审核中', tip: '您的资料正在审核中,请耐心等待' },
+    passed: { title: '认证已通过', tip: '您的企业/个体户认证已通过审核' }
+}
+
+/** 当前步骤:0~2 为表单步骤,3 为提交审核结果页 */
+const step = ref(0)
+
+/** 结果页类型:'auditing' 审核中 / 'passed' 已通过 */
+const resultState = ref('')
+
+/** 提交中标记:防止重复点击造成重复提交 */
+const submitting = ref(false)
+
+/** 审核状态(0待审核 1已通过 2已驳回;空表示从未提交) */
+const auditStatus = ref('')
+
+/** 驳回原因:仅审核状态为「已驳回」时有值 */
+const rejectReason = ref('')
+
+/** 全部表单字段(有效期起止拆为起/止两个值,提交时再拼接为文本) */
+const form = reactive({
+    subjectType: '',
+    companyName: '',
+    licenseImage: '',
+    creditCode: '',
+    licenseValidType: '',
+    licenseValidStart: '',
+    licenseValidEnd: '',
+    registeredRegion: '',
+    companyAddress: '',
+    legalPerson: '',
+    legalIdCardFront: '',
+    legalIdCardBack: '',
+    legalIdValidType: '',
+    legalIdValidStart: '',
+    legalIdValidEnd: '',
+    legalIdAddress: '',
+    contactName: '',
+    contactPhone: '',
+    contactEmail: '',
+    payeeType: '',
+    bankCardImage: '',
+    bankCardNo: '',
+    bankCity: ''
+})
+
+/** 地区选择器回显值:picker 的 value 需与三级数组保持一致 */
+const companyRegionValue = ref([])
+const bankRegionValue = ref([])
+
+/** 值 → 文案:按同下标映射,未命中返回空串 */
+const labelOf = (values, labels, value) => {
+    const index = values.indexOf(value)
+    return index > -1 ? labels[index] : ''
+}
+
+/** 解析「起~止」文本为起止两个日期;长期或未填时均返回空 */
+const parseValidPeriod = (period) => {
+    if (!period) return { start: '', end: '' }
+    const [start = '', end = ''] = period.split('~')
+    return { start, end }
+}
+
+/** 拼接「起~止」文本:任一项缺失则返回空(长期提交空值) */
+const buildValidPeriod = (start, end) => (start && end ? `${start}~${end}` : '')
+
+/** 是否展示驳回原因 */
+const showRejectReason = computed(() => auditStatus.value === '2' && !!rejectReason.value)
+
+/** 是否已提交(结果页):锁定时不可回退修改 */
+const locked = computed(() => step.value === 3)
+
+/** 结果页文案对象 */
+const resultInfo = computed(() => RESULT_MAP[resultState.value] || RESULT_MAP.auditing)
+
+/** 主体类型文案(用于选择项回显) */
+const subjectTypeLabel = computed(() => labelOf(SUBJECT_TYPE_VALUES, SUBJECT_TYPE_LABELS, form.subjectType))
+
+/** 返回上一页:无上级页面(如直接进入)时回到个人中心 */
+const handleBack = () => {
+    const pages = getCurrentPages()
+    if (pages.length > 1) {
+        uni.navigateBack()
+        return
+    }
+    uni.switchTab({ url: '/pages/profile/index' })
+}
+
+/** 点击步骤条:已提交(结果页)不可回退,仅允许回退到已完成的步骤 */
+const handleStepTap = (idx) => {
+    if (locked.value) return
+    if (idx < step.value) step.value = idx
+}
+
+/** 通用下拉选择回写(主体类型 / 有效期类型) */
+const handleSelectChange = (field, values, e) => {
+    form[field] = values[e.detail.value] || ''
+}
+
+/** 收款类型选择(直接存中文文本) */
+const handlePayeeTypeChange = (e) => {
+    form.payeeType = PAYEE_TYPE_OPTIONS[e.detail.value] || ''
+}
+
+/** 地区选择:回写文本并按空格拆分为数组用于 picker 回显 */
+const handleRegionChange = (field, target, e) => {
+    const region = e.detail.value || []
+    form[field] = region.join(' ')
+    if (target === 'company') {
+        companyRegionValue.value = region
+    } else {
+        bankRegionValue.value = region
+    }
+}
+
+/** 日期选择回写(有效期起 / 止) */
+const handleDateChange = (field, e) => {
+    form[field] = e.detail.value
+}
+
+/** 图片来源选项:相册上传 / 拍照上传,供用户按场景选择 */
+const IMAGE_SOURCE_OPTIONS = [
+    { label: '拍照', sourceType: ['camera'] },
+    { label: '从相册选择', sourceType: ['album'] }
+]
+
+/**
+ * 选图并上传:先选来源(相册/拍照)-> 选图 -> 大小校验 -> 上传文件服务 -> 写回表单字段
+ * @param {String} field 表单字段名
+ * @param {String} label 字段中文名(用于失败提示)
+ */
+const handleChooseImage = (field, label) => {
+    uni.showActionSheet({
+        itemList: IMAGE_SOURCE_OPTIONS.map((item) => item.label),
+        success: (sheetRes) => {
+            const option = IMAGE_SOURCE_OPTIONS[sheetRes.tapIndex]
+            if (option) chooseAndUploadImage(field, label, option.sourceType)
+        }
+    })
+}
+
+/**
+ * 按指定来源选图并上传(相册 / 拍照)
+ * @param {String} field 表单字段名
+ * @param {String} label 字段中文名(用于失败提示)
+ * @param {Array} sourceType 图片来源:['camera'] 拍照 / ['album'] 相册
+ */
+const chooseAndUploadImage = (field, label, sourceType) => {
+    uni.chooseImage({
+        count: 1,
+        sizeType: ['compressed'],
+        sourceType,
+        success: async (res) => {
+            const filePath = (res.tempFilePaths || [])[0]
+            const file = (res.tempFiles || [])[0]
+            if (!filePath) return
+            // 前置大小校验:超限直接提示,避免无效上传
+            if (file && file.size > IMAGE_MAX_SIZE) {
+                uni.showToast({ title: '图片不能超过5MB', icon: 'none' })
+                return
+            }
+            uni.showLoading({ title: '上传中', mask: true })
+            try {
+                const uploadRes = await uploadAvatarFile(filePath)
+                const url = uploadRes.data && uploadRes.data.url
+                uni.hideLoading()
+                if (!url) {
+                    uni.showToast({ title: '上传失败,请重试', icon: 'none' })
+                    return
+                }
+                form[field] = url
+            } catch (error) {
+                uni.hideLoading()
+                console.error(`[COMPANY_CERT] 上传${label}失败`, error)
+                uni.showToast({ title: '上传失败,请重试', icon: 'none' })
+            }
+        }
+    })
+}
+
+/** 单步必填校验:返回失败提示(通过返回空串) */
+const validateStep = (idx) => {
+    // 第 1 步:企业信息
+    if (idx === 0) {
+        if (!form.subjectType) return '请选择主体类型'
+        if (!form.companyName.trim()) return '请输入企业/个体名称'
+        if (!form.licenseImage) return '请上传营业执照'
+        if (!form.creditCode.trim()) return '请输入统一社会信用代码'
+        if (!form.licenseValidType) return '请选择营业执照有效期类型'
+        // 短期必须填写起止日期;长期隐藏且不校验
+        if (form.licenseValidType === '2' && (!form.licenseValidStart || !form.licenseValidEnd)) {
+            return '请选择营业执照有效期起止日期'
+        }
+        if (!form.registeredRegion) return '请选择营业执照注册地区'
+        if (!form.companyAddress.trim()) return '请输入营业执照详细地址'
+        return ''
+    }
+    // 第 2 步:法人信息
+    if (idx === 1) {
+        if (!form.legalPerson.trim()) return '请输入法人姓名'
+        if (!form.legalIdCardFront) return '请上传法人身份证人像面'
+        if (!form.legalIdCardBack) return '请上传法人身份证国徽面'
+        if (!form.legalIdValidType) return '请选择法人身份证有效期类型'
+        if (form.legalIdValidType === '2' && (!form.legalIdValidStart || !form.legalIdValidEnd)) {
+            return '请选择法人身份证有效期起止日期'
+        }
+        if (!form.legalIdAddress.trim()) return '请输入法人身份证详细地址'
+        if (!form.contactName.trim()) return '请输入负责人姓名'
+        if (!form.contactPhone.trim()) return '请输入负责人联系电话'
+        if (!form.contactEmail.trim()) return '请输入负责人邮箱'
+        return ''
+    }
+    // 第 3 步:收款信息
+    if (idx === 2) {
+        if (!form.payeeType) return '请选择收款类型'
+        if (!form.bankCardImage) return '请上传银行卡号码面照片'
+        if (!form.bankCardNo.trim()) return '请输入银行卡号'
+        if (!form.bankCity) return '请选择开户城市'
+        return ''
+    }
+    return ''
+}
+
+/** 整表单校验:按步骤顺序返回首个缺失项提示 */
+const validateAll = () => validateStep(0) || validateStep(1) || validateStep(2)
+
+/** 组装提交入参(字段名与后端 CompanyCertificationApplyDto 一致) */
+const buildPayload = () => ({
+    subjectType: form.subjectType,
+    companyName: form.companyName.trim(),
+    licenseImage: form.licenseImage,
+    creditCode: form.creditCode.trim(),
+    licenseValidType: form.licenseValidType,
+    licenseValidPeriod: form.licenseValidType === '2' ? buildValidPeriod(form.licenseValidStart, form.licenseValidEnd) : '',
+    registeredRegion: form.registeredRegion,
+    companyAddress: form.companyAddress.trim(),
+    legalPerson: form.legalPerson.trim(),
+    legalIdCardFront: form.legalIdCardFront,
+    legalIdCardBack: form.legalIdCardBack,
+    legalIdValidType: form.legalIdValidType,
+    legalIdValidPeriod: form.legalIdValidType === '2' ? buildValidPeriod(form.legalIdValidStart, form.legalIdValidEnd) : '',
+    legalIdAddress: form.legalIdAddress.trim(),
+    contactName: form.contactName.trim(),
+    contactPhone: form.contactPhone.trim(),
+    contactEmail: form.contactEmail.trim(),
+    payeeType: form.payeeType,
+    bankCardImage: form.bankCardImage,
+    bankCardNo: form.bankCardNo.trim(),
+    bankCity: form.bankCity
+})
+
+/**
+ * 拉取企业认证状态并回填表单
+ * 从未提交 → 停在第 1 步空表单;已驳回 → 停在第 1 步并展示驳回原因;
+ * 审核中 / 已通过 → 直接进入第 4 步结果页(提交后不可再编辑)。
+ * 查询失败静默降级为「未认证」,不阻断用户填报。
+ */
+const loadCompanyCertificationStatus = async () => {
+    try {
+        const res = await getCompanyCertificationStatus()
+        const data = res.data || {}
+        auditStatus.value = data.auditStatus || ''
+        rejectReason.value = data.auditRemark || ''
+        // 回填企业信息
+        form.subjectType = data.subjectType || ''
+        form.companyName = data.companyName || ''
+        form.licenseImage = data.licenseImage || ''
+        form.creditCode = data.creditCode || ''
+        form.licenseValidType = data.licenseValidType || ''
+        form.registeredRegion = data.registeredRegion || ''
+        form.companyAddress = data.companyAddress || ''
+        // 回填法人信息
+        form.legalPerson = data.legalPerson || ''
+        form.legalIdCardFront = data.legalIdCardFront || ''
+        form.legalIdCardBack = data.legalIdCardBack || ''
+        form.legalIdValidType = data.legalIdValidType || ''
+        form.legalIdAddress = data.legalIdAddress || ''
+        form.contactName = data.contactName || ''
+        form.contactPhone = data.contactPhone || ''
+        form.contactEmail = data.contactEmail || ''
+        // 回填收款信息
+        form.payeeType = data.payeeType || ''
+        form.bankCardImage = data.bankCardImage || ''
+        form.bankCardNo = data.bankCardNo || ''
+        form.bankCity = data.bankCity || ''
+        // 有效期文本拆回起止日期
+        const licensePeriod = parseValidPeriod(data.licenseValidPeriod)
+        form.licenseValidStart = licensePeriod.start
+        form.licenseValidEnd = licensePeriod.end
+        const legalPeriod = parseValidPeriod(data.legalIdValidPeriod)
+        form.legalIdValidStart = legalPeriod.start
+        form.legalIdValidEnd = legalPeriod.end
+        // 地区文本按空格拆回三级数组,供 picker 回显
+        if (form.registeredRegion) companyRegionValue.value = form.registeredRegion.split(' ')
+        if (form.bankCity) bankRegionValue.value = form.bankCity.split(' ')
+        // 决定首屏:审核中 / 已通过锁定在结果页
+        if (auditStatus.value === '0') {
+            resultState.value = 'auditing'
+            step.value = 3
+        } else if (auditStatus.value === '1') {
+            resultState.value = 'passed'
+            step.value = 3
+        } else {
+            step.value = 0
+        }
+    } catch (error) {
+        console.error('[COMPANY_CERT] 查询企业认证状态失败', error)
+        auditStatus.value = ''
+        rejectReason.value = ''
+        step.value = 0
+    }
+}
+
+/** 主按钮:第 3 步提交,前两步逐级校验后进入下一步 */
+const handlePrimary = () => {
+    if (submitting.value) return
+    if (step.value === 2) {
+        handleSubmit()
+        return
+    }
+    const errorMsg = validateStep(step.value)
+    if (errorMsg) {
+        uni.showToast({ title: errorMsg, icon: 'none' })
+        return
+    }
+    step.value += 1
+}
+
+/** 提交企业/个体认证:整表单校验通过后调接口,成功后进入结果页 */
+const handleSubmit = async () => {
+    const errorMsg = validateAll()
+    if (errorMsg) {
+        uni.showToast({ title: errorMsg, icon: 'none' })
+        return
+    }
+    submitting.value = true
+    uni.showLoading({ title: '提交中', mask: true })
+    try {
+        await submitCompanyCertification(buildPayload())
+        uni.hideLoading()
+        auditStatus.value = '0'
+        resultState.value = 'auditing'
+        step.value = 3
+        uni.showToast({ title: '提交成功,等待审核', icon: 'none' })
+    } catch (error) {
+        uni.hideLoading()
+        console.error('[COMPANY_CERT] 提交企业认证失败', error)
+        uni.showToast({ title: (error && error.message) || '提交失败,请重试', icon: 'none' })
+    } finally {
+        submitting.value = false
+    }
+}
+
+// 进入页面校验登录态并拉取认证状态回填(未登录跳登录页)
+// 注意:选图/拍照(chooseImage)返回时会再次触发 onShow,若重复拉取会覆盖用户已填内容,
+// 故用 hasLoadedStatus 保证状态回填仅在首次显示时执行一次
+let hasLoadedStatus = false
+onShow(() => {
+    if (!ensureLogin()) return
+    if (hasLoadedStatus) return
+    hasLoadedStatus = true
+    loadCompanyCertificationStatus()
+})
+</script>
+
+<style scoped lang="scss">
+.page {
+    height: 100vh;
+    display: flex;
+    flex-direction: column;
+    box-sizing: border-box;
+    background-color: #ffffff;
+}
+
+/* ==================== 自定义导航栏 ==================== */
+
+.nav {
+    flex-shrink: 0;
+    background-color: #ffffff;
+}
+
+.nav-bar {
+    position: relative;
+    height: 88rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.nav-back {
+    position: absolute;
+    left: 16rpx;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 64rpx;
+    height: 64rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+/* 纯 CSS 左向箭头:左 + 下边框旋转 45° */
+.icon-back {
+    width: 22rpx;
+    height: 22rpx;
+    border-left: 4rpx solid #1f2937;
+    border-bottom: 4rpx solid #1f2937;
+    transform: rotate(45deg);
+}
+
+.nav-title {
+    font-size: 34rpx;
+    font-weight: 700;
+    line-height: 1;
+    letter-spacing: 1rpx;
+    color: #0f172a;
+}
+
+/* ==================== 四段式步骤条 ==================== */
+
+.steps {
+    flex-shrink: 0;
+    display: flex;
+    align-items: flex-start;
+    padding: 24rpx 32rpx 28rpx;
+    border-bottom: 2rpx solid #f1f5f9;
+}
+
+.step {
+    position: relative;
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+.step-dot {
+    position: relative;
+    z-index: 1;
+    width: 44rpx;
+    height: 44rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 50%;
+    background: #e2e8f0;
+}
+
+/* 已完成:草绿实心 + 打勾 */
+.step-dot--done {
+    background: #82c236;
+}
+
+/* 当前:草绿渐变高亮 */
+.step-dot--active {
+    background: linear-gradient(90deg, #82c236 0%, #639c1f 100%);
+}
+
+.step-dot-check {
+    font-size: 24rpx;
+    font-weight: 700;
+    line-height: 1;
+    color: #ffffff;
+}
+
+.step-dot-num {
+    font-size: 24rpx;
+    font-weight: 600;
+    line-height: 1;
+    color: #94a3b8;
+}
+
+.step-dot--active .step-dot-num {
+    color: #ffffff;
+}
+
+/* 连接线:从当前步骤圆心延伸到下一步骤圆心 */
+.step-line {
+    position: absolute;
+    top: 21rpx;
+    left: 50%;
+    width: 100%;
+    height: 4rpx;
+    background: #e2e8f0;
+}
+
+.step-line--done {
+    background: #82c236;
+}
+
+.step-label {
+    margin-top: 12rpx;
+    font-size: 24rpx;
+    line-height: 1;
+    color: #94a3b8;
+}
+
+.step-label--active {
+    font-weight: 600;
+    color: #639c1f;
+}
+
+.step-label--done {
+    color: #334155;
+}
+
+/* ==================== 内容区 ==================== */
+
+.body {
+    flex: 1;
+    min-height: 0;
+}
+
+.form {
+    padding: 32rpx 32rpx 40rpx;
+}
+
+/* ==================== 表单字段 ==================== */
+
+.field {
+    margin-bottom: 40rpx;
+}
+
+.field-label {
+    display: block;
+    font-size: 28rpx;
+    font-weight: 600;
+    line-height: 1;
+    color: #334155;
+}
+
+.field-input {
+    height: 88rpx;
+    margin-top: 8rpx;
+    font-size: 30rpx;
+    color: #1f2937;
+    border-bottom: 2rpx solid #e2e8f0;
+}
+
+/* 下拉选择行:文本 + 右箭头 */
+.field-select {
+    height: 88rpx;
+    margin-top: 8rpx;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    border-bottom: 2rpx solid #e2e8f0;
+}
+
+.field-select-text {
+    font-size: 30rpx;
+    color: #1f2937;
+}
+
+.field-select-placeholder {
+    font-size: 30rpx;
+    color: #94a3b8;
+}
+
+.icon-chevron {
+    width: 32rpx;
+    height: 32rpx;
+    flex-shrink: 0;
+    background-repeat: no-repeat;
+    background-position: center center;
+    background-size: 100% 100%;
+    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjOTRBM0I4IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTkgMTggNi02LTYtNiIvPjwvc3ZnPg==');
+}
+
+/* ==================== 有效期起止日期 ==================== */
+
+.date-range {
+    height: 88rpx;
+    margin-top: 8rpx;
+    display: flex;
+    align-items: center;
+    border-bottom: 2rpx solid #e2e8f0;
+}
+
+.date-range picker {
+    flex: 1;
+}
+
+.date-value {
+    font-size: 30rpx;
+    color: #1f2937;
+}
+
+.date-value--placeholder {
+    color: #94a3b8;
+}
+
+.date-sep {
+    flex-shrink: 0;
+    padding: 0 20rpx;
+    font-size: 30rpx;
+    color: #94a3b8;
+}
+
+/* ==================== 图片上传 ==================== */
+
+.upload {
+    width: 100%;
+    height: 260rpx;
+    margin-top: 16rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-sizing: border-box;
+    border: 2rpx dashed #cbd5e1;
+    border-radius: 12rpx;
+    background: #f8fafc;
+    overflow: hidden;
+}
+
+/* 法人身份证正反面并排 */
+.upload-row {
+    display: flex;
+    gap: 24rpx;
+    margin-top: 16rpx;
+}
+
+.upload--half {
+    flex: 1;
+    width: auto;
+    height: 220rpx;
+    margin-top: 0;
+}
+
+.upload-img {
+    width: 100%;
+    height: 100%;
+}
+
+.upload-empty {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 16rpx;
+}
+
+.upload-empty-text {
+    font-size: 26rpx;
+    color: #94a3b8;
+}
+
+.icon-camera {
+    width: 48rpx;
+    height: 48rpx;
+    background-repeat: no-repeat;
+    background-position: center center;
+    background-size: 100% 100%;
+    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjOTRBM0I4IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTE0LjUgNGgtNUw3IDdINGEyIDIgMCAwIDAtMiAydjlhMiAyIDAgMCAwIDIgMmgxNmEyIDIgMCAwIDAgMi0yVjlhMiAyIDAgMCAwLTItMmgtM2wtMi41LTN6Ii8+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMyIgcj0iMyIvPjwvc3ZnPg==');
+}
+
+/* ==================== 驳回原因 ==================== */
+
+.reject-tip {
+    margin-bottom: 32rpx;
+    padding: 20rpx 24rpx;
+    border-radius: 12rpx;
+    background: #fef2f2;
+}
+
+.reject-tip-text {
+    font-size: 26rpx;
+    line-height: 1.5;
+    color: #ef4444;
+}
+
+/* ==================== 结果页 ==================== */
+
+.result {
+    padding: 140rpx 64rpx 0;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+.result-icon {
+    width: 140rpx;
+    height: 140rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 50%;
+}
+
+.result-icon--audit {
+    background: #eff6ff;
+}
+
+.result-icon--done {
+    background: #f7fee7;
+}
+
+.result-icon-text {
+    font-size: 40rpx;
+    font-weight: 700;
+    line-height: 1;
+}
+
+.result-icon--audit .result-icon-text {
+    color: #1e5ebc;
+}
+
+.result-icon--done .result-icon-text {
+    color: #4d7c0f;
+}
+
+.result-title {
+    margin-top: 40rpx;
+    font-size: 34rpx;
+    font-weight: 700;
+    line-height: 1;
+    color: #0f172a;
+}
+
+.result-tip {
+    margin-top: 20rpx;
+    font-size: 28rpx;
+    line-height: 1.5;
+    text-align: center;
+    color: #94a3b8;
+}
+
+/* ==================== 底部提交 ==================== */
+
+.footer {
+    flex-shrink: 0;
+    padding: 24rpx 48rpx calc(44rpx + env(safe-area-inset-bottom));
+}
+
+.btn-submit {
+    width: 100%;
+    height: 96rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 12rpx;
+    background: linear-gradient(90deg, #82c236 0%, #639c1f 100%);
+    box-shadow: 0 16rpx 40rpx rgba(99, 156, 31, 0.28);
+}
+
+.btn-submit--disabled {
+    opacity: 0.6;
+}
+
+.btn-submit-text {
+    font-size: 32rpx;
+    font-weight: 600;
+    line-height: 1;
+    color: #ffffff;
+}
+</style>
diff --git a/hugray-user-miniapp/pages/profile/edit.vue b/hugray-user-miniapp/pages/profile/edit.vue
index 9cf013f..f6ae495 100644
--- a/hugray-user-miniapp/pages/profile/edit.vue
+++ b/hugray-user-miniapp/pages/profile/edit.vue
@@ -26,8 +26,8 @@
 
         <view class="body">
             <view class="list">
-                <!-- 头像:点击更换(本期占位) -->
-                <view class="row" @tap="handlePlaceholder('头像更换')">
+                <!-- 头像:点击更换 -->
+                <view class="row" @tap="handleAvatarChange">
                     <text class="row-label">头像</text>
                     <view class="row-right">
                         <view class="avatar">
@@ -45,7 +45,7 @@
                 </view>
 
                 <!-- 实名认证 -->
-                <view class="row" @tap="handlePlaceholder('实名认证')">
+                <view class="row" @tap="handleRealNameCertification">
                     <text class="row-label">实名认证</text>
                     <view class="row-right">
                         <view class="badge" :class="realNameBadge.cls">
@@ -56,7 +56,7 @@
                 </view>
 
                 <!-- 企业 / 个体户认证 -->
-                <view class="row" @tap="handlePlaceholder('企业/个体户认证')">
+                <view class="row" @tap="handleCompanyCertification">
                     <text class="row-label">企业/个体户认证</text>
                     <view class="row-right">
                         <view class="badge" :class="companyBadge.cls">
@@ -82,10 +82,13 @@
 import { computed, ref } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
 import { clearToken, ensureLogin } from '@/utils/request.js'
-import { getMemberInfo, logout } from '@/api/index.js'
+import { getMemberInfo, logout, uploadAvatarFile, updateAvatar } from '@/api/index.js'
 
 /** 状态栏高度:自定义导航栏下用于顶部留白 */
 const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
+
+/** 头像文件大小上限:5MB(选图后前置校验,超限直接拒绝,避免无效上传) */
+const AVATAR_MAX_SIZE = 5 * 1024 * 1024
 
 /** 用户头像:登录后由 getMemberInfo 下发,为空时回退相机占位 */
 const avatar = ref('')
@@ -94,8 +97,7 @@
 const phone = ref('')
 
 /**
- * 认证状态字典:key 为后端下发的状态枚举,值含徽标文案与样式类
- * 本期为原型,两处认证均取 none(未认证)占位,后端接入后直接替换 ref 初值即可
+ * 认证状态字典:key 为本地徽标状态,值含徽标文案与样式类
  */
 const AUTH_STATUS_MAP = {
     none: { text: '未认证', cls: 'badge--none' },
@@ -104,10 +106,21 @@
     done: { text: '已认证', cls: 'badge--done' }
 }
 
-/** 实名认证状态(本期占位) */
+/**
+ * 后端认证状态码 → 本地徽标状态(0未认证 1审核中 2已认证 3认证失败)
+ * 个人中心「实名认证」行据此回显真实状态
+ */
+const AUTH_STATUS_CODE_MAP = {
+    '0': 'none',
+    '1': 'ing',
+    '2': 'done',
+    '3': 'fail'
+}
+
+/** 实名认证状态:由会员信息接口下发的 authStatus 映射,缺省「未认证」 */
 const realNameStatus = ref('none')
 
-/** 企业 / 个体户认证状态(本期占位) */
+/** 企业 / 个体户认证状态:由会员信息接口下发的 companyAuthStatus 映射,缺省「未认证」 */
 const companyStatus = ref('none')
 
 /** 实名认证徽标:状态缺省兜底「未认证」 */
@@ -126,9 +139,72 @@
     uni.switchTab({ url: '/pages/profile/index' })
 }
 
-/** 占位交互:头像更换 / 实名认证 / 企业认证 / 注销 / 更多 / 帮助本期均不接后端 */
+/** 占位交互:注销 / 更多 / 帮助本期均不接后端 */
 const handlePlaceholder = (name) => {
     uni.showToast({ title: `${name}功能开发中`, icon: 'none' })
+}
+
+/** 跳转实名认证页(个人主体) */
+const handleRealNameCertification = () => {
+    uni.navigateTo({ url: '/pages/profile/certification' })
+}
+
+/** 跳转企业/个体认证向导页(企业 / 个体工商户主体) */
+const handleCompanyCertification = () => {
+    uni.navigateTo({ url: '/pages/profile/company-certification' })
+}
+
+/**
+ * 更换头像入口:先弹出选择方式(相册 / 拍照 / 取消)
+ * 说明:取消按钮由系统 ActionSheet 自带,用户取消时不触发 success,无需额外处理
+ */
+const handleAvatarChange = () => {
+    uni.showActionSheet({
+        itemList: ['从相册选择', '拍照上传'],
+        success: (res) => {
+            // tapIndex 0=相册 1=拍照
+            chooseAndUploadAvatar(res.tapIndex === 1 ? 'camera' : 'album')
+        }
+    })
+}
+
+/**
+ * 选图并上传:选图 -> 大小校验 -> 上传文件服务 -> 写回会员资料 -> 本地即时回显
+ * @param {String} sourceType 图片来源:'album' 相册 / 'camera' 拍照
+ */
+const chooseAndUploadAvatar = (sourceType) => {
+    uni.chooseImage({
+        count: 1,
+        sizeType: ['compressed'],
+        sourceType: [sourceType],
+        success: async (res) => {
+            const filePath = (res.tempFilePaths || [])[0]
+            const file = (res.tempFiles || [])[0]
+            if (!filePath) return
+            // 前置大小校验:超限直接提示,避免无效上传
+            if (file && file.size > AVATAR_MAX_SIZE) {
+                uni.showToast({ title: '图片不能超过5MB', icon: 'none' })
+                return
+            }
+            try {
+                // STEP 1: 上传文件到文件服务,拿到可访问 url
+                const uploadRes = await uploadAvatarFile(filePath)
+                const url = uploadRes.data && uploadRes.data.url
+                if (!url) {
+                    uni.showToast({ title: '上传失败,请重试', icon: 'none' })
+                    return
+                }
+                // STEP 2: 把 url 写回会员资料
+                await updateAvatar(url)
+                // STEP 3: 本地即时回显,无需再拉接口
+                avatar.value = url
+                uni.showToast({ title: '头像已更新', icon: 'success' })
+            } catch (error) {
+                console.error('[PROFILE_EDIT] 更新头像失败', error)
+                uni.showToast({ title: (error && error.message) || '更新头像失败', icon: 'none' })
+            }
+        }
+    })
 }
 
 /** 退出登录:二次确认后调用退出接口并清理本地登录态,回到个人中心 */
@@ -152,17 +228,23 @@
     })
 }
 
-/** 拉取当前会员信息(头像 / 脱敏手机号),失败时静默降级为空 */
+/** 拉取当前会员信息(头像 / 脱敏手机号 / 实名认证状态),失败时静默降级为空 */
 const loadMemberInfo = async () => {
     try {
         const res = await getMemberInfo()
         const data = res.data || {}
         avatar.value = data.avatar || ''
         phone.value = data.phone || ''
+        // 实名认证徽标据后端 authStatus 映射回显(审核通过/驳回后再次进入本页即刷新)
+        realNameStatus.value = AUTH_STATUS_CODE_MAP[data.authStatus] || 'none'
+        // 企业/个体户认证徽标据后端 companyAuthStatus 映射回显,与实名认证相互独立
+        companyStatus.value = AUTH_STATUS_CODE_MAP[data.companyAuthStatus] || 'none'
     } catch (error) {
         console.error('[PROFILE_EDIT] 拉取会员信息失败', error)
         avatar.value = ''
         phone.value = ''
+        realNameStatus.value = 'none'
+        companyStatus.value = 'none'
     }
 }
 
diff --git a/hugray-user-miniapp/pages/profile/index.vue b/hugray-user-miniapp/pages/profile/index.vue
index 38ce0af..d41373d 100644
--- a/hugray-user-miniapp/pages/profile/index.vue
+++ b/hugray-user-miniapp/pages/profile/index.vue
@@ -164,6 +164,11 @@
         uni.navigateTo({ url: '/pages/address/list' })
         return
     }
+    // 我的邀请:跳转「我的邀请」页(邀请码 / 我邀请的 / 邀请我的)
+    if (menu.key === 'invite') {
+        uni.navigateTo({ url: '/pages/profile/invite' })
+        return
+    }
     uni.showToast({ title: '敬请期待', icon: 'none' })
 }
 
diff --git a/hugray-user-miniapp/pages/profile/invite.vue b/hugray-user-miniapp/pages/profile/invite.vue
new file mode 100644
index 0000000..5459889
--- /dev/null
+++ b/hugray-user-miniapp/pages/profile/invite.vue
@@ -0,0 +1,781 @@
+<template>
+    <view class="page">
+        <!-- 自定义导航栏:状态栏留白由行内 paddingTop 撑开 -->
+        <view class="nav" :style="{ paddingTop: statusBarHeight + 'px' }">
+            <view class="nav-bar">
+                <view class="nav-back" @tap="handleBack">
+                    <view class="icon-back"></view>
+                </view>
+                <text class="nav-title">我的邀请</text>
+            </view>
+        </view>
+
+        <!-- 三段式 Tab:邀请码 / 我邀请的 / 邀请我的 -->
+        <view class="tabs">
+            <view
+                v-for="tab in TAB_LIST"
+                :key="tab.key"
+                class="tab-item"
+                @tap="handleTabChange(tab.key)"
+            >
+                <text class="tab-text" :class="{ 'tab-text--active': activeTab === tab.key }">
+                    {{ tab.label }}
+                </text>
+                <!-- 选中态底部指示条 -->
+                <view v-if="activeTab === tab.key" class="tab-indicator"></view>
+            </view>
+        </view>
+
+        <view class="body">
+            <!-- ==================== Tab1:邀请码 ==================== -->
+            <view v-if="activeTab === 'code'" class="code-panel">
+                <view class="qrcode-card">
+                    <!-- 真实小程序码(base64 DataURI),扫码直达注册页并自动填入邀请码 -->
+                    <image v-if="qrcode" class="qrcode-img" :src="qrcode" mode="aspectFit" />
+                    <view v-else class="qrcode-placeholder">
+                        <text class="qrcode-placeholder-text">二维码生成中…</text>
+                    </view>
+                    <text class="qrcode-caption">我的邀请码</text>
+                    <!-- 点击复制邀请码 -->
+                    <view class="code-value" @tap="handleCopyCode">
+                        <text class="code-value-text">{{ inviteCode || '—' }}</text>
+                    </view>
+                </view>
+
+                <!-- 分享按钮:open-type=share 触发 onShareAppMessage -->
+                <button class="btn-share" open-type="share">
+                    <text class="btn-share-text">分享邀请码</text>
+                </button>
+            </view>
+
+            <!-- ==================== Tab2:我邀请的 ==================== -->
+            <view v-else-if="activeTab === 'invitees'" class="invitee-panel">
+                <view v-if="invitees.length > 0" class="invitee-list">
+                    <view v-for="item in invitees" :key="item.userId" class="invitee-row">
+                        <!-- 头像:为空回退默认用户图标 -->
+                        <view class="invitee-avatar">
+                            <image
+                                v-if="item.avatar"
+                                class="invitee-avatar-img"
+                                :src="item.avatar"
+                                mode="aspectFill"
+                            />
+                            <view v-else class="icon icon-user"></view>
+                        </view>
+                        <!-- 中部:昵称 + 等级徽标 / 脱敏手机号 -->
+                        <view class="invitee-main">
+                            <view class="invitee-name-line">
+                                <text class="invitee-name">{{ displayName(item) }}</text>
+                                <view class="level-badge">
+                                    <text class="level-badge-text">{{ item.systemLevel || 'C' }}</text>
+                                </view>
+                            </view>
+                            <text class="invitee-phone">{{ item.phone || '—' }}</text>
+                        </view>
+                        <!-- 右侧:合计消费 / 绑定日 -->
+                        <view class="invitee-side">
+                            <text class="invitee-amount">合计消费:¥{{ formatYuanAmount(item.buyAmountTotal) }}</text>
+                            <text v-if="item.bindDate" class="invitee-date">绑定日:{{ item.bindDate }}</text>
+                        </view>
+                    </view>
+                </view>
+
+                <!-- 空态:无邀请记录 -->
+                <view v-else-if="!listLoading" class="empty">
+                    <view class="empty-icon-wrap">
+                        <view class="icon icon-empty-invite"></view>
+                    </view>
+                    <text class="empty-title">还没有邀请好友</text>
+                    <text class="empty-desc">分享邀请码,邀请好友加入</text>
+                </view>
+
+                <!-- 加载更多提示 -->
+                <view v-if="invitees.length > 0" class="list-foot">
+                    <text class="list-foot-text">{{ hasMore ? '加载中…' : '没有更多了' }}</text>
+                </view>
+            </view>
+
+            <!-- ==================== Tab3:邀请我的 ==================== -->
+            <view v-else class="referrer-panel">
+                <view v-if="referrer && referrer.userId" class="referrer-card">
+                    <view class="referrer-avatar">
+                        <image
+                            v-if="referrer.avatar"
+                            class="referrer-avatar-img"
+                            :src="referrer.avatar"
+                            mode="aspectFill"
+                        />
+                        <view v-else class="icon icon-user-lg"></view>
+                    </view>
+                    <view class="referrer-name-line">
+                        <text class="referrer-name">{{ displayName(referrer) }}</text>
+                        <view class="level-badge">
+                            <text class="level-badge-text">{{ referrer.systemLevel || 'C' }}</text>
+                        </view>
+                    </view>
+                    <text class="referrer-phone">{{ referrer.phone || '—' }}</text>
+                    <text v-if="referrer.bindDate" class="referrer-date">绑定日:{{ referrer.bindDate }}</text>
+                </view>
+
+                <!-- 空态:无上级信息 -->
+                <view v-else-if="!referrerLoading" class="empty">
+                    <view class="empty-icon-wrap">
+                        <view class="icon icon-empty-referrer"></view>
+                    </view>
+                    <text class="empty-title">暂无上级信息</text>
+                    <text class="empty-desc">绑定上级后这里会展示ta的信息</text>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onLoad, onReachBottom, onShareAppMessage } from '@dcloudio/uni-app'
+import { ensureLogin } from '@/utils/request.js'
+import { getInviteCode, getInviteQrcode, getInvitees, getReferrer } from '@/api/index.js'
+
+/** 状态栏高度:自定义导航栏下用于顶部留白 */
+const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
+
+/** 每页条数:与后端 TableSupport 默认值保持一致 */
+const PAGE_SIZE = 10
+
+/** 三 Tab 配置:key 与后端无关,仅前端切换用 */
+const TAB_LIST = [
+    { key: 'code', label: '邀请码' },
+    { key: 'invitees', label: '我邀请的' },
+    { key: 'referrer', label: '邀请我的' }
+]
+
+/** 当前 Tab,默认「邀请码」 */
+const activeTab = ref('code')
+
+/** 本人邀请码原值(8 位 A-Z0-9) */
+const inviteCode = ref('')
+
+/** 本人邀请码小程序码(PNG base64 DataURI),直接作为 image src */
+const qrcode = ref('')
+
+/** 我邀请的 列表数据 */
+const invitees = ref([])
+/** 我邀请的 当前页码(下一页待请求页码) */
+const pageNum = ref(1)
+/** 我邀请的 是否还有下一页 */
+const hasMore = ref(true)
+/** 我邀请的 加载中标志(防止重复请求) */
+const listLoading = ref(false)
+
+/** 邀请我的 上级信息,无上级时为 null */
+const referrer = ref(null)
+/** 邀请我的 加载中标志 */
+const referrerLoading = ref(false)
+/** 邀请我的 是否已加载过(懒加载,避免重复请求) */
+const referrerLoaded = ref(false)
+
+/**
+ * 展示名:昵称优先,为空时回退脱敏手机号,均无则兜底「会员」
+ * @param {Object} user 含 nickname / phone 的用户对象
+ */
+const displayName = (user) => (user && (user.nickname || user.phone)) || '会员'
+
+/**
+ * 合计消费格式化:后端 buyAmountTotal 单位为「元」,保留两位小数
+ * 注意:不可复用 formatYuan(后者按「分」处理)
+ * @param {Number|String} val 金额(元)
+ */
+const formatYuanAmount = (val) => {
+    const num = Number(val)
+    return Number.isFinite(num) ? num.toFixed(2) : '0.00'
+}
+
+/** 拉取本人邀请码,成功后继续拉取小程序码 */
+const loadInviteInfo = async () => {
+    try {
+        const res = await getInviteCode()
+        inviteCode.value = (res.data && res.data.referralCode) || ''
+        if (inviteCode.value) {
+            loadQrcode(inviteCode.value)
+        }
+    } catch (e) {
+        console.error('[INVITE] 获取邀请码失败', e)
+    }
+}
+
+/**
+ * 拉取小程序码(以邀请码为 scene)
+ * @param {String} code 邀请码
+ */
+const loadQrcode = async (code) => {
+    try {
+        const res = await getInviteQrcode(code)
+        qrcode.value = (res.data && res.data.qrcode) || ''
+    } catch (e) {
+        console.error('[INVITE] 获取邀请二维码失败', e)
+        uni.showToast({ title: '二维码生成失败,请重试', icon: 'none' })
+    }
+}
+
+/**
+ * 拉取我邀请的列表(分页)
+ * @param {Boolean} reset 是否重置为第一页(首次进入 / 下拉刷新)
+ */
+const loadInvitees = async (reset = false) => {
+    if (listLoading.value) return
+    // 非重置且已无更多数据时直接返回,避免无效请求
+    if (!reset && !hasMore.value) return
+    if (reset) {
+        pageNum.value = 1
+        hasMore.value = true
+    }
+    listLoading.value = true
+    try {
+        const res = await getInvitees({ pageNum: pageNum.value, pageSize: PAGE_SIZE })
+        // 分页接口返回 TableDataInfo:数据在顶层 rows(非 data 包裹)
+        const rows = res.rows || []
+        invitees.value = reset ? rows : invitees.value.concat(rows)
+        const total = res.total || 0
+        // 已加载条数达到总数或本页返回空,视为无更多
+        hasMore.value = rows.length > 0 && invitees.value.length < total
+        if (hasMore.value) pageNum.value += 1
+    } catch (e) {
+        console.error('[INVITE] 获取邀请列表失败', e)
+    } finally {
+        listLoading.value = false
+    }
+}
+
+/** 拉取邀请我的(上级)信息 */
+const loadReferrer = async () => {
+    referrerLoading.value = true
+    try {
+        const res = await getReferrer()
+        referrer.value = res.data || null
+    } catch (e) {
+        console.error('[INVITE] 获取上级信息失败', e)
+        referrer.value = null
+    } finally {
+        referrerLoading.value = false
+        referrerLoaded.value = true
+    }
+}
+
+/** Tab 切换:进入「我邀请的」「邀请我的」时按需懒加载 */
+const handleTabChange = (key) => {
+    if (activeTab.value === key) return
+    activeTab.value = key
+    if (key === 'invitees' && invitees.value.length === 0) {
+        loadInvitees(true)
+    }
+    if (key === 'referrer' && !referrerLoaded.value) {
+        loadReferrer()
+    }
+}
+
+/** 点击邀请码:复制到剪贴板 */
+const handleCopyCode = () => {
+    if (!inviteCode.value) return
+    uni.setClipboardData({
+        data: inviteCode.value,
+        success: () => uni.showToast({ title: '邀请码已复制', icon: 'none' })
+    })
+}
+
+/** 返回:优先返回上一页,无上一页时回个人中心(Tab 页) */
+const handleBack = () => {
+    const pages = getCurrentPages()
+    if (pages.length > 1) {
+        uni.navigateBack()
+        return
+    }
+    uni.switchTab({ url: '/pages/profile/index' })
+}
+
+/** 页面加载:校验登录态并拉取邀请码与小程序码 */
+onLoad(() => {
+    if (!ensureLogin()) return
+    loadInviteInfo()
+})
+
+/** 触底加载更多(仅「我邀请的」Tab 生效) */
+onReachBottom(() => {
+    if (activeTab.value === 'invitees') {
+        loadInvitees(false)
+    }
+})
+
+/** 分享:携带邀请码直达注册页,扫码/点开注册即自动绑定 */
+onShareAppMessage(() => {
+    const path = inviteCode.value
+        ? `/pages/login/register?referralCode=${inviteCode.value}`
+        : '/pages/login/register'
+    return {
+        title: '邀请你加入代谢300,注册自动绑定邀请码',
+        path
+    }
+})
+</script>
+
+<style scoped lang="scss">
+.page {
+    min-height: 100vh;
+    display: flex;
+    flex-direction: column;
+    box-sizing: border-box;
+    background-color: #f8fafc;
+}
+
+/* ==================== 自定义导航栏 ==================== */
+
+.nav {
+    flex-shrink: 0;
+    background-color: #ffffff;
+}
+
+.nav-bar {
+    position: relative;
+    height: 88rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.nav-back {
+    position: absolute;
+    left: 16rpx;
+    top: 50%;
+    transform: translateY(-50%);
+    width: 64rpx;
+    height: 64rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+/* 纯 CSS 左向箭头:左 + 下边框旋转 45° */
+.icon-back {
+    width: 22rpx;
+    height: 22rpx;
+    border-left: 4rpx solid #0f172a;
+    border-bottom: 4rpx solid #0f172a;
+    transform: rotate(45deg);
+}
+
+.nav-title {
+    font-size: 34rpx;
+    font-weight: 700;
+    line-height: 1;
+    letter-spacing: 1rpx;
+    color: #0f172a;
+}
+
+/* ==================== 三段式 Tab ==================== */
+
+.tabs {
+    flex-shrink: 0;
+    display: flex;
+    align-items: center;
+    background-color: #ffffff;
+    border-bottom: 2rpx solid #e2e8f0;
+}
+
+.tab-item {
+    flex: 1;
+    height: 88rpx;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.tab-text {
+    font-size: 30rpx;
+    line-height: 1;
+    color: #64748b;
+}
+
+.tab-text--active {
+    font-weight: 600;
+    color: #10b981;
+}
+
+/* 选中态底部指示条 */
+.tab-indicator {
+    position: absolute;
+    bottom: 0;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 96rpx;
+    height: 4rpx;
+    border-radius: 4rpx;
+    background-color: #10b981;
+}
+
+.body {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+}
+
+/* ==================== Tab1:邀请码 ==================== */
+
+.code-panel {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    padding: 40rpx 40rpx 60rpx;
+}
+
+.qrcode-card {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 60rpx 32rpx 56rpx;
+    border-radius: 16rpx;
+    background-color: #ffffff;
+    border: 2rpx solid #e2e8f0;
+    box-shadow: 0 6rpx 16rpx rgba(15, 23, 42, 0.06);
+}
+
+.qrcode-img {
+    width: 400rpx;
+    height: 400rpx;
+}
+
+.qrcode-placeholder {
+    width: 400rpx;
+    height: 400rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 12rpx;
+    background-color: #f1f5f9;
+}
+
+.qrcode-placeholder-text {
+    font-size: 26rpx;
+    line-height: 1;
+    color: #94a3b8;
+}
+
+.qrcode-caption {
+    margin-top: 40rpx;
+    font-size: 30rpx;
+    font-weight: 600;
+    line-height: 1;
+    color: #0f172a;
+}
+
+.code-value {
+    margin-top: 20rpx;
+    padding: 12rpx 32rpx;
+    border-radius: 12rpx;
+    background-color: #ecfdf5;
+}
+
+.code-value-text {
+    font-size: 32rpx;
+    font-weight: 700;
+    letter-spacing: 6rpx;
+    line-height: 1;
+    color: #10b981;
+}
+
+/* 分享按钮:整宽绿色胶囊 */
+.btn-share {
+    margin-top: auto;
+    width: 100%;
+    height: 96rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0;
+    border: none;
+    border-radius: 48rpx;
+    background: linear-gradient(90deg, #34d399 0%, #10b981 100%);
+    box-shadow: 0 12rpx 32rpx rgba(16, 185, 129, 0.28);
+}
+
+/* 覆盖小程序 button 默认伪元素边框 */
+.btn-share::after {
+    border: none;
+}
+
+.btn-share-text {
+    font-size: 32rpx;
+    font-weight: 600;
+    line-height: 1;
+    color: #ffffff;
+}
+
+/* ==================== Tab2:我邀请的 ==================== */
+
+.invitee-panel {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    padding: 24rpx 32rpx 0;
+}
+
+.invitee-list {
+    border-radius: 16rpx;
+    overflow: hidden;
+    background-color: #ffffff;
+    border: 2rpx solid #e2e8f0;
+    box-shadow: 0 6rpx 16rpx rgba(15, 23, 42, 0.06);
+}
+
+.invitee-row {
+    display: flex;
+    align-items: center;
+    padding: 28rpx 28rpx;
+    border-bottom: 2rpx solid #f1f5f9;
+}
+
+/* 末行无分隔线 */
+.invitee-row:last-child {
+    border-bottom: none;
+}
+
+.invitee-avatar {
+    flex-shrink: 0;
+    width: 88rpx;
+    height: 88rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 50%;
+    overflow: hidden;
+    background-color: #f1f5f9;
+}
+
+.invitee-avatar-img {
+    width: 100%;
+    height: 100%;
+}
+
+.invitee-main {
+    flex: 1;
+    min-width: 0;
+    margin-left: 24rpx;
+    display: flex;
+    flex-direction: column;
+    gap: 12rpx;
+}
+
+.invitee-name-line {
+    display: flex;
+    align-items: center;
+    gap: 12rpx;
+}
+
+.invitee-name {
+    max-width: 240rpx;
+    font-size: 30rpx;
+    font-weight: 600;
+    line-height: 1;
+    color: #0f172a;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+
+.invitee-phone {
+    font-size: 26rpx;
+    line-height: 1;
+    color: #64748b;
+}
+
+.invitee-side {
+    flex-shrink: 0;
+    margin-left: 16rpx;
+    display: flex;
+    flex-direction: column;
+    align-items: flex-end;
+    gap: 12rpx;
+}
+
+.invitee-amount {
+    font-size: 28rpx;
+    font-weight: 600;
+    line-height: 1;
+    color: #0f172a;
+}
+
+.invitee-date {
+    font-size: 24rpx;
+    line-height: 1;
+    color: #94a3b8;
+}
+
+/* ==================== Tab3:邀请我的 ==================== */
+
+.referrer-panel {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 80rpx 40rpx 0;
+}
+
+.referrer-card {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 64rpx 32rpx;
+    border-radius: 16rpx;
+    background-color: #ffffff;
+    border: 2rpx solid #e2e8f0;
+    box-shadow: 0 6rpx 16rpx rgba(15, 23, 42, 0.06);
+}
+
+.referrer-avatar {
+    width: 160rpx;
+    height: 160rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 50%;
+    overflow: hidden;
+    background-color: #f1f5f9;
+}
+
+.referrer-avatar-img {
+    width: 100%;
+    height: 100%;
+}
+
+.referrer-name-line {
+    margin-top: 32rpx;
+    display: flex;
+    align-items: center;
+    gap: 16rpx;
+}
+
+.referrer-name {
+    font-size: 36rpx;
+    font-weight: 700;
+    line-height: 1;
+    color: #0f172a;
+}
+
+.referrer-phone {
+    margin-top: 24rpx;
+    font-size: 30rpx;
+    line-height: 1;
+    letter-spacing: 1rpx;
+    color: #334155;
+}
+
+.referrer-date {
+    margin-top: 20rpx;
+    font-size: 26rpx;
+    line-height: 1;
+    color: #94a3b8;
+}
+
+/* ==================== 等级徽标(实心绿胶囊) ==================== */
+
+.level-badge {
+    display: flex;
+    align-items: center;
+    padding: 4rpx 14rpx;
+    border-radius: 6rpx;
+    background-color: #10b981;
+}
+
+.level-badge-text {
+    font-size: 22rpx;
+    font-weight: 600;
+    line-height: 1;
+    color: #ffffff;
+}
+
+/* ==================== 空态 / 加载更多 ==================== */
+
+.empty {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 140rpx 0 0;
+}
+
+/* 圆形底衬 + 空态图标 */
+.empty-icon-wrap {
+    width: 200rpx;
+    height: 200rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 50%;
+    background-color: #f1f5f9;
+}
+
+.empty-title {
+    margin-top: 36rpx;
+    font-size: 30rpx;
+    font-weight: 600;
+    line-height: 1;
+    color: #475569;
+}
+
+.empty-desc {
+    margin-top: 16rpx;
+    font-size: 26rpx;
+    line-height: 1;
+    color: #94a3b8;
+}
+
+.list-foot {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 32rpx 0;
+}
+
+.list-foot-text {
+    font-size: 24rpx;
+    line-height: 1;
+    color: #cbd5e1;
+}
+
+/* ==================== 图标(内联 base64 SVG) ==================== */
+
+.icon {
+    flex-shrink: 0;
+    background-repeat: no-repeat;
+    background-position: center center;
+    background-size: 100% 100%;
+}
+
+/* 默认用户占位(#CBD5E1) */
+.icon-user {
+    width: 68rpx;
+    height: 68rpx;
+    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjQ0JENUUxIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTE5IDIxdi0yYTQgNCAwIDAgMC00LTRIOWE0IDQgMCAwIDAtNCA0djIiLz48Y2lyY2xlIGN4PSIxMiIgY3k9IjciIHI9IjQiLz48L3N2Zz4=');
+}
+
+/* 上级大头像占位(#CBD5E1) */
+.icon-user-lg {
+    width: 120rpx;
+    height: 120rpx;
+    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjQ0JENUUxIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTE5IDIxdi0yYTQgNCAwIDAgMC00LTRIOWE0IDQgMCAwIDAtNCA0djIiLz48Y2lyY2xlIGN4PSIxMiIgY3k9IjciIHI9IjQiLz48L3N2Zz4=');
+}
+
+/* 空态图标:邀请好友(#CBD5E1) */
+.icon-empty-invite {
+    width: 96rpx;
+    height: 96rpx;
+    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjY2JkNWUxIiBzdHJva2Utd2lkdGg9IjEuNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTUgMjF2LTJhNCA0IDAgMCAwLTQtNEg2YTQgNCAwIDAgMC00IDR2MiIvPjxjaXJjbGUgY3g9IjguNSIgY3k9IjciIHI9IjQiLz48cGF0aCBkPSJNMTkgOHY2Ii8+PHBhdGggZD0iTTIyIDExaC02Ii8+PC9zdmc+');
+}
+
+/* 空态图标:暂无上级(#CBD5E1) */
+.icon-empty-referrer {
+    width: 96rpx;
+    height: 96rpx;
+    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjY2JkNWUxIiBzdHJva2Utd2lkdGg9IjEuNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTkgMjF2LTJhNCA0IDAgMCAwLTQtNEg5YTQgNCAwIDAgMC00IDR2MiIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iNyIgcj0iNCIvPjwvc3ZnPg==');
+}
+</style>
diff --git a/hugray-user-miniapp/utils/request.js b/hugray-user-miniapp/utils/request.js
index 6e51d28..9bfa7a8 100644
--- a/hugray-user-miniapp/utils/request.js
+++ b/hugray-user-miniapp/utils/request.js
@@ -136,6 +136,100 @@
     }, 1500)
 }
 
+// ======================== 文件上传方法 ========================
+
+/**
+ * 统一文件上传方法(基于 uni.uploadFile)
+ * 与 request 保持一致的令牌注入、响应解包与登录失效处理,供图片/文件上传使用
+ * @param {String} url              上传接口路径(不含根地址;传完整 http 地址则直接使用)
+ * @param {String} filePath         本地文件路径(如 chooseImage 返回的 tempFilePath)
+ * @param {Object} [options]        可选配置
+ * @param {String} [options.name]   表单文件字段名,默认 file
+ * @param {Object} [options.formData] 附加表单参数
+ * @param {Object} [options.header] 自定义请求头
+ * @param {Boolean} [options.auth]  是否需要携带 token,默认 true
+ * @param {Boolean} [options.loading] 是否显示上传中提示,默认 true
+ * @param {String} [options.loadingText] 上传提示文案
+ * @returns {Promise<Object>} 解析后的响应体 { code, msg, data }
+ */
+export function uploadFile(url = '', filePath = '', options = {}) {
+    const {
+        name = 'file',
+        formData = {},
+        header = {},
+        auth = true,
+        loading = true,
+        loadingText = '上传中...'
+    } = options
+
+    // 拼接完整请求地址
+    const fullUrl = /^https?:\/\//i.test(url) ? url : config.baseUrl + url
+
+    // 组装请求头并注入令牌(multipart 边界由底层自动生成,此处不设 Content-Type)
+    const finalHeader = { ...header }
+    const token = getToken()
+    if (auth && token) {
+        finalHeader.Authorization = 'Bearer ' + token
+    }
+
+    if (loading) {
+        uni.showLoading({ title: loadingText, mask: true })
+    }
+
+    return new Promise((resolve, reject) => {
+        uni.uploadFile({
+            url: fullUrl,
+            filePath,
+            name,
+            formData,
+            header: finalHeader,
+            timeout: config.timeout,
+            success: (res) => {
+                const { statusCode } = res
+
+                // 非 HTTP 200 视为网络异常
+                if (statusCode !== 200) {
+                    reject(new Error('网络请求异常:' + statusCode))
+                    return
+                }
+
+                // uploadFile 返回的 data 为字符串,需解析为 JSON
+                let body
+                try {
+                    body = JSON.parse(res.data)
+                } catch (e) {
+                    reject(new Error('上传响应解析失败'))
+                    return
+                }
+
+                // 登录失效:清理登录态并跳转登录页
+                if (body && body.code === config.unauthorizedCode) {
+                    _handleUnauthorized()
+                    reject(new Error(body.msg || '登录状态已过期'))
+                    return
+                }
+
+                // 业务成功:返回完整响应体,调用方按需取 data
+                if (body && body.code === config.successCode) {
+                    resolve(body)
+                    return
+                }
+
+                // 业务失败:统一抛出错误信息
+                reject(new Error((body && body.msg) || '上传失败'))
+            },
+            fail: (err) => {
+                reject(new Error((err && err.errMsg) || '上传失败'))
+            },
+            complete: () => {
+                if (loading) {
+                    uni.hideLoading()
+                }
+            }
+        })
+    })
+}
+
 // ======================== 快捷方法 ========================
 
 /** GET 请求 */

--
Gitblit v1.7.1