Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)

含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 10:04:03 +00:00
commit 76f266645d
507 changed files with 99891 additions and 0 deletions
+407
View File
@@ -0,0 +1,407 @@
import 'package:flutter/material.dart';
import 'theme.dart';
/// 后端枚举 / 码 → 中文。对齐 web `labels.ts` + 投标状态。
const zhMap = <String, String>{
'TECH_INITIAL': '技术初审',
'FETCH': '取标',
'TECH_FINAL': '技术终审',
'DRAFTING': '制标',
'REVIEW1': '商务一审',
'REVIEW2': '商务二审',
'PRINT': '印封',
'LIST': '投标执行',
'PENDING': '待审批',
'WON': '中标',
'LOST': '未中标',
'FAILED': '流标废标',
'TERMINATED': '已终止',
'DRAFT': '草稿',
'APPROVED': '已通过',
'REJECTED': '已驳回',
'CANCELLED': '已取消',
'ACTIVE': '启用',
'DISABLED': '停用',
'OPEN': '进行中',
'OFFSET': '已冲账',
'DONE': '已完成',
'RETURNED': '已交回',
'PAID': '已付',
'UNPAID': '待付',
'TO_ISSUE': '待开',
'ISSUED': '已开',
'RECEIVED': '已回',
'OVERDUE': '逾期',
'INTERNAL_ACCEPTED': '内部已验收',
'ACCEPTED': '已验收',
'ALL': '全部',
'DEPT': '本部门',
'SELF': '本人',
'BID': '投标',
'PROJECT': '正式项目',
'ADMIN': '行政',
'MARKETING': '营销',
'LABOR': '人工',
'TRAVEL': '差旅',
'PURCHASE': '采购',
'TENDER': '招标',
'PERFORMANCE': '履约保证金',
'WARRANTY': '项目质保金',
'EXPENSE': '报销',
'BOND': '保证金打出',
'LOAN': '借款',
'OFFICE': '办公申请',
'SEAL': '用章',
'HR': '人事',
'LEAVE': '请假',
'OVERTIME': '加班',
'TRIP': '出差',
'OUT': '外出',
'RESIGN': '离职',
'PROMOTE': '晋升',
'INTERN': '实习',
'PROBATION': '试用',
'REGULAR': '正式',
'LEFT': '离职',
'SINGLE': '单休',
'DOUBLE': '双休',
'MALE': '',
'FEMALE': '',
'HIGH_SCHOOL': '高中及以下',
'COLLEGE': '专科',
'BACHELOR': '本科',
'MASTER': '硕士',
'DOCTOR': '博士',
'HIRE': '入职',
'REGULARIZE': '转正',
'TRANSFER': '调岗',
'CREATE': '发起',
'SUBMIT': '提交',
'APPROVE': '同意',
'REJECT': '驳回',
'DRAFTER': '制标人',
'FETCHER': '取标人',
'COORDINATOR': '特殊协调人',
'BIDDER': '投标人',
'SPECIAL': '特殊筛选',
'FLOW': '普通筛选',
'BID_TECH_INITIAL': '技术初审',
'BID_TECH_FINAL': '技术终审',
'BID_FETCH': '取标',
'BID_ASSIGN_FETCH': '派取标人',
'BID_ASSIGN_DRAFT': '派制标人',
'BID_FINAL_PASSED': '终审通过通知',
'BID_DRAFT_DONE': '制标完成通知',
'BID_TERMINATED': '投标终止通知',
'BID_DRAFT': '制标',
'BID_REVIEW1': '商务一审',
'BID_REVIEW2': '商务二审',
'BID_PRINT': '印封',
'BID_COMPLETE': '补全投标',
'BID_EXECUTION': '投标执行',
'BID_ASSIGN_FETCHER': '派取标人',
'UPDATE_COMPETITOR_WIN': '登记对方中标信息',
'UPDATE_LEGAL_ENTITY': '修改参与主体',
'CONFIRMED': '已确认',
'INTERNAL': '内部验收',
'CUSTOMER': '客户验收',
'CONTRACT_APPROVAL': '合同审批',
'CONTRACT_DRAFT': '完善合同草稿',
'EXPENSE_APPROVAL': '报销确认',
'LOAN_APPROVAL': '借款确认',
'LOAN_OFFSET': '借款冲账',
'PROJECT_TIMESHEET': '工时填报',
'PROJECT_ACCEPT': '项目验收',
'SEAL_APPROVAL': '用章审批',
'SEAL_RETURN': '印章归还',
'CREDENTIAL_RETURN': '证照归还',
'BID_OPEN': '开标反馈',
'WORK_ASSIGN': '工作安排',
'OFFICE_APPLY': '办公申请',
'HR_LEAVE': '人事申请',
'SEAL_REQUEST': '用章申请',
'WORK_REPORT': '工作汇报',
'NOTICE': '通知公告',
'ASSET_BUY': '资产购置',
'ASSET_USE': '资产领用',
'VEHICLE': '用车',
'DIGITAL': '数字资产',
'FIXED': '固定资产',
'IN_USE': '占用中',
'IN_STOCK': '在库',
'OUT_STOCK': '已出库',
'TAKEN_OUT': '外带',
'LENT': '外借',
'BORROWED': '已外借',
'IDLE': '闲置',
'SCRAPPED': '已报废',
'AVAILABLE': '可用',
'MAINTAIN': '维修中',
'OCCUPIED': '占用中',
'PENDING_IN': '待入库',
'ID_CARD': '身份证',
'PASSPORT': '护照',
'OTHER_ID': '其他证件',
'GENERAL': '通用',
'TODO': '待办',
'TASK': '任务截止',
'BORROW': '证照归还',
'LOGIN': '登录',
'LOGIN_FAIL': '登录失败',
'APPROVAL': '审批',
'FORBIDDEN': '越权',
'ACL': '权限',
'SENT': '已提交',
'UPDATE': '修改',
'DELETE': '删除',
'OK': '成功',
'FAIL': '失败',
'BUSINESS': '商务',
'TECH': '技术',
'FINANCE': '财务',
'PUBLISHED': '已发布',
'ANNOUNCE': '公告',
'POLICY': '制度',
'direct': '单聊',
'group': '群聊',
'admin': '管理员',
'owner': '负责人',
'hr': '人事',
'finance': '财务',
'finance_manager': '财务经理',
'cashier': '出纳',
'seal': '用章管理员',
'asset': '资产管理员',
'project_manager': '项目经理',
'tech_director': '技术总监',
'marketing': '营销',
'bidder': '投标',
'staff': '员工',
'user': '用户',
'true': '',
'false': '',
'INQUIRY': '询价',
'OPEN_TENDER': '公开招标',
'INVITE': '邀请招标',
'COMPETITIVE': '竞争性谈判',
'SINGLE_SOURCE': '单一来源',
};
/// 详情页字段名。没有映射的 camelCase 一律不展示,避免露出英文列名。
const fieldZh = <String, String>{
'title': '标题',
'name': '名称',
'purpose': '事由',
'reason': '事由',
'remark': '备注',
'status': '状态',
'kind': '类型',
'source': '来源',
'sourceType': '筛选类型',
'bizType': '业务',
'amount': '金额',
'days': '天数',
'claimNo': '报销单号',
'loanNo': '借款单号',
'bidNo': '招标编号',
'externalNo': '招标编号',
'contractNo': '合同号',
'projectNo': '项目编号',
'projectType': '项目类型',
'sealType': '印章类型',
'destination': '目的地',
'assetKind': '资产类型',
'createdAt': '创建时间',
'updatedAt': '更新时间',
'dueAt': '截止',
'startAt': '开始',
'endAt': '结束',
'decidedAt': '审批时间',
'comment': '意见',
'createdBy': '发起人',
'bidCase': '关联投标',
'legalEntity': '参与主体',
'qualificationNeed': '所需资质',
'tenderMethod': '招标方式',
'applyStartAt': '申领开始',
'applyEndAt': '申领截止',
'applyMethod': '申领方式',
'openAt': '开标时间',
'openMethod': '开标方式',
'openPlace': '开标地点',
'bondMethod': '保证金',
'bondStatus': '保证金状态',
'bondType': '保证金类型',
'delivererName': '递交人',
'contactName': '联系人',
'contactPhone': '联系电话',
'nasPath': '标书路径',
'summary': '摘要',
'resultNote': '结果说明',
'printCost': '印封费用',
'printNote': '印封备注',
'projectNature': '项目性质',
'securityLevel': '密级',
'priceCap': '限价',
'quoteAmount': '报价',
'needSurvey': '需要踏勘',
'fetchedAt': '取标时间',
'printedAt': '印封时间',
'openedResultAt': '开标反馈时间',
'actionLogs': '操作记录',
'projects': '关联项目',
'files': '附件',
'reviewers': '审批人',
'assignees': '经办人',
'versions': '标书版本',
'displayName': '姓名',
'departmentName': '部门',
'department': '部门',
'mobile': '手机',
'email': '邮箱',
'party': '客商',
'applicant': '申请人',
'assignee': '经办人',
'keeper': '保管人',
'holder': '占用人',
'spec': '规格',
'unit': '单位',
'qty': '数量',
'quantity': '数量',
'location': '位置',
'address': '地址',
'code': '编号',
'no': '编号',
'type': '类型',
'category': '分类',
'level': '级别',
'priority': '优先级',
'progress': '进度',
'budget': '预算',
'cost': '成本',
'owner': '负责人',
'manager': '负责人',
'description': '说明',
'content': '内容',
'body': '正文',
'note': '备注',
'result': '结果',
'outcome': '结果',
'method': '方式',
'channel': '渠道',
'scope': '范围',
'role': '角色',
'roles': '角色',
'employmentStatus': '用工状态',
'gender': '性别',
'idNo': '证件号',
'idType': '证件类型',
'idNumber': '证件号码',
'idCard': '身份证号',
'jobTitle': '岗位',
'hireDate': '入职日期',
'hiredAt': '入职日期',
'birthday': '出生日期',
'birthDate': '出生日期',
'education': '学历',
'school': '毕业院校',
'major': '专业',
'emergencyContact': '紧急联系人',
'emergencyPhone': '紧急电话',
'bankName': '开户行',
'bankAccount': '银行账号',
'workCity': '工作城市',
'officeLocation': '办公地点',
'employeeNo': '工号',
'staffNo': '工号',
'jobNo': '工号',
'realName': '姓名',
'username': '账号',
'phone': '手机',
'wechat': '微信',
'nation': '民族',
'nativePlace': '籍贯',
'maritalStatus': '婚姻状况',
'addressHome': '家庭住址',
'entryDate': '入职日期',
'regularDate': '转正日期',
'leaveDate': '离职日期',
'contractEnd': '合同到期',
'keeperName': '保管人',
'holderName': '占用人',
'legalEntityName': '参与主体',
'winnerName': '中标单位',
'winnerAmount': '中标金额',
};
String zh(dynamic v) {
if (v == null) return '';
if (v is bool) return v ? '' : '';
final s = '$v'.trim();
if (s.isEmpty) return '';
return zhMap[s] ?? s;
}
/// 字段名转中文。仍是英文驼峰的返回空串,调用方应跳过。
String fieldLabel(String k) {
final key = k.contains('.') ? k.split('.').last : k;
if (fieldZh.containsKey(k)) return fieldZh[k]!;
if (fieldZh.containsKey(key)) return fieldZh[key]!;
if (zhMap.containsKey(key)) return zhMap[key]!;
if (RegExp(r'[\u4e00-\u9fff]').hasMatch(key)) return key;
return '';
}
Color statusColor(dynamic v) {
switch ('$v') {
case 'APPROVED':
case 'DONE':
case 'OFFSET':
case 'RETURNED':
case 'PAID':
case 'WON':
case 'ACCEPTED':
case 'ISSUED':
case 'RECEIVED':
return kWeGreen;
case 'PENDING':
case 'OPEN':
case 'DRAFT':
case 'FETCH':
case 'DRAFTING':
case 'TECH_INITIAL':
case 'TECH_FINAL':
case 'REVIEW1':
case 'REVIEW2':
case 'PRINT':
case 'LIST':
return const Color(0xFFFA9D3B);
case 'REJECTED':
case 'CANCELLED':
case 'OVERDUE':
case 'TERMINATED':
case 'FAILED':
case 'LOST':
return kDanger;
default:
return kMute;
}
}
class StatusDot extends StatelessWidget {
const StatusDot(this.code, {super.key});
final dynamic code;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: statusColor(code).withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(4),
),
child: Text(zh(code), style: TextStyle(fontSize: 11, color: statusColor(code), fontWeight: FontWeight.w600)),
);
}
}