For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 交付可验收的纪检扫码反馈平台:微信 H5 提交反馈/建言 + Web 管理端台账办理与配置 + FastAPI 后端(含角色菜单授权)。
Architecture: 单仓三端——backend(FastAPI)提供 /api/public/* 与 /api/web/*;mobile-wx(uni-app→H5)只调公开接口;web-admin(Vue3+Element Plus)JWT 登录后按 sys_role_menu 动态菜单。MySQL 库 ajyy,MinIO 独立 bucket ajyy。
Tech Stack: Python 3.9+ / FastAPI 0.109 / SQLAlchemy 2 / PyMySQL / bcrypt / python-jose / minio;Vue 3.5 + Vite + Element Plus + Pinia + vue-router(hash) + Axios;uni-app Vue3 H5。
Spec: docs/superpowers/specs/2026-09-06-ajyy-feedback-platform-design.md
{ code: 200, message, data, timestamp }(毫秒时间戳)tenant_no;软删除 del_flag(0 正常 / 1 删除)id:UUID 字符串;附件 URL 列表:JSON 数组待处理 | 处理中 | 已办结;建言状态:待查阅 | 已采纳 | 已回复#a51f23;视觉/字段对照 原型图/index.html、原型图/admin.html../shanxia2.0/backend 等,但禁止拷贝 CRM/报销等业务模块TestClient;本地可用 SQLite(sqlite+pysqlite:///:memory:)跑单测,联调再用 MySQL ajyybackend/
requirements.txt
.env.example
pytest.ini
app/
main.py # 挂载路由、CORS、启动种子
core/
config.py # Settings
database.py # engine, SessionLocal, get_db
response.py # success_response / error_response
security.py # hash/verify password, create/decode JWT
deps.py # get_current_user, require_menu(*codes)
storage.py # MinIO 上传(STORAGE_TYPE=local|minio)
models/
__init__.py
system.py # SysUser, SysRole, SysMenu, SysRoleMenu, OperLog
business.py # Feedback, Suggestion, HotlineConfig, QrcodeConfig, Attachment
schemas/
auth.py
system.py
business.py
common.py # PageResult
crud/
user.py, role.py, menu.py, feedback.py, suggestion.py, config.py, attachment.py, oper_log.py
services/
auth_service.py
feedback_service.py
suggestion_service.py
config_service.py
dashboard_service.py
seed.py # 幂等种子
api/
public.py # /api/public/*
auth.py # /api/web/auth/*
admin.py # /api/web/admin/*
tests/
conftest.py
test_health.py
test_auth.py
test_public_feedback.py
test_admin_feedback.py
test_role_menus.py
docs/sql/
001_init.sql # 可选:MySQL DDL 对照
web-admin/
package.json
vite.config.ts
.env.development
index.html
src/
main.ts
App.vue
styles/index.css
utils/http.ts
api/auth.ts, admin.ts
stores/user.ts
router/index.ts
layouts/AdminLayout.vue
views/login/index.vue
views/dashboard/index.vue
views/feedback/index.vue
views/suggestion/index.vue
views/hotline/index.vue
views/qrcode/index.vue
views/user/index.vue
views/role/index.vue
views/audit/index.vue
mobile-wx/
pages.json # 注册全部 H5 页
config/api.js
utils/request.js
utils/upload.js
api/public.js
pages/home/index.vue
pages/notice/index.vue
pages/feedback/index.vue
pages/suggest/index.vue
pages/hotline/index.vue
(分享二维码可用 home 弹层,不必单独路由)
static/ # logo 等,可从 原型图/assets 拷贝
Files:
backend/requirements.txtbackend/pytest.inibackend/app/__init__.pybackend/app/main.pybackend/app/core/config.pybackend/app/core/response.pybackend/tests/conftest.pybackend/tests/test_health.pyInterfaces:
Produces: success_response(data=None, message="success") -> dict;Settings via get_settings();GET /api/health → {code:200, data:{status:"ok"}}
[ ] Step 1: Write failing health test
# backend/tests/test_health.py
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health():
r = client.get("/api/health")
assert r.status_code == 200
body = r.json()
assert body["code"] == 200
assert body["data"]["status"] == "ok"
assert "timestamp" in body
Run: cd backend && python -m pytest tests/test_health.py -v
Expected: import/collection error or 404
requirements.txt(精简):
fastapi==0.109.0
uvicorn[standard]==0.27.0
sqlalchemy==2.0.25
pymysql==1.1.0
pydantic==2.5.3
pydantic-settings==2.1.0
python-jose[cryptography]==3.3.0
bcrypt==4.0.1
python-multipart==0.0.6
python-dotenv==1.0.0
minio==7.2.20
httpx==0.26.0
pytest==8.0.0
app/core/response.py:照搬 shanxia 的 success_response / error_response(含毫秒 timestamp)。
app/core/config.py:
from functools import lru_cache
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
APP_NAME: str = "ajyy"
API_V1_PREFIX: str = "/api"
DATABASE_URL: str = "sqlite+pysqlite:///:memory:"
SECRET_KEY: str = "change-me-in-production"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 12
ALGORITHM: str = "HS256"
INIT_ADMIN_USERNAME: str = "admin"
INIT_ADMIN_PASSWORD: str = "Admin@123"
STORAGE_TYPE: str = "local" # local | minio
LOCAL_STORAGE_DIR: str = "./uploads"
MINIO_ENDPOINT: str = "127.0.0.1:9000"
MINIO_ACCESS_KEY: str = ""
MINIO_SECRET_KEY: str = ""
MINIO_BUCKET: str = "ajyy"
MINIO_SECURE: bool = False
MINIO_BASE_URL: str = ""
CORS_ORIGINS: str = "*"
class Config:
env_file = ".env"
extra = "ignore"
@lru_cache
def get_settings() -> Settings:
return Settings()
app/main.py:创建 FastAPI,CORS,GET /api/health 返回 success_response({"status": "ok"})。
Run: cd backend && pip install -r requirements.txt && python -m pytest tests/test_health.py -v
Expected: PASS
git add backend
git commit -m "feat(backend): scaffold FastAPI app with health check"
Files:
backend/app/core/database.pybackend/app/models/system.pybackend/app/models/business.pybackend/app/models/__init__.pybackend/app/services/seed.pybackend/app/main.py(startup 建表+种子)backend/tests/conftest.pybackend/tests/test_seed.pybackend/docs/sql/001_init.sql(MySQL 对照 DDL,可选但推荐)Interfaces:
init_db() 建表;seed_if_empty(db) 幂等写入 3 角色、菜单树、admin 用户、热线/二维码各 1 行Menu codes(固定):dashboard,feedback,suggestion,hotline,qrcode,user,role,audit;按钮:feedback:handle,suggestion:handle,hotline:write,qrcode:write,user:manage,role:grant
[ ] Step 1: Write seed test
# backend/tests/test_seed.py
def test_seed_creates_admin_and_roles(db_session):
from app.services.seed import seed_if_empty
from app.models.system import SysUser, SysRole, SysMenu, SysRoleMenu
seed_if_empty(db_session)
seed_if_empty(db_session) # idempotent
assert db_session.query(SysRole).count() == 3
assert db_session.query(SysUser).filter_by(username="admin").count() == 1
assert db_session.query(SysMenu).count() >= 8
assert db_session.query(SysRoleMenu).count() >= 1
Run: cd backend && python -m pytest tests/test_seed.py -v
Expected: FAIL import/missing
database.py:create_engine(settings.DATABASE_URL, ...);SQLite 时 connect_args={"check_same_thread": False};SessionLocal;get_db;Base = declarative_base();init_db() → Base.metadata.create_all。
模型字段(必须):
SysUser: id, username, password_hash, display_name, role_id, status(0启用/1停用), del_flag, create_time, update_time, create_by, update_by
SysRole: id, role_code, role_name, remark, del_flag, ...
SysMenu: id, parent_id, menu_code, menu_name, path, menu_type(M目录/C菜单/F按钮), sort, visible, del_flag, ...
SysRoleMenu: id, role_id, menu_id
Feedback: id, biz_no, fb_type(投诉|表扬), target_name, target_dept, target_post, content, reporter_name, reporter_phone, attachments(JSON), status, handle_remark, del_flag, timestamps
Suggestion: id, biz_no, content, contact_name, contact_unit, contact_post, contact_phone, attachments(JSON), status, handle_remark, del_flag, timestamps
HotlineConfig: id, phone, email, address, work_time, ...
QrcodeConfig: id, land_url, title, image_url, ...
Attachment: id, file_name, object_key, url, size, biz_type, biz_id, ...
OperLog: id, user_id, username, module, action, detail, ip, create_time
种子授权矩阵:
| role_code | 菜单/按钮 |
|---|---|
| admin | 全部 |
| handler | dashboard, feedback, suggestion, feedback:handle, suggestion:handle |
| viewer | dashboard, feedback, suggestion(无 handle) |
密码用 bcrypt hash(passlib 或直接 bcrypt.hashpw)。
Run: cd backend && python -m pytest tests/test_seed.py -v
Expected: PASS
git add backend
git commit -m "feat(backend): add models, init_db, and idempotent seed data"
Files:
backend/app/core/security.pybackend/app/core/deps.pybackend/app/schemas/auth.pybackend/app/crud/user.pybackend/app/crud/menu.pybackend/app/services/auth_service.pybackend/app/api/auth.pybackend/app/main.py(include router)backend/tests/test_auth.pybackend/tests/conftest.py(提供已种子的 client)Interfaces:
Produces:
POST /api/web/auth/login body {username, password} → {token, tokenType:"Bearer"}GET /api/web/auth/current-user Bearer → {id, username, displayName, roleCode, roleName}GET /api/web/auth/menus → 树形菜单列表(仅 menu_type in M/C,按 sort)POST /api/web/auth/logout → successrequire_menu(*codes):当前用户角色若无任一 code → 403get_current_user:无效/过期 token → 401[ ] Step 1: Write auth tests
def test_login_and_menus(client):
r = client.post("/api/web/auth/login", json={"username": "admin", "password": "Admin@123"})
assert r.status_code == 200
token = r.json()["data"]["token"]
me = client.get("/api/web/auth/current-user", headers={"Authorization": f"Bearer {token}"})
assert me.json()["data"]["roleCode"] == "admin"
menus = client.get("/api/web/auth/menus", headers={"Authorization": f"Bearer {token}"})
codes = {m["menuCode"] for m in _flatten(menus.json()["data"])}
assert "dashboard" in codes
assert "role" in codes
def test_login_wrong_password(client):
r = client.post("/api/web/auth/login", json={"username": "admin", "password": "bad"})
assert r.status_code == 200
assert r.json()["code"] == 401 # 或 HTTP 401;全项目统一一种,推荐 HTTP 401
(选定:HTTP 状态码 401/403,body 仍可用 error_response。)
[ ] Step 2: Run — expect fail
[ ] Step 3: Implement security + auth routes
create_access_token(sub: str, extra: dict) -> str;decode_token;OAuth2 bearer。
登录失败写 oper_log 可选;成功登录写 oper_log(module=auth, action=login)。
JSON 字段对外 camelCase(Pydantic alias_generator 或手动 dict)。
git add backend
git commit -m "feat(backend): add JWT login, current-user, and menus API"
Files:
backend/app/core/storage.pybackend/app/crud/attachment.pybackend/app/api/public.py(先只 upload + 空路由壳)backend/tests/test_upload.pybackend/.env.exampleInterfaces:
storage.upload_file(content: bytes, filename: str, folder: str) -> dict 含 url, objectKeyPOST /api/public/attachment/upload multipart field file,可选 folder → {id, url, fileName, size}STORAGE_TYPE=local 时落到 LOCAL_STORAGE_DIR,URL 形如 /uploads/...;并用 StaticFiles 挂载便于本地预览
[ ] Step 1: Write upload test (local storage)
def test_public_upload(client, tmp_path, monkeypatch):
monkeypatch.setenv("STORAGE_TYPE", "local")
monkeypatch.setenv("LOCAL_STORAGE_DIR", str(tmp_path))
# 重新 load settings / app 若需要
files = {"file": ("a.png", b"fake-png-bytes", "image/png")}
r = client.post("/api/public/attachment/upload", files=files)
assert r.status_code == 200
data = r.json()["data"]
assert data["url"]
assert data["id"]
git commit -m "feat(backend): add local/minio storage and public upload API"
Files:
backend/app/schemas/business.pybackend/app/crud/feedback.pybackend/app/crud/suggestion.pybackend/app/crud/config.pybackend/app/services/feedback_service.pybackend/app/services/suggestion_service.pybackend/app/services/config_service.pybackend/app/api/public.pybackend/tests/test_public_feedback.pyInterfaces:
GET /api/public/hotline → 热线对象GET /api/public/qrcode → 二维码配置POST /api/public/feedback body:
{fbType, targetName?, targetDept?, targetPost?, content, reporterName?, reporterPhone?, attachmentUrls?: string[]}
→ {id, bizNo};默认 status=待处理;biz_no=FK+YYYYMMDD+3 位日序号POST /api/public/suggestion 类似;biz_no 前缀 JY;status=待查阅content 空 → 400
[ ] Step 1: Tests
def test_submit_feedback(client):
r = client.post("/api/public/feedback", json={
"fbType": "投诉", "content": "测试投诉内容"
})
assert r.status_code == 200
assert r.json()["data"]["bizNo"].startswith("FK")
def test_feedback_requires_content(client):
r = client.post("/api/public/feedback", json={"fbType": "投诉", "content": ""})
assert r.status_code == 400
def test_get_hotline(client):
assert client.get("/api/public/hotline").json()["code"] == 200
git commit -m "feat(backend): add public feedback, suggestion, and config APIs"
Files:
backend/app/api/admin.pybackend/app/services/dashboard_service.pybackend/app/crud/oper_log.pybackend/tests/test_admin_feedback.pyInterfaces:
GET /api/web/admin/dashboard → {feedbackTotal, complaintPending, suggestionTotal, weekNew, recentFeedback:[]}GET /api/web/admin/feedbacks?status=&fbType=&keyword=&page=1&pageSize=10 → {list, total, page, pageSize}GET /api/web/admin/feedbacks/{id}PUT /api/web/admin/feedbacks/{id}/status body {status, handleRemark?} — 需 feedback:handle;非法状态迁移 → 400;写 oper_log/suggestions,权限 suggestion:handleGET/PUT /api/web/admin/hotline — PUT 需 hotline:writeGET/PUT /api/web/admin/qrcode — PUT 需 qrcode:writePOST /api/web/admin/attachment/upload — 需登录(任意已登录角色)状态迁移允许:
待处理→处理中|已办结;处理中→已办结;同状态仅更新备注允许建言:待查阅→已采纳|已回复;已采纳/已回复之间互转允许
[ ] Step 1: Tests(viewer 无办理权限;handler 可办理;admin 可改配置)
用种子额外创建 handler/viewer 测试用户,或在 test 里直接插用户。
def test_viewer_cannot_handle(client, viewer_headers):
# 先造一条反馈
fb = client.post("/api/public/feedback", json={"fbType": "投诉", "content": "x"*4}).json()["data"]
r = client.put(f"/api/web/admin/feedbacks/{fb['id']}/status",
headers=viewer_headers,
json={"status": "处理中"})
assert r.status_code == 403
git commit -m "feat(backend): add admin dashboard, ledger, handle, and config APIs"
Files:
backend/app/schemas/system.pybackend/app/crud/role.pybackend/app/api/admin.pybackend/tests/test_role_menus.pyInterfaces:
GET /api/web/admin/users — 需 user:managePOST /api/web/admin/users body {username, password, displayName, roleId}PUT /api/web/admin/users/{id} body {displayName?, roleId?, status?}PUT /api/web/admin/users/{id}/reset-password body {password}GET /api/web/admin/rolesGET /api/web/admin/menus/tree — 全量菜单树(授权页用)GET /api/web/admin/roles/{id}/menus → menuIds: string[]PUT /api/web/admin/roles/{id}/menus body {menuIds: string[]} — 需 role:grant;全量替换 sys_role_menuGET /api/web/admin/oper-logs?page&pageSize — 需登录且菜单 audit(admin 种子有)
[ ] Step 1: Test role grant changes menus for user
def test_role_grant_updates_menus(client, admin_headers, db_session):
roles = client.get("/api/web/admin/roles", headers=admin_headers).json()["data"]
viewer = next(r for r in roles if r["roleCode"] == "viewer")
# 去掉 suggestion 菜单后再查 viewer 用户 menus…
git commit -m "feat(backend): add user admin, role menu grant, and oper-log list"
Files:
web-admin/ 全套 Vite Vue3 TS 工程(可用 npm create vite@latest web-admin -- --template vue-ts 后加依赖)web-admin/src/** 登录/布局/路由/store/httpInterfaces:
Produces: 可 pnpm/npm run dev;登录后按 menus 渲染侧栏;无 token 跳 /login
[ ] Step 1: Scaffold
cd /Users/wuzilong/work/00code/ajyy
npm create vite@latest web-admin -- --template vue-ts
cd web-admin && npm i && npm i element-plus vue-router@4 pinia axios @element-plus/icons-vue
.env.development:
VITE_API_BASE_URL=/api
VITE_API_PROXY_TARGET=http://127.0.0.1:8000
vite.config.ts:proxy /api → VITE_API_PROXY_TARGET;hash history。
utils/http.ts:axios 实例,请求头 Authorization: Bearer ${token};code!==200 时 ElMessage;401 清 token 跳登录。
stores/user.ts:token, user, menus, login, fetchProfile, logout;hasPerm(code: string) 递归 menus + 按钮(登录后可另存 permCodes: string[]——建议 current-user 或 menus 接口同时返回按钮 codes;实现约定:GET /menus 返回含按钮的扁平 permCodes 放在响应 data.permCodes,树在 data.tree)。
若 Task 3 的 menus 仅返回树,则在本 Task 回头小改后端:
{ "tree": [...], "permCodes": ["feedback:handle", ...] }
并补一条后端测试断言 permCodes。
路由表:静态 /login;业务路由 meta.menuCode 与菜单 path 对齐:
| path | menuCode | component |
|---|---|---|
/dashboard |
dashboard | dashboard |
/feedback |
feedback | feedback |
/suggestion |
suggestion | suggestion |
/hotline |
hotline | hotline |
/qrcode |
qrcode | qrcode |
/user |
user | user |
/role |
role | role |
/audit |
audit | audit |
守卫:无 token → login;有 token 无对应 menuCode → /dashboard 或 403 页。
侧栏主色可用纪检红接近色(管理端不必强制 #a51f23,但 logo/标题用「安徽建工集团医院纪检室」)。
Run backend: uvicorn app.main:app --reload --port 8000
Run admin: npm run dev
登录 admin / Admin@123,侧栏应出现全部菜单。
git add web-admin
git commit -m "feat(web-admin): scaffold app with login and dynamic menus"
Files:
web-admin/src/views/dashboard/index.vueweb-admin/src/views/feedback/index.vueweb-admin/src/views/suggestion/index.vueweb-admin/src/views/hotline/index.vueweb-admin/src/views/qrcode/index.vueweb-admin/src/api/admin.tsInterfaces: Consumes Task 6 APIs。字段与交互对照 原型图/admin.html。
[ ] Step 1: dashboard — 四统计卡片 + 最近反馈表(编号、类型、摘要、时间、状态)
[ ] Step 2: feedback list — 状态下拉、类型、关键词;表格;详情 Drawer;办理区:状态下拉+备注+保存;无 feedback:handle 时隐藏保存
[ ] Step 3: suggestion — 对称实现
[ ] Step 4: hotline + qrcode forms — 保存调用 PUT;二维码支持上传自定义图(admin upload)+ 预览;无写权限则表单 disabled
[ ] Step 5: Manual verify with handler/viewer accounts(Task 7 可先用 SQL/接口创建)+ Commit
git commit -m "feat(web-admin): add dashboard, ledger, and config pages"
Files:
web-admin/src/views/user/index.vueweb-admin/src/views/role/index.vueCreate: web-admin/src/views/audit/index.vue
[ ] Step 1: 用户列表 — 新建、启停、重置密码、改角色(Element Plus Table + Dialog)
[ ] Step 2: 角色授权页 — 左侧角色列表,右侧 el-tree show-checkbox 绑定全量菜单树;保存 PUT .../menus
[ ] Step 3: 审计列表 — 简单分页表
[ ] Step 4: Verify — 去掉 viewer 的 suggestion 菜单后,用 viewer 登录侧栏不再出现建言;恢复后出现
[ ] Step 5: Commit
git commit -m "feat(web-admin): add user admin, role grant, and audit pages"
Files:
mobile-wx/pages.jsonmobile-wx/App.vue(全局样式变量 --primary:#a51f23)mobile-wx/config/api.jsmobile-wx/utils/request.jsmobile-wx/utils/upload.jsmobile-wx/api/public.jsmobile-wx/pages/home/index.vuemobile-wx/pages/notice/index.vuemobile-wx/pages/feedback/index.vuemobile-wx/pages/suggest/index.vuemobile-wx/pages/hotline/index.vuemobile-wx/pages/index/index.vueInterfaces: Consumes Task 4–5 public APIs。UI 对照 原型图/index.html(布局/文案/倒计时 5 秒)。
// config/api.js
export const BASE_URL = 'http://127.0.0.1:8000/api' // H5 开发可改;真机用局域网 IP
request.js:uni.request 封装,解析 code===200。
upload.js:uni.chooseImage / uni.chooseFile + uni.uploadFile → /public/attachment/upload。
[ ] Step 2: pages.json 注册五页;首页为 pages/home/index;navigationBar 红色
[ ] Step 3: 实现各页
home:四大入口;分享弹层读 /public/qrcode(有 imageUrl 用图,否则用 landUrl + 第三方或 canvas 生成二维码——可用纯图片配置优先,避免强依赖库;若无自定义图则展示 URL 文本 + 提示「请使用后台上传二维码图」或引入轻量 uQRCode)
notice:须知 + 5s countdown 后可点「已阅读并继续」
feedback:类型切换、被反馈人、内容、附件、匿名联系方式、提交成功 Toast/回首页
suggest:对称
hotline:展示配置,tel: 拨打
[ ] Step 4: HBuilderX 运行到浏览器 / 微信开发者工具 H5,走通提交 → 管理端可见
[ ] Step 5: Commit
git add mobile-wx
git commit -m "feat(mobile-wx): add H5 feedback portal pages wired to public API"
Files:
backend/.env.example(若未完备则补全)docs/deploy-test.mdCreate: docs/acceptance-checklist.md
[ ] Step 1: 写测试环境说明
docs/deploy-test.md 内容须含:
ajyy(utf8mb4)DATABASE_URL=mysql+pymysql://USER:PASS@HOST:3306/ajyySTORAGE_TYPE=minio,bucket ajyy,填写与 shanxia 测试环境相同 endpoint/密钥uvicorn app.main:app --host 0.0.0.0 --port 8000/apiqrcode_config.land_url[ ] Step 2: 验收清单 — 逐项对应建设清单 1–17(功能),每项可勾选
[ ] Step 3: 在测试环境实际跑通最小路径(若凭证可用):提交一条反馈 + admin 办理 + 改热线在 H5 可见
[ ] Step 4: Commit
git add docs backend/.env.example
git commit -m "docs: add test deploy guide and acceptance checklist"
| Spec 项 | Task |
|---|---|
| 仓库三端结构 | 1, 8, 11 |
| MySQL ajyy / MinIO bucket | 2, 4, 12 |
| sys_user/role/menu/role_menu | 2, 3, 7 |
| feedback/suggestion/hotline/qrcode/attachment/oper_log | 2, 5, 6, 7 |
| 公开 API | 4, 5 |
| JWT 登录 / menus / 角色授权页 | 3, 7, 8, 10 |
| 管理台账办理/工作台/配置 | 6, 9 |
| 用户管理 | 7, 10 |
| mobile 6 页能力 | 11 |
| 不做限流脱敏/小程序 | 全局约束 + 未列入任务 |
| 验收/部署说明 | 12 |
{ tree, permCodes }(Task 8 若需则回改 Task 3)id/urladmin / Admin@123(仅 .env / 种子,上线必改)