xsh_1997 il y a 3 semaines
Parent
commit
4b311006ff
34 fichiers modifiés avec 8061 ajouts et 8 suppressions
  1. 4 0
      huimv-employment/app/.gitignore
  2. 11 0
      huimv-employment/app/App.vue
  3. 150 0
      huimv-employment/app/common/chat-data.js
  4. 1435 0
      huimv-employment/app/common/fe.scss
  5. 10 0
      huimv-employment/app/common/mp-nav.js
  6. 81 0
      huimv-employment/app/common/store.js
  7. 230 0
      huimv-employment/app/components/EnterpriseHubPanel.vue
  8. 37 0
      huimv-employment/app/components/FeBottomNav.vue
  9. 89 0
      huimv-employment/app/components/FeMpNavBar.vue
  10. 78 0
      huimv-employment/app/components/FePageBar.vue
  11. 215 0
      huimv-employment/app/components/chat/FeBatchQrModal.vue
  12. 242 0
      huimv-employment/app/components/chat/FeCostSheet.vue
  13. 232 0
      huimv-employment/app/components/chat/FeDraftSheet.vue
  14. 137 0
      huimv-employment/app/components/chat/FeProgressSheet.vue
  15. 104 0
      huimv-employment/app/components/chat/FeReportFloat.vue
  16. 122 0
      huimv-employment/app/components/chat/FeReportSheet.vue
  17. 78 0
      huimv-employment/app/components/chat/FeSettlementSheet.vue
  18. 2378 0
      huimv-employment/app/doc/flexible-employment-demo.html
  19. 535 0
      huimv-employment/app/doc/微信小程序功能需求.md
  20. 20 0
      huimv-employment/app/index.html
  21. 34 0
      huimv-employment/app/main.js
  22. 72 0
      huimv-employment/app/manifest.json
  23. 44 0
      huimv-employment/app/pages.json
  24. 152 0
      huimv-employment/app/pages/auth/login.vue
  25. 624 0
      huimv-employment/app/pages/chat/index.vue
  26. 409 0
      huimv-employment/app/pages/employer/chat.vue
  27. 52 0
      huimv-employment/app/pages/index/index.vue
  28. 101 0
      huimv-employment/app/pages/worker/profile.vue
  29. 174 0
      huimv-employment/app/pages/worker/register.vue
  30. 142 0
      huimv-employment/app/pages/worker/wallet.vue
  31. BIN
      huimv-employment/app/static/logo.png
  32. 13 0
      huimv-employment/app/uni.promisify.adaptor.js
  33. 56 0
      huimv-employment/app/uni.scss
  34. 0 8
      huimv-employment/fe-miniapp/README.md

+ 4 - 0
huimv-employment/app/.gitignore

@@ -0,0 +1,4 @@
1
+node_modules/
2
+unpackage/
3
+.hbuilderx/
4
+.DS_Store

+ 11 - 0
huimv-employment/app/App.vue

@@ -0,0 +1,11 @@
1
+<script>
2
+export default {
3
+	onLaunch() {
4
+		console.log('App Launch')
5
+	}
6
+}
7
+</script>
8
+
9
+<style lang="scss">
10
+@import './common/fe.scss';
11
+</style>

+ 150 - 0
huimv-employment/app/common/chat-data.js

@@ -0,0 +1,150 @@
1
+export const CHAT_SESSIONS = [
2
+	{ id: 'employment', title: '用工需求咨询', time: '今天 14:30' },
3
+	{ id: 'knowledge', title: '用工知识咨询', time: '今天 10:15' },
4
+	{ id: 'order', title: '用工订单管理', time: '昨天 16:20' },
5
+	{ id: 'cost', title: '成本测算', time: '3天前' },
6
+	{ id: 'attendance', title: '现场考勤记录', time: '3天前' },
7
+	{ id: 'emergency', title: '紧急事件处理', time: '1周前' }
8
+]
9
+
10
+export const CHAT_DATA = {
11
+	employment: [
12
+		{ type: 'text', role: 'ai', content: '老板早上好!为您汇报昨日进度:' },
13
+		{ type: 'report', role: 'ai' },
14
+		{ type: 'draft', role: 'ai' },
15
+		{ type: 'text', role: 'user', content: '招10个搬运工,浦东仓库,2天' },
16
+		{ type: 'summary1', role: 'ai' },
17
+		{ type: 'summary2', role: 'ai' }
18
+	],
19
+	knowledge: [
20
+		{ type: 'text', role: 'ai', content: '我可以为您解答灵活用工政策法规、税务处理、合同管理、社保保险、用工风险等方面的问题。请告诉我您想了解哪方面的内容?' },
21
+		{ type: 'knowledge_actions', role: 'ai' }
22
+	],
23
+	order: [
24
+		{ type: 'text', role: 'ai', content: '当前订单状态:' },
25
+		{ type: 'orders', role: 'ai' }
26
+	],
27
+	cost: [
28
+		{ type: 'text', role: 'ai', content: '请告诉我用工人数、工作天数和日薪标准,我来帮您精准测算成本。例如:"10个人,工作2天,日薪500元"' }
29
+	],
30
+	attendance: [
31
+		{ type: 'text', role: 'ai', content: '请用自然语言描述今天的考勤情况,例如:"今天张三请假,李四加班了2小时,王五计件做了500个"。我会自动匹配当前批次并生成考勤卡片。' }
32
+	],
33
+	emergency: [
34
+		{ type: 'text', role: 'ai', content: '当发生紧急事件时,请描述事件情况,我会生成处理指南并帮您一键生成保险报案草稿。例如:"有工人搬东西砸到脚了,现在去医院"' }
35
+	]
36
+}
37
+
38
+export const BATCH_QR = {
39
+	modalTitle: '方案已确认',
40
+	subtitle: '浦东仓库搬运 BATCH-001',
41
+	hint: '分享给临时工扫码即可登记',
42
+	batchId: 'BATCH-001',
43
+	title: '浦东仓库搬运',
44
+	registered: 7,
45
+	total: 10,
46
+	shareLink: 'https://fe.example.com/r/BATCH-001'
47
+}
48
+
49
+// 15×15 演示二维码点阵(真机兼容,不依赖 SVG 图片)
50
+export const QR_DEMO_PATTERN =
51
+	'111111111111111' +
52
+	'100000000000001' +
53
+	'101111011111101' +
54
+	'101010010101101' +
55
+	'101111011111101' +
56
+	'100000000000001' +
57
+	'111111011111111' +
58
+	'000000011010000' +
59
+	'101101011110101' +
60
+	'010101010101010' +
61
+	'101011011110101' +
62
+	'010100010101010' +
63
+	'101111011111101' +
64
+	'100000000000001' +
65
+	'111111111111111'
66
+
67
+export const DAILY_REPORT = {
68
+	date: '今天',
69
+	items: [
70
+		{ title: '浦东仓库', desc: '搬运 BATCH-001', value: '8/10 人已登记', status: 'normal' },
71
+		{ title: '王五实名失败', desc: '请提醒临时工重新提交身份信息', value: '已自动提醒', status: 'danger' },
72
+		{ title: '结算单待确认', desc: '浦东仓库搬运 BATCH-001', value: '¥12,800', status: 'pending' }
73
+	]
74
+}
75
+
76
+export const PROGRESS_PERSONS = [
77
+	{ name: '张三', phone: '138****1234', status: 'done', statusText: '已完成' },
78
+	{ name: '李四', phone: '139****5678', status: 'done', statusText: '已完成' },
79
+	{ name: '王五', phone: '137****9012', status: 'fail', statusText: '核验失败' },
80
+	{ name: '赵六', phone: '136****3456', status: 'pending', statusText: '待登记' },
81
+	{ name: '钱七', phone: '135****7890', status: 'review', statusText: '审核中' }
82
+]
83
+
84
+export const PROGRESS_STEPS = [
85
+	{ key: 'draft', title: '草稿创建', time: '01-15 14:30', status: 'done' },
86
+	{ key: 'plan', title: '方案确认', time: '01-15 14:35', status: 'done' },
87
+	{ key: 'register', title: '扫码登记', status: 'active', highlight: '进行中 7/10' },
88
+	{ key: 'review', title: '企业审核', status: 'pending' },
89
+	{ key: 'contract', title: '合同签署', status: 'pending' },
90
+	{ key: 'work', title: '开始用工', status: 'pending' }
91
+]
92
+
93
+export const TRADITIONAL_COST_PLAN = {
94
+	icon: 'ⓘ',
95
+	title: '传统用工 (B)',
96
+	total: '¥15,200',
97
+	avgCost: '¥760/天'
98
+}
99
+
100
+export const COST_PLANS = [
101
+	{
102
+		id: 'a',
103
+		recommended: true,
104
+		icon: '⭐',
105
+		title: '合规优先 (A)',
106
+		total: '¥12,800',
107
+		avgCost: '¥640/天',
108
+		rows: [
109
+			{ label: '到手金额', value: '¥500/天' }
110
+		],
111
+		breakdown: [
112
+			{ label: '劳务费', value: '¥10,000' },
113
+			{ label: '服务费(8%)', value: '¥800' },
114
+			{ label: '保险(高档)', value: '¥200' },
115
+			{ label: '税费代征', value: '¥1,800' }
116
+		],
117
+		compliance: ['业务真实', '资金合规', '个税申报']
118
+	},
119
+	{
120
+		id: 'c',
121
+		icon: '💡',
122
+		title: '成本优先 (C)',
123
+		total: '¥11,200',
124
+		avgCost: '¥560/天',
125
+		rows: [
126
+			{ label: '到手金额', value: '¥420/天' }
127
+		],
128
+		breakdown: [
129
+			{ label: '劳务费', value: '¥9,000' },
130
+			{ label: '服务费(8%)', value: '¥720' },
131
+			{ label: '保险(基础)', value: '¥80' },
132
+			{ label: '税费代征', value: '¥1,400' }
133
+		],
134
+		compliance: ['业务真实', '资金合规']
135
+	}
136
+]
137
+
138
+export const MESSAGES = [
139
+	{ type: 'pending', title: '结算单待确认', desc: '浦东仓库搬运 BATCH-001 · ¥12,800', time: '10分钟前' },
140
+	{ type: 'error', title: '王五实名核验失败', desc: '请提醒临时工重新提交身份信息', time: '1小时前' }
141
+]
142
+
143
+export const WALLET_RECORDS = [
144
+	{ title: '浦东仓库搬运', meta: '01-20 · 已完成', amount: '¥1,000' },
145
+	{ title: '虹桥活动执行', meta: '01-15 · 已完成', amount: '¥800' }
146
+]
147
+
148
+export function showToast(title) {
149
+	uni.showToast({ title, icon: 'none', duration: 2000 })
150
+}

Fichier diff supprimé car celui-ci est trop grand
+ 1435 - 0
huimv-employment/app/common/fe.scss


+ 10 - 0
huimv-employment/app/common/mp-nav.js

@@ -0,0 +1,10 @@
1
+/** 隐藏微信小程序导航栏左侧「返回首页」按钮 */
2
+export function hideWxHomeButton() {
3
+	// #ifdef MP-WEIXIN
4
+	if (typeof uni.hideHomeButton === 'function') {
5
+		uni.hideHomeButton()
6
+	} else if (typeof wx !== 'undefined' && typeof wx.hideHomeButton === 'function') {
7
+		wx.hideHomeButton()
8
+	}
9
+	// #endif
10
+}

+ 81 - 0
huimv-employment/app/common/store.js

@@ -0,0 +1,81 @@
1
+const defaultWorker = () => ({
2
+	name: '',
3
+	phone: '',
4
+	registered: false
5
+})
6
+
7
+const store = {
8
+	state: {
9
+		loggedIn: false,
10
+		role: 'enterprise',
11
+		user: {
12
+			name: '张经理',
13
+			avatarText: '张',
14
+			mobile: '138****8888'
15
+		},
16
+		enterprise: {
17
+			name: '某某科技有限公司',
18
+			code: '91310000...'
19
+		},
20
+		worker: defaultWorker()
21
+	},
22
+
23
+	login(role = 'enterprise') {
24
+		this.state.loggedIn = true
25
+		this.state.role = role
26
+		if (role === 'worker' && !this.state.worker) {
27
+			this.state.worker = defaultWorker()
28
+		}
29
+		this.persist()
30
+	},
31
+
32
+	logout() {
33
+		this.state.loggedIn = false
34
+		this.state.worker = defaultWorker()
35
+		this.persist()
36
+	},
37
+
38
+	registerWorker(profile = {}) {
39
+		this.state.worker = {
40
+			...this.state.worker,
41
+			...profile,
42
+			registered: true
43
+		}
44
+		this.persist()
45
+	},
46
+
47
+	isWorkerRegistered() {
48
+		return !!(this.state.worker && this.state.worker.registered)
49
+	},
50
+
51
+	getWorkerEntryUrl() {
52
+		return this.isWorkerRegistered() ? '/pages/worker/wallet' : '/pages/worker/register'
53
+	},
54
+
55
+	persist() {
56
+		try {
57
+			uni.setStorageSync('fe_demo', this.state)
58
+		} catch (e) {}
59
+	},
60
+
61
+	restore() {
62
+		try {
63
+			const data = uni.getStorageSync('fe_demo')
64
+			if (data) {
65
+				this.state = { ...this.state, ...data }
66
+				if (!this.state.worker) {
67
+					this.state.worker = defaultWorker()
68
+				} else if (this.state.worker.registered === undefined) {
69
+					this.state.worker.registered = false
70
+				}
71
+			}
72
+		} catch (e) {}
73
+	},
74
+
75
+	getState() {
76
+		return this.state
77
+	}
78
+}
79
+
80
+store.restore()
81
+export default store

+ 230 - 0
huimv-employment/app/components/EnterpriseHubPanel.vue

@@ -0,0 +1,230 @@
1
+<template>
2
+	<view class="enterprise-hub">
3
+		<view class="fe-profile-card">
4
+			<view class="fe-profile-avatar-lg">{{ user.avatarText }}</view>
5
+			<view class="enterprise-hub__name">{{ enterprise.name }}</view>
6
+			<view class="enterprise-hub__code">{{ enterprise.code }}</view>
7
+			<view class="enterprise-hub__badge">✓ 已认证</view>
8
+		</view>
9
+
10
+		<view class="fe-section-title">消息与待办中心</view>
11
+		<view class="fe-profile-menu-item menu-card" @tap="toast('待确认 2 条')">
12
+			<view class="fe-profile-menu-icon" style="background:#FEF3C7;">🔔</view>
13
+			<view class="menu-card__main">
14
+				<view class="fe-profile-menu-title">消息与待办中心</view>
15
+				<view class="fe-profile-menu-desc">待确认 2 条</view>
16
+			</view>
17
+			<text class="menu-card__arrow">›</text>
18
+		</view>
19
+
20
+		<view class="fe-section-title">智能偏好设置</view>
21
+		<view class="menu-card">
22
+			<view class="fe-profile-menu-item" @tap="openConfig('workType')">
23
+				<view class="fe-profile-menu-icon" style="background:#DBEAFE;">🔧</view>
24
+				<view class="menu-card__main"><view class="fe-profile-menu-title">常用工种与薪资基准</view></view>
25
+				<text class="menu-card__arrow">›</text>
26
+			</view>
27
+			<view class="fe-profile-menu-item" @tap="openConfig('insurance')">
28
+				<view class="fe-profile-menu-icon" style="background:#D1FAE5;">🛡</view>
29
+				<view class="menu-card__main">
30
+					<view class="fe-profile-menu-title">默认保险方案偏好</view>
31
+					<view class="fe-profile-menu-desc">已配置</view>
32
+				</view>
33
+				<text class="menu-card__arrow">›</text>
34
+			</view>
35
+			<view class="fe-profile-menu-item" @tap="openConfig('payment')">
36
+				<view class="fe-profile-menu-icon" style="background:#FEF3C7;">📅</view>
37
+				<view class="menu-card__main"><view class="fe-profile-menu-title">常规发薪日与结算周期</view></view>
38
+				<text class="menu-card__arrow">›</text>
39
+			</view>
40
+		</view>
41
+
42
+		<view class="fe-section-title">财务与账户</view>
43
+		<view class="menu-card">
44
+			<view class="fe-profile-menu-item" @tap="toast('对公账户')">
45
+				<view class="fe-profile-menu-icon" style="background:#FEE2E2;">💳</view>
46
+				<view class="menu-card__main"><view class="fe-profile-menu-title">对公账户与打款通道</view></view>
47
+				<text class="menu-card__arrow">›</text>
48
+			</view>
49
+			<view class="fe-profile-menu-item" @tap="toast('开票抬头')">
50
+				<view class="fe-profile-menu-icon" style="background:#EDE9FE;">📋</view>
51
+				<view class="menu-card__main"><view class="fe-profile-menu-title">企业开票抬头与税控</view></view>
52
+				<text class="menu-card__arrow">›</text>
53
+			</view>
54
+			<view class="fe-profile-menu-item" @tap="toast('平台余额')">
55
+				<view class="fe-profile-menu-icon" style="background:#F3F4F6;">💼</view>
56
+				<view class="menu-card__main"><view class="fe-profile-menu-title">平台余额与充值管理</view></view>
57
+				<text class="menu-card__arrow">›</text>
58
+			</view>
59
+		</view>
60
+
61
+		<view class="fe-section-title">合规与安全审计</view>
62
+		<view class="menu-card">
63
+			<view class="fe-profile-menu-item" @tap="toast('合同归档')">
64
+				<view class="fe-profile-menu-icon" style="background:#EDE9FE;">📄</view>
65
+				<view class="menu-card__main"><view class="fe-profile-menu-title">历史合同与电子签章归档</view></view>
66
+				<text class="menu-card__arrow">›</text>
67
+			</view>
68
+			<view class="fe-profile-menu-item" @tap="toast('操作日志')">
69
+				<view class="fe-profile-menu-icon" style="background:#DBEAFE;">🔍</view>
70
+				<view class="menu-card__main"><view class="fe-profile-menu-title">操作日志与合规证据链导出</view></view>
71
+				<text class="menu-card__arrow">›</text>
72
+			</view>
73
+			<view class="fe-profile-menu-item" @tap="toast('通知设置')">
74
+				<view class="fe-profile-menu-icon" style="background:#D1FAE5;">🔔</view>
75
+				<view class="menu-card__main"><view class="fe-profile-menu-title">关键节点消息通知设置</view></view>
76
+				<text class="menu-card__arrow">›</text>
77
+			</view>
78
+		</view>
79
+
80
+		<view class="fe-section-title">系统设置与帮助</view>
81
+		<view class="enterprise-hub__help-btns">
82
+			<view class="fe-btn fe-btn--secondary fe-btn--sm" @tap="toast('关于我们')">关于我们</view>
83
+			<view class="fe-btn fe-btn--secondary fe-btn--sm" @tap="toast('常见问题')">常见问题</view>
84
+			<view class="fe-btn fe-btn--secondary fe-btn--sm" @tap="toast('联系客服')">联系客服</view>
85
+		</view>
86
+
87
+		<view v-if="showLogout" class="fe-btn fe-btn--secondary fe-btn--lg fe-btn--full enterprise-hub__logout" @tap="$emit('logout')">退出账号</view>
88
+
89
+		<!-- Config Sheet -->
90
+		<view class="fe-overlay" :class="{ 'fe-overlay--active': configOpen }" @tap="configOpen = false" />
91
+		<view class="fe-bottom-sheet" :class="{ 'fe-bottom-sheet--active': configOpen }">
92
+			<view class="fe-sheet-handle" />
93
+			<view class="fe-sheet-header">
94
+				<view class="fe-sheet-title">🛡 {{ configTitle }}</view>
95
+			</view>
96
+			<view class="fe-sheet-source">来自:企业管理中枢</view>
97
+			<scroll-view scroll-y class="fe-sheet-body" :show-scrollbar="false">
98
+				<view class="fe-sheet-body__inner">
99
+					<view v-for="(m, i) in configMsgs" :key="i" class="fe-chat-msg" :class="'fe-chat-msg--' + m.role">
100
+						<view v-if="m.role === 'ai'" class="fe-chat-avatar fe-chat-avatar--ai" style="width:56rpx;height:56rpx;font-size:22rpx;">AI</view>
101
+						<view v-if="m.role === 'user'" class="fe-chat-avatar fe-chat-avatar--user" style="width:56rpx;height:56rpx;font-size:22rpx;">{{ user.avatarText }}</view>
102
+						<view class="fe-chat-bubble" style="font-size:26rpx;"><text>{{ m.content }}</text></view>
103
+					</view>
104
+				</view>
105
+			</scroll-view>
106
+			<view class="fe-input-bar fe-input-bar--inline">
107
+				<input class="fe-input-bar__field" v-model="configInput" placeholder="描述您的配置需求..." @confirm="sendConfig" />
108
+				<view class="fe-input-bar__btn fe-input-bar__btn--send active" @tap="sendConfig">➤</view>
109
+			</view>
110
+		</view>
111
+	</view>
112
+</template>
113
+
114
+<script>
115
+import store from '@/common/store.js'
116
+import { showToast } from '@/common/chat-data.js'
117
+
118
+const CONFIG_DATA = {
119
+	workType: { title: '常用工种与薪资基准', welcome: '请告诉我您常用的工种和薪资基准,例如:"活动执行类日薪500-600元,搬运工400-500元"' },
120
+	insurance: { title: '默认保险方案偏好', welcome: '请告诉我您的保险偏好,例如:"高空作业买高档雇主险,普通搬运买基础意外险"' },
121
+	payment: { title: '常规发薪日与结算周期', welcome: '请告诉我您的发薪日和结算周期,例如:"每月15号发薪,用工结束后3个工作日内结算"' }
122
+}
123
+
124
+export default {
125
+	name: 'EnterpriseHubPanel',
126
+	props: {
127
+		showLogout: { type: Boolean, default: false }
128
+	},
129
+	data() {
130
+		return {
131
+			user: {},
132
+			enterprise: {},
133
+			configOpen: false,
134
+			configTitle: '',
135
+			configMsgs: [],
136
+			configInput: ''
137
+		}
138
+	},
139
+	created() {
140
+		this.syncStore()
141
+	},
142
+	methods: {
143
+		toast: showToast,
144
+		syncStore() {
145
+			const s = store.getState()
146
+			this.user = s.user
147
+			this.enterprise = s.enterprise
148
+		},
149
+		openConfig(type) {
150
+			const cfg = CONFIG_DATA[type]
151
+			this.configTitle = cfg.title
152
+			this.configMsgs = [{ role: 'ai', content: cfg.welcome }]
153
+			this.configInput = ''
154
+			this.configOpen = true
155
+		},
156
+		sendConfig() {
157
+			const text = this.configInput.trim()
158
+			if (!text) return
159
+			this.configMsgs.push({ role: 'user', content: text })
160
+			this.configInput = ''
161
+			setTimeout(() => {
162
+				this.configMsgs.push({
163
+					role: 'ai',
164
+					content: `已解析您的配置需求。以下是识别到的规则:\n\n"${text}"\n\n请确认此配置,确认后将应用于后续所有用工草稿。`
165
+				})
166
+			}, 800)
167
+		}
168
+	}
169
+}
170
+</script>
171
+
172
+<style lang="scss" scoped>
173
+.enterprise-hub__name {
174
+	font-size: 32rpx;
175
+	font-weight: 700;
176
+}
177
+
178
+.enterprise-hub__code {
179
+	font-size: 26rpx;
180
+	color: #64748B;
181
+	margin-top: 4rpx;
182
+}
183
+
184
+.enterprise-hub__badge {
185
+	display: inline-flex;
186
+	align-items: center;
187
+	gap: 8rpx;
188
+	padding: 6rpx 20rpx;
189
+	border-radius: 9999rpx;
190
+	background: #D1FAE5;
191
+	color: #065F46;
192
+	font-size: 24rpx;
193
+	font-weight: 600;
194
+	margin-top: 16rpx;
195
+}
196
+
197
+.menu-card {
198
+	background: #fff;
199
+	border-radius: $fe-radius-md;
200
+	margin-bottom: 8rpx;
201
+	border: 1rpx solid $fe-border-light;
202
+	overflow: hidden;
203
+}
204
+
205
+.menu-card__main {
206
+	flex: 1;
207
+	min-width: 0;
208
+}
209
+
210
+.menu-card__arrow {
211
+	color: #94A3B8;
212
+	flex-shrink: 0;
213
+}
214
+
215
+.enterprise-hub__help-btns {
216
+	display: flex;
217
+	flex-wrap: wrap;
218
+	gap: 16rpx;
219
+}
220
+
221
+.enterprise-hub__logout {
222
+	margin-top: 48rpx;
223
+	color: $fe-danger !important;
224
+}
225
+
226
+.fe-input-bar--inline {
227
+	position: relative;
228
+	box-shadow: none;
229
+}
230
+</style>

+ 37 - 0
huimv-employment/app/components/FeBottomNav.vue

@@ -0,0 +1,37 @@
1
+<template>
2
+	<view class="fe-bottom-nav">
3
+		<view
4
+			v-for="item in items"
5
+			:key="item.key"
6
+			class="fe-bottom-nav__item"
7
+			:class="{ 'fe-bottom-nav__item--active': current === item.key }"
8
+			@tap="go(item)"
9
+		>
10
+			<text class="fe-bottom-nav__icon">{{ item.icon }}</text>
11
+			<text>{{ item.label }}</text>
12
+		</view>
13
+	</view>
14
+</template>
15
+
16
+<script>
17
+export default {
18
+	name: 'FeBottomNav',
19
+	props: {
20
+		current: { type: String, default: 'home' }
21
+	},
22
+	data() {
23
+		return {
24
+			items: [
25
+				{ key: 'home', label: '首页', icon: '🏠', url: '/pages/worker/wallet' },
26
+				{ key: 'mine', label: '我的', icon: '👤', url: '/pages/worker/profile' }
27
+			]
28
+		}
29
+	},
30
+	methods: {
31
+		go(item) {
32
+			if (item.key === this.current) return
33
+			uni.redirectTo({ url: item.url })
34
+		}
35
+	}
36
+}
37
+</script>

+ 89 - 0
huimv-employment/app/components/FeMpNavBar.vue

@@ -0,0 +1,89 @@
1
+<template>
2
+	<view class="fe-mp-nav-wrap">
3
+		<view class="fe-mp-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
4
+			<view class="fe-mp-nav__inner" :style="{ height: navBarHeight + 'px' }">
5
+				<view class="fe-mp-nav__back fe-nav-btn" @tap="$emit('back')">‹</view>
6
+				<text class="fe-mp-nav__title">{{ title }}</text>
7
+				<view class="fe-mp-nav__side" />
8
+			</view>
9
+		</view>
10
+		<view class="fe-mp-nav__placeholder" :style="{ height: totalHeight + 'px' }" />
11
+	</view>
12
+</template>
13
+
14
+<script>
15
+export default {
16
+	name: 'FeMpNavBar',
17
+	props: {
18
+		title: { type: String, default: '' }
19
+	},
20
+	data() {
21
+		return {
22
+			statusBarHeight: 20,
23
+			navBarHeight: 44
24
+		}
25
+	},
26
+	computed: {
27
+		totalHeight() {
28
+			return this.statusBarHeight + this.navBarHeight
29
+		}
30
+	},
31
+	created() {
32
+		const sys = uni.getSystemInfoSync()
33
+		this.statusBarHeight = sys.statusBarHeight || 20
34
+		// #ifdef MP-WEIXIN
35
+		const menu = uni.getMenuButtonBoundingClientRect()
36
+		if (menu && menu.top) {
37
+			this.navBarHeight = (menu.top - this.statusBarHeight) * 2 + menu.height
38
+		}
39
+		// #endif
40
+	}
41
+}
42
+</script>
43
+
44
+<style lang="scss" scoped>
45
+.fe-mp-nav {
46
+	position: fixed;
47
+	top: 0;
48
+	left: 0;
49
+	right: 0;
50
+	z-index: 99;
51
+	background: #fff;
52
+	border-bottom: 1rpx solid $fe-border-light;
53
+}
54
+
55
+.fe-mp-nav__placeholder {
56
+	flex-shrink: 0;
57
+}
58
+
59
+.fe-mp-nav__inner {
60
+	display: flex;
61
+	align-items: center;
62
+	padding: 0 16rpx 0 8rpx;
63
+	box-sizing: border-box;
64
+}
65
+
66
+.fe-mp-nav__back {
67
+	width: 72rpx;
68
+	height: 72rpx;
69
+	font-size: 48rpx;
70
+	line-height: 1;
71
+	color: $fe-foreground;
72
+}
73
+
74
+.fe-mp-nav__title {
75
+	flex: 1;
76
+	text-align: center;
77
+	font-size: 34rpx;
78
+	font-weight: 700;
79
+	color: $fe-foreground;
80
+	overflow: hidden;
81
+	text-overflow: ellipsis;
82
+	white-space: nowrap;
83
+}
84
+
85
+.fe-mp-nav__side {
86
+	width: 72rpx;
87
+	flex-shrink: 0;
88
+}
89
+</style>

+ 78 - 0
huimv-employment/app/components/FePageBar.vue

@@ -0,0 +1,78 @@
1
+<template>
2
+	<view class="fe-page-bar" :class="{ 'fe-page-bar--fixed': fixed }">
3
+		<view class="fe-page-bar__inner">
4
+			<view class="fe-page-bar__left">
5
+				<slot name="left" />
6
+				<slot name="title"><text class="fe-page-bar__title">{{ title }}</text></slot>
7
+			</view>
8
+			<view class="fe-page-bar__actions">
9
+				<slot name="actions" />
10
+			</view>
11
+		</view>
12
+	</view>
13
+</template>
14
+
15
+<script>
16
+export default {
17
+	name: 'FePageBar',
18
+	props: {
19
+		title: { type: String, default: '' },
20
+		fixed: { type: Boolean, default: false }
21
+	}
22
+}
23
+</script>
24
+
25
+<style lang="scss" scoped>
26
+.fe-page-bar {
27
+	background: #ffffff;
28
+	border-bottom: 1rpx solid $fe-border-light;
29
+	box-sizing: border-box;
30
+	flex-shrink: 0;
31
+}
32
+
33
+/* 原生导航栏下方:页面内容区 top:0 即紧贴系统顶栏 */
34
+.fe-page-bar--fixed {
35
+	position: fixed;
36
+	top: 0;
37
+	left: 0;
38
+	right: 0;
39
+	z-index: 99;
40
+}
41
+
42
+.fe-page-bar__inner {
43
+	height: $fe-h-page-bar;
44
+	display: flex;
45
+	align-items: center;
46
+	justify-content: space-between;
47
+	padding: 0 28rpx;
48
+	box-sizing: border-box;
49
+}
50
+
51
+.fe-page-bar__left {
52
+	display: flex;
53
+	align-items: center;
54
+	gap: 16rpx;
55
+	flex: 1;
56
+	min-width: 0;
57
+	overflow: hidden;
58
+}
59
+
60
+.fe-page-bar__title {
61
+	font-weight: 700;
62
+	font-size: 34rpx;
63
+	color: $fe-foreground;
64
+	overflow: hidden;
65
+	text-overflow: ellipsis;
66
+	white-space: nowrap;
67
+	flex: 1;
68
+	min-width: 0;
69
+}
70
+
71
+.fe-page-bar__actions {
72
+	display: flex;
73
+	align-items: center;
74
+	flex-wrap: nowrap;
75
+	gap: 4rpx;
76
+	flex-shrink: 0;
77
+}
78
+</style>

+ 215 - 0
huimv-employment/app/components/chat/FeBatchQrModal.vue

@@ -0,0 +1,215 @@
1
+<template>
2
+	<view>
3
+		<view class="fe-modal-overlay" :class="{ 'fe-modal-overlay--active': active }" @tap="$emit('close')" />
4
+		<view class="fe-modal fe-batch-qr-modal" :class="{ 'fe-modal--active': active }">
5
+			<view class="fe-batch-qr-modal__body">
6
+				<view class="fe-batch-qr-modal__title">{{ batch.modalTitle }}</view>
7
+				<view class="fe-batch-qr-modal__subtitle">{{ batchSubtitle }}</view>
8
+
9
+				<view class="fe-batch-qr-modal__code-wrap">
10
+					<view class="fe-batch-qr-modal__matrix">
11
+						<view
12
+							v-for="(cell, i) in qrCells"
13
+							:key="i"
14
+							class="fe-batch-qr-modal__cell"
15
+							:class="{ 'fe-batch-qr-modal__cell--on': cell }"
16
+						/>
17
+					</view>
18
+				</view>
19
+
20
+				<view class="fe-batch-qr-modal__hint">{{ batch.hint }}</view>
21
+
22
+				<view class="fe-batch-qr-modal__progress">
23
+					<view class="fe-progress-label">
24
+						<text class="fe-progress-label__text">登记进度</text>
25
+						<text class="fe-progress-label__value">{{ progressText }}</text>
26
+					</view>
27
+					<view class="fe-progress-track">
28
+						<view class="fe-progress-fill" :style="{ width: progressPercent + '%' }" />
29
+					</view>
30
+				</view>
31
+
32
+				<view class="fe-batch-qr-modal__actions">
33
+					<view class="fe-batch-qr-modal__action" @tap="onSave">
34
+						<text class="fe-batch-qr-modal__action-icon">🖼</text>
35
+						<text>保存相册</text>
36
+					</view>
37
+					<view class="fe-batch-qr-modal__action" @tap="onShare">
38
+						<text class="fe-batch-qr-modal__action-icon">↗</text>
39
+						<text>分享好友</text>
40
+					</view>
41
+					<view class="fe-batch-qr-modal__action" @tap="onCopyLink">
42
+						<text class="fe-batch-qr-modal__action-icon">🔗</text>
43
+						<text>复制链接</text>
44
+					</view>
45
+					<view class="fe-batch-qr-modal__action" @tap="onViewProgress">
46
+						<text class="fe-batch-qr-modal__action-icon">📈</text>
47
+						<text>查看进度</text>
48
+					</view>
49
+				</view>
50
+			</view>
51
+		</view>
52
+	</view>
53
+</template>
54
+
55
+<script>
56
+import { BATCH_QR, QR_DEMO_PATTERN, showToast } from '@/common/chat-data.js'
57
+
58
+export default {
59
+	name: 'FeBatchQrModal',
60
+	props: {
61
+		active: { type: Boolean, default: false },
62
+		batch: {
63
+			type: Object,
64
+			default() {
65
+				return { ...BATCH_QR }
66
+			}
67
+		}
68
+	},
69
+	computed: {
70
+		qrCells() {
71
+			return QR_DEMO_PATTERN.split('').map((c) => c === '1')
72
+		},
73
+		batchSubtitle() {
74
+			return this.batch.subtitle || `${this.batch.title} ${this.batch.batchId}`
75
+		},
76
+		progressText() {
77
+			return `${this.batch.registered}/${this.batch.total}`
78
+		},
79
+		progressPercent() {
80
+			if (!this.batch.total) return 0
81
+			return Math.round((this.batch.registered / this.batch.total) * 100)
82
+		}
83
+	},
84
+	methods: {
85
+		onSave() {
86
+			showToast('已保存到相册')
87
+			this.$emit('save')
88
+		},
89
+		onShare() {
90
+			showToast('已分享')
91
+			this.$emit('share')
92
+		},
93
+		onCopyLink() {
94
+			const link = this.batch.shareLink || ''
95
+			if (!link) {
96
+				showToast('暂无登记链接')
97
+				return
98
+			}
99
+			uni.setClipboardData({
100
+				data: link,
101
+				success: () => showToast('已复制链接')
102
+			})
103
+			this.$emit('copy')
104
+		},
105
+		onViewProgress() {
106
+			this.$emit('progress')
107
+		}
108
+	}
109
+}
110
+</script>
111
+
112
+<style lang="scss" scoped>
113
+.fe-batch-qr-modal {
114
+	max-width: 760rpx;
115
+}
116
+
117
+.fe-batch-qr-modal__body {
118
+	padding: 40rpx 40rpx 36rpx;
119
+	text-align: center;
120
+}
121
+
122
+.fe-batch-qr-modal__title {
123
+	font-size: 34rpx;
124
+	font-weight: 700;
125
+	color: $fe-foreground;
126
+	margin-bottom: 8rpx;
127
+}
128
+
129
+.fe-batch-qr-modal__subtitle {
130
+	font-size: 26rpx;
131
+	color: $fe-muted;
132
+	margin-bottom: 24rpx;
133
+}
134
+
135
+.fe-batch-qr-modal__code-wrap {
136
+	width: 360rpx;
137
+	height: 360rpx;
138
+	margin: 0 auto 20rpx;
139
+	padding: 32rpx;
140
+	background: #fff;
141
+	border: 2rpx solid $fe-border;
142
+	border-radius: $fe-radius-md;
143
+	box-sizing: border-box;
144
+	display: flex;
145
+	align-items: center;
146
+	justify-content: center;
147
+}
148
+
149
+.fe-batch-qr-modal__matrix {
150
+	width: 280rpx;
151
+	height: 280rpx;
152
+	display: grid;
153
+	grid-template-columns: repeat(15, 1fr);
154
+	gap: 3rpx;
155
+}
156
+
157
+.fe-batch-qr-modal__cell {
158
+	background: #fff;
159
+	border-radius: 2rpx;
160
+}
161
+
162
+.fe-batch-qr-modal__cell--on {
163
+	background: $fe-foreground;
164
+}
165
+
166
+.fe-batch-qr-modal__hint {
167
+	font-size: 26rpx;
168
+	color: $fe-muted;
169
+	margin-bottom: 24rpx;
170
+}
171
+
172
+.fe-batch-qr-modal__progress {
173
+	margin-bottom: 32rpx;
174
+	text-align: left;
175
+}
176
+
177
+.fe-progress-label__text {
178
+	color: $fe-muted;
179
+}
180
+
181
+.fe-progress-label__value {
182
+	font-weight: 700;
183
+	color: $fe-primary;
184
+}
185
+
186
+.fe-batch-qr-modal__actions {
187
+	display: grid;
188
+	grid-template-columns: 1fr 1fr;
189
+	gap: 16rpx;
190
+}
191
+
192
+.fe-batch-qr-modal__action {
193
+	display: flex;
194
+	align-items: center;
195
+	justify-content: center;
196
+	gap: 10rpx;
197
+	padding: 20rpx 16rpx;
198
+	border-radius: $fe-radius-sm;
199
+	border: 1rpx solid $fe-border;
200
+	background: $fe-surface;
201
+	font-size: 26rpx;
202
+	font-weight: 600;
203
+	color: $fe-foreground;
204
+	box-sizing: border-box;
205
+}
206
+
207
+.fe-batch-qr-modal__action:active {
208
+	background: $fe-primary-bg;
209
+}
210
+
211
+.fe-batch-qr-modal__action-icon {
212
+	font-size: 30rpx;
213
+	line-height: 1;
214
+}
215
+</style>

+ 242 - 0
huimv-employment/app/components/chat/FeCostSheet.vue

@@ -0,0 +1,242 @@
1
+<template>
2
+	<view class="fe-bottom-sheet fe-bottom-sheet--cost" :class="{ 'fe-bottom-sheet--active': active }">
3
+		<view class="fe-sheet-handle" />
4
+		<view class="fe-sheet-header">
5
+			<view class="fe-sheet-title">💰 成本对比</view>
6
+			<view class="fe-swipe-indicator">
7
+				<text class="fe-swipe-indicator__arrow">‹</text>
8
+				<text>滑动</text>
9
+				<text class="fe-swipe-indicator__arrow">›</text>
10
+			</view>
11
+		</view>
12
+		<view class="fe-sheet-source">浦东仓库搬运 · 10人×2天</view>
13
+		<view class="fe-sheet-body fe-sheet-body--cost">
14
+			<swiper
15
+				class="fe-cost-swiper"
16
+				:style="{ height: swiperHeight + 'px' }"
17
+				:current="currentIndex"
18
+				@change="onSwiperChange"
19
+			>
20
+				<swiper-item
21
+					v-for="(plan, planIndex) in plans"
22
+					:key="plan.id"
23
+					class="fe-cost-swiper__item"
24
+					:style="{ height: swiperHeight + 'px' }"
25
+				>
26
+					<scroll-view
27
+						scroll-y
28
+						class="fe-cost-swiper__scroll"
29
+						:style="{ height: swiperHeight + 'px' }"
30
+						:show-scrollbar="false"
31
+						:enhanced="false"
32
+					>
33
+						<view class="fe-cost-swiper__inner">
34
+							<view
35
+								class="fe-cost-card"
36
+								:class="{ 'fe-cost-card--recommended': currentIndex === planIndex }"
37
+							>
38
+								<view v-if="plan.recommended && currentIndex === planIndex" class="fe-cost-card__tag">推荐</view>
39
+								<view class="fe-cost-card__head">
40
+									<view class="fe-cost-card__name">
41
+										<text>{{ plan.icon }}</text>
42
+										<text>{{ plan.title }}</text>
43
+									</view>
44
+									<view class="fe-cost-card__toggle" @tap.stop="toggleExpand(plan.id)">
45
+										<view
46
+											class="fe-cost-card__chevron"
47
+											:class="{ 'fe-cost-card__chevron--up': isExpanded(plan.id) }"
48
+										/>
49
+									</view>
50
+								</view>
51
+								<view class="fe-cost-total">{{ plan.total }}</view>
52
+								<view class="fe-info-row">
53
+									<text class="fe-info-row__label">人均成本</text>
54
+									<text class="fe-info-row__value">{{ plan.avgCost }}</text>
55
+								</view>
56
+								<block v-if="isExpanded(plan.id)">
57
+									<view v-for="(row, i) in plan.rows" :key="'r-' + i" class="fe-info-row">
58
+										<text class="fe-info-row__label">{{ row.label }}</text>
59
+										<text class="fe-info-row__value">{{ row.value }}</text>
60
+									</view>
61
+									<view v-if="plan.breakdown" class="fe-cost-card__breakdown">
62
+										<view v-for="(row, i) in plan.breakdown" :key="'b-' + i" class="fe-info-row">
63
+											<text class="fe-info-row__label">{{ row.label }}</text>
64
+											<text class="fe-info-row__value">{{ row.value }}</text>
65
+										</view>
66
+									</view>
67
+									<view v-if="plan.compliance" class="fe-compliance">
68
+										<text v-for="(badge, i) in plan.compliance" :key="'c-' + i" class="fe-compliance-badge">✓ {{ badge }}</text>
69
+									</view>
70
+								</block>
71
+							</view>
72
+
73
+							<view class="fe-cost-card fe-cost-card--muted">
74
+								<view class="fe-cost-card__name">
75
+									<text>{{ traditional.icon }}</text>
76
+									<text>{{ traditional.title }}</text>
77
+								</view>
78
+								<view class="fe-cost-total fe-cost-total--muted">{{ traditional.total }}</view>
79
+								<view class="fe-info-row">
80
+									<text class="fe-info-row__label">人均成本</text>
81
+									<text class="fe-info-row__value">{{ traditional.avgCost }}</text>
82
+								</view>
83
+							</view>
84
+						</view>
85
+					</scroll-view>
86
+				</swiper-item>
87
+			</swiper>
88
+			<view class="fe-dot-indicator">
89
+				<view
90
+					v-for="(plan, i) in plans"
91
+					:key="plan.id"
92
+					class="fe-dot-indicator__dot"
93
+					:class="{ 'fe-dot-indicator__dot--active': currentIndex === i }"
94
+				/>
95
+			</view>
96
+		</view>
97
+		<view class="fe-sheet-footer">
98
+			<view class="fe-btn fe-btn--secondary fe-btn--lg" @tap="toast('已导出对比报告')">对比传统</view>
99
+			<view class="fe-btn fe-btn--primary fe-btn--lg" @tap="$emit('confirm')">确认此方案</view>
100
+		</view>
101
+	</view>
102
+</template>
103
+
104
+<script>
105
+import { COST_PLANS, TRADITIONAL_COST_PLAN, showToast } from '@/common/chat-data.js'
106
+
107
+export default {
108
+	name: 'FeCostSheet',
109
+	props: {
110
+		active: { type: Boolean, default: false }
111
+	},
112
+	data() {
113
+		return {
114
+			plans: COST_PLANS,
115
+			traditional: TRADITIONAL_COST_PLAN,
116
+			currentIndex: 0,
117
+			expandedPlans: {},
118
+			swiperHeight: uni.upx2px(800)
119
+		}
120
+	},
121
+	watch: {
122
+		active(val) {
123
+			if (val) {
124
+				this.currentIndex = 0
125
+				this.expandedPlans = {}
126
+			}
127
+		}
128
+	},
129
+	methods: {
130
+		toast: showToast,
131
+		onSwiperChange(e) {
132
+			this.currentIndex = e.detail.current
133
+		},
134
+		isExpanded(id) {
135
+			if (this.expandedPlans[id] === undefined) return true
136
+			return !!this.expandedPlans[id]
137
+		},
138
+		toggleExpand(id) {
139
+			this.$set(this.expandedPlans, id, !this.expandedPlans[id])
140
+		}
141
+	}
142
+}
143
+</script>
144
+
145
+<style lang="scss" scoped>
146
+.fe-bottom-sheet--cost {
147
+	max-height: 92vh;
148
+}
149
+
150
+.fe-sheet-body--cost {
151
+	flex: 1;
152
+	width: 100%;
153
+	min-height: 0;
154
+	max-height: none;
155
+	box-sizing: border-box;
156
+	display: flex;
157
+	flex-direction: column;
158
+	overflow: hidden;
159
+}
160
+
161
+.fe-cost-swiper {
162
+	width: 100%;
163
+	flex-shrink: 0;
164
+}
165
+
166
+.fe-cost-swiper__item {
167
+	height: 100%;
168
+}
169
+
170
+.fe-cost-swiper__scroll {
171
+	width: 100%;
172
+	box-sizing: border-box;
173
+}
174
+
175
+.fe-cost-swiper__inner {
176
+	padding: 16rpx 40rpx 24rpx;
177
+	box-sizing: border-box;
178
+}
179
+
180
+.fe-cost-card__head {
181
+	display: flex;
182
+	align-items: center;
183
+	justify-content: space-between;
184
+	gap: 16rpx;
185
+	margin-bottom: 24rpx;
186
+}
187
+
188
+.fe-cost-card__name {
189
+	display: flex;
190
+	align-items: center;
191
+	gap: 12rpx;
192
+	font-size: 30rpx;
193
+	font-weight: 700;
194
+	flex: 1;
195
+	min-width: 0;
196
+}
197
+
198
+.fe-cost-card__toggle {
199
+	width: 56rpx;
200
+	height: 56rpx;
201
+	border-radius: 50%;
202
+	background: rgba(124, 58, 237, 0.1);
203
+	display: flex;
204
+	align-items: center;
205
+	justify-content: center;
206
+	flex-shrink: 0;
207
+}
208
+
209
+.fe-cost-card__chevron {
210
+	width: 18rpx;
211
+	height: 18rpx;
212
+	border-right: 4rpx solid $fe-primary;
213
+	border-bottom: 4rpx solid $fe-primary;
214
+	transform: rotate(45deg);
215
+	margin-top: -6rpx;
216
+	transition: transform 0.2s ease;
217
+}
218
+
219
+.fe-cost-card__chevron--up {
220
+	transform: rotate(-135deg);
221
+	margin-top: 6rpx;
222
+}
223
+
224
+.fe-cost-card__breakdown {
225
+	border-top: 1rpx solid #F1F5F9;
226
+	margin-top: 16rpx;
227
+	padding-top: 16rpx;
228
+}
229
+
230
+.fe-cost-card--muted {
231
+	opacity: 0.65;
232
+	margin-bottom: 0;
233
+
234
+	.fe-cost-card__name {
235
+		margin-bottom: 24rpx;
236
+	}
237
+}
238
+
239
+.fe-cost-total--muted {
240
+	color: #64748B;
241
+}
242
+</style>

+ 232 - 0
huimv-employment/app/components/chat/FeDraftSheet.vue

@@ -0,0 +1,232 @@
1
+<template>
2
+	<view class="fe-bottom-sheet" :class="{ 'fe-bottom-sheet--active': active }">
3
+		<view class="fe-sheet-handle" />
4
+		<view class="fe-sheet-header">
5
+			<view class="fe-sheet-title">📄 用工草稿 <text class="fe-sheet-badge">待确认</text></view>
6
+		</view>
7
+		<view class="fe-sheet-source">来源:AI对话 · 浦东仓库需求</view>
8
+		<scroll-view scroll-y class="fe-sheet-body" :show-scrollbar="false">
9
+			<view class="fe-sheet-body__inner">
10
+				<view class="fe-form-section" :class="{ 'fe-form-section--editing': basicEditing }">
11
+					<view class="fe-form-section__title">
12
+						<text>基础信息</text>
13
+						<text class="fe-form-section__edit" @tap="toggleBasicEdit">{{ basicEditing ? '保存' : '编辑' }}</text>
14
+					</view>
15
+					<view class="fe-info-row">
16
+						<text class="fe-info-row__label">用工人数</text>
17
+						<view v-if="basicEditing" class="fe-info-row__field">
18
+							<input class="fe-info-row__input" v-model="form.workers" type="number" />
19
+							<text class="fe-info-row__suffix">人</text>
20
+						</view>
21
+						<text v-else class="fe-info-row__value">{{ form.workers }} 人</text>
22
+					</view>
23
+					<view class="fe-info-row">
24
+						<text class="fe-info-row__label">工作天数</text>
25
+						<view v-if="basicEditing" class="fe-info-row__field">
26
+							<input class="fe-info-row__input" v-model="form.days" type="number" />
27
+							<text class="fe-info-row__suffix">天</text>
28
+						</view>
29
+						<text v-else class="fe-info-row__value">{{ form.days }} 天</text>
30
+					</view>
31
+					<view class="fe-info-row">
32
+						<text class="fe-info-row__label">岗位类型</text>
33
+						<input
34
+							v-if="basicEditing"
35
+							class="fe-info-row__input fe-info-row__input--full"
36
+							v-model="form.jobType"
37
+						/>
38
+						<text v-else class="fe-info-row__value">{{ form.jobType }}</text>
39
+					</view>
40
+					<view class="fe-info-row">
41
+						<text class="fe-info-row__label">工作地点</text>
42
+						<input
43
+							v-if="basicEditing"
44
+							class="fe-info-row__input fe-info-row__input--full"
45
+							v-model="form.location"
46
+						/>
47
+						<text v-else class="fe-info-row__value">{{ form.location }}</text>
48
+					</view>
49
+				</view>
50
+				<view class="fe-form-section" :class="{ 'fe-form-section--editing': requirementsEditing }">
51
+					<view class="fe-form-section__title">
52
+						<text>岗位要求</text>
53
+						<text class="fe-form-section__edit" @tap="toggleRequirementsEdit">{{ requirementsEditing ? '保存' : '编辑' }}</text>
54
+					</view>
55
+					<textarea
56
+						v-if="requirementsEditing"
57
+						class="fe-form-section__textarea"
58
+						v-model="form.requirements"
59
+						:auto-height="true"
60
+						placeholder="每行一条要求"
61
+					/>
62
+					<view v-else class="fe-form-section__desc">
63
+						<text v-for="(line, i) in requirementLines" :key="i">{{ line }}</text>
64
+					</view>
65
+				</view>
66
+				<view class="fe-nl-input-section">
67
+					<text class="fe-nl-input-section__label">💬 或直接告诉我要改什么:</text>
68
+					<textarea class="fe-nl-input" v-model="draftEdit" placeholder="例如:人数改成8个,加个叉车工" />
69
+					<view class="fe-biz-card__actions fe-nl-input-section__actions">
70
+						<view class="fe-biz-btn" @tap="toast('已发送修改')">发送</view>
71
+						<view v-if="draftEdit.trim()" class="fe-biz-btn fe-biz-btn--primary" @tap="confirmNlEdit">确认修改</view>
72
+					</view>
73
+				</view>
74
+				<view class="fe-form-section fe-form-section--highlight">
75
+					<view class="fe-form-section__title">预估成本</view>
76
+					<view class="fe-info-row"><text class="fe-info-row__label">总支出</text><text class="fe-info-row__value fe-info-row__value--highlight">¥12,800</text></view>
77
+					<view class="fe-info-row"><text class="fe-info-row__label">人均</text><text class="fe-info-row__value">¥640/天</text></view>
78
+				</view>
79
+			</view>
80
+		</scroll-view>
81
+		<view class="fe-sheet-footer">
82
+			<view class="fe-btn fe-btn--secondary fe-btn--lg" @tap="toast('已重新生成草稿')">重新生成</view>
83
+			<view class="fe-btn fe-btn--primary fe-btn--lg" @tap="$emit('confirm')">确认此方案</view>
84
+		</view>
85
+	</view>
86
+</template>
87
+
88
+<script>
89
+import { showToast } from '@/common/chat-data.js'
90
+
91
+const DEFAULT_FORM = {
92
+	workers: '10',
93
+	days: '2',
94
+	jobType: '搬运工',
95
+	location: '浦东新区',
96
+	requirements: '• 年龄18-45岁,身体健康\n• 有搬运经验优先'
97
+}
98
+
99
+export default {
100
+	name: 'FeDraftSheet',
101
+	props: {
102
+		active: { type: Boolean, default: false }
103
+	},
104
+	data() {
105
+		return {
106
+			draftEdit: '',
107
+			basicEditing: false,
108
+			requirementsEditing: false,
109
+			form: { ...DEFAULT_FORM }
110
+		}
111
+	},
112
+	computed: {
113
+		requirementLines() {
114
+			return this.form.requirements
115
+				.split('\n')
116
+				.map((line) => line.trim())
117
+				.filter(Boolean)
118
+		}
119
+	},
120
+	watch: {
121
+		active(val) {
122
+			if (!val) {
123
+				this.basicEditing = false
124
+				this.requirementsEditing = false
125
+			}
126
+		}
127
+	},
128
+	methods: {
129
+		toast: showToast,
130
+		toggleBasicEdit() {
131
+			if (this.basicEditing) {
132
+				if (!this.form.workers.trim() || !this.form.days.trim()) {
133
+					this.toast('请填写用工人数和工作天数')
134
+					return
135
+				}
136
+				if (!this.form.jobType.trim() || !this.form.location.trim()) {
137
+					this.toast('请填写岗位类型和工作地点')
138
+					return
139
+				}
140
+				this.basicEditing = false
141
+				this.toast('基础信息已保存')
142
+				return
143
+			}
144
+			this.basicEditing = true
145
+		},
146
+		toggleRequirementsEdit() {
147
+			if (this.requirementsEditing) {
148
+				if (!this.form.requirements.trim()) {
149
+					this.toast('请填写岗位要求')
150
+					return
151
+				}
152
+				this.requirementsEditing = false
153
+				this.toast('岗位要求已保存')
154
+				return
155
+			}
156
+			this.requirementsEditing = true
157
+		},
158
+		confirmNlEdit() {
159
+			this.toast('修改已确认')
160
+			this.draftEdit = ''
161
+		}
162
+	}
163
+}
164
+</script>
165
+
166
+<style lang="scss" scoped>
167
+.fe-form-section--editing {
168
+	border-color: $fe-primary-light;
169
+	background: $fe-primary-bg;
170
+}
171
+
172
+.fe-info-row__field {
173
+	display: flex;
174
+	align-items: center;
175
+	justify-content: flex-end;
176
+	gap: 8rpx;
177
+	flex: 1;
178
+	min-width: 0;
179
+}
180
+
181
+.fe-info-row__input {
182
+	width: 120rpx;
183
+	height: 64rpx;
184
+	padding: 0 16rpx;
185
+	border: 1rpx solid $fe-primary-light;
186
+	border-radius: $fe-radius-sm;
187
+	font-size: 28rpx;
188
+	font-weight: 600;
189
+	text-align: right;
190
+	background: #fff;
191
+	box-sizing: border-box;
192
+}
193
+
194
+.fe-info-row__input--full {
195
+	width: 100%;
196
+	flex: 1;
197
+	text-align: right;
198
+}
199
+
200
+.fe-info-row__suffix {
201
+	font-size: 28rpx;
202
+	font-weight: 600;
203
+	color: $fe-foreground;
204
+	flex-shrink: 0;
205
+}
206
+
207
+.fe-form-section__textarea {
208
+	width: 100%;
209
+	min-height: 160rpx;
210
+	padding: 20rpx;
211
+	border: 1rpx solid $fe-primary-light;
212
+	border-radius: $fe-radius-sm;
213
+	font-size: 26rpx;
214
+	line-height: 1.6;
215
+	color: $fe-muted;
216
+	background: #fff;
217
+	box-sizing: border-box;
218
+}
219
+
220
+.fe-nl-input-section__label {
221
+	font-size: 26rpx;
222
+	color: #7C3AED;
223
+	font-weight: 600;
224
+	margin-bottom: 16rpx;
225
+	display: block;
226
+}
227
+
228
+.fe-nl-input-section__actions {
229
+	margin-top: 16rpx;
230
+	justify-content: flex-end;
231
+}
232
+</style>

+ 137 - 0
huimv-employment/app/components/chat/FeProgressSheet.vue

@@ -0,0 +1,137 @@
1
+<template>
2
+	<view class="fe-bottom-sheet" :class="{ 'fe-bottom-sheet--active': active, 'fe-bottom-sheet--elevated': elevated }">
3
+		<view class="fe-sheet-handle" />
4
+		<view class="fe-sheet-header">
5
+			<view class="fe-sheet-title">📈 进度管理</view>
6
+		</view>
7
+		<view class="fe-sheet-source">浦东仓库搬运 BATCH-001</view>
8
+		<scroll-view scroll-y class="fe-sheet-body" :show-scrollbar="false">
9
+			<view class="fe-sheet-body__inner">
10
+				<view class="fe-stat-grid">
11
+					<view class="fe-stat-box"><view class="fe-stat-box__num fe-stat-box__num--warning">3</view><view class="fe-stat-box__label">待登记</view></view>
12
+					<view class="fe-stat-box"><view class="fe-stat-box__num fe-stat-box__num--info">4</view><view class="fe-stat-box__label">登记中</view></view>
13
+					<view class="fe-stat-box"><view class="fe-stat-box__num fe-stat-box__num--success">3</view><view class="fe-stat-box__label">已完成</view></view>
14
+					<view class="fe-stat-box fe-stat-box--primary"><view class="fe-stat-box__num fe-stat-box__num--primary">10</view><view class="fe-stat-box__label">总计</view></view>
15
+				</view>
16
+				<view class="fe-progress-label">
17
+					<text class="fe-progress-label__text">登记进度</text>
18
+					<text class="fe-progress-label__value">7/10</text>
19
+				</view>
20
+				<view class="fe-progress-track"><view class="fe-progress-fill" style="width:70%;" /></view>
21
+				<text class="fe-section-heading fe-section-heading--solo">办理流程</text>
22
+				<view v-for="step in steps" :key="step.key" class="fe-timeline-item" :class="'fe-timeline-item--' + step.status">
23
+					<view class="fe-timeline-dot" :class="'fe-timeline-dot--' + step.status">
24
+						<text v-if="step.status === 'done'">✓</text>
25
+						<text v-else-if="step.status === 'active'">⏱</text>
26
+					</view>
27
+					<view>
28
+						<view class="fe-timeline-title" :class="{ 'fe-timeline-title--pending': step.status === 'pending' }">
29
+							{{ step.title }}
30
+							<text v-if="step.highlight" class="fe-timeline-highlight">{{ step.highlight }}</text>
31
+						</view>
32
+						<view v-if="step.time" class="fe-timeline-time">{{ step.time }}</view>
33
+					</view>
34
+				</view>
35
+				<view class="fe-section-heading-row">
36
+					<text class="fe-section-heading">人员列表</text>
37
+					<view class="fe-section-refresh" @tap="refreshPersons">
38
+						<text class="fe-section-refresh__icon">↻</text>
39
+						<text>刷新</text>
40
+					</view>
41
+				</view>
42
+				<view v-for="p in persons" :key="p.name" class="fe-person-item">
43
+					<view class="fe-person-avatar">{{ p.name[0] }}</view>
44
+					<view class="fe-person-info">
45
+						<view class="fe-person-name">{{ p.name }}</view>
46
+						<view class="fe-person-phone">{{ p.phone }}</view>
47
+					</view>
48
+					<text class="fe-person-status" :class="'fe-person-status--' + p.status">{{ p.statusText }}</text>
49
+				</view>
50
+			</view>
51
+		</scroll-view>
52
+		<view class="fe-sheet-footer">
53
+			<view class="fe-btn fe-btn--secondary fe-btn--lg" @tap="toast('已查看全部')">查看全部</view>
54
+			<view class="fe-btn fe-btn--primary fe-btn--lg" @tap="toast('已发送催办')">催办未登记</view>
55
+		</view>
56
+	</view>
57
+</template>
58
+
59
+<script>
60
+import { PROGRESS_PERSONS, PROGRESS_STEPS, showToast } from '@/common/chat-data.js'
61
+
62
+export default {
63
+	name: 'FeProgressSheet',
64
+	props: {
65
+		active: { type: Boolean, default: false },
66
+		elevated: { type: Boolean, default: false }
67
+	},
68
+	data() {
69
+		return {
70
+			persons: PROGRESS_PERSONS,
71
+			steps: PROGRESS_STEPS
72
+		}
73
+	},
74
+	methods: {
75
+		toast: showToast,
76
+		refreshPersons() {
77
+			this.persons = [...PROGRESS_PERSONS]
78
+			this.toast('已刷新')
79
+		}
80
+	}
81
+}
82
+</script>
83
+
84
+<style lang="scss" scoped>
85
+.fe-stat-box__num--warning { color: #F59E0B; }
86
+.fe-stat-box__num--info { color: #6366F1; }
87
+.fe-stat-box__num--success { color: #10B981; }
88
+.fe-stat-box__num--primary { color: #7C3AED; }
89
+
90
+.fe-progress-label {
91
+	display: flex;
92
+	justify-content: space-between;
93
+	margin: 24rpx 0 12rpx;
94
+}
95
+
96
+.fe-progress-label__text { color: #64748B; }
97
+.fe-progress-label__value { font-weight: 700; color: #7C3AED; }
98
+
99
+.fe-section-heading--solo {
100
+	display: block;
101
+	margin: 32rpx 0 20rpx;
102
+}
103
+
104
+.fe-section-heading-row {
105
+	display: flex;
106
+	align-items: center;
107
+	justify-content: space-between;
108
+	margin: 32rpx 0 20rpx;
109
+}
110
+
111
+.fe-section-heading {
112
+	font-size: 28rpx;
113
+	font-weight: 700;
114
+}
115
+
116
+.fe-section-refresh {
117
+	display: inline-flex;
118
+	align-items: center;
119
+	gap: 6rpx;
120
+	font-size: 26rpx;
121
+	font-weight: 600;
122
+	color: $fe-primary;
123
+}
124
+
125
+.fe-section-refresh__icon {
126
+	font-size: 32rpx;
127
+	line-height: 1;
128
+}
129
+
130
+.fe-timeline-title { font-weight: 600; }
131
+.fe-timeline-time { font-size: 24rpx; color: #94A3B8; }
132
+.fe-timeline-highlight { color: #7C3AED; }
133
+
134
+.fe-person-info { flex: 1; min-width: 0; }
135
+.fe-person-name { font-weight: 600; }
136
+.fe-person-phone { font-size: 24rpx; color: #64748B; }
137
+</style>

+ 104 - 0
huimv-employment/app/components/chat/FeReportFloat.vue

@@ -0,0 +1,104 @@
1
+<template>
2
+	<view
3
+		v-if="visible"
4
+		class="fe-report-float"
5
+		:style="{ top: top + 'px' }"
6
+		@touchstart="onTouchStart"
7
+		@touchmove.stop.prevent="onTouchMove"
8
+		@touchend="onTouchEnd"
9
+	>
10
+		<text class="fe-report-float__icon">📊</text>
11
+	</view>
12
+</template>
13
+
14
+<script>
15
+export default {
16
+	name: 'FeReportFloat',
17
+	props: {
18
+		visible: { type: Boolean, default: false }
19
+	},
20
+	data() {
21
+		return {
22
+			top: 0,
23
+			dragging: false,
24
+			touchStartY: 0,
25
+			startTop: 0,
26
+			moved: false,
27
+			inited: false
28
+		}
29
+	},
30
+	watch: {
31
+		visible(val) {
32
+			if (val && !this.inited) this.initPosition()
33
+		}
34
+	},
35
+	mounted() {
36
+		if (this.visible) this.initPosition()
37
+	},
38
+	methods: {
39
+		initPosition() {
40
+			const sys = uni.getSystemInfoSync()
41
+			const headerH = uni.upx2px(112)
42
+			const footerH = uni.upx2px(128 + 112) + (sys.safeAreaInsets?.bottom || 0)
43
+			const floatH = uni.upx2px(88)
44
+			const min = headerH + uni.upx2px(24)
45
+			const max = sys.windowHeight - footerH - floatH - uni.upx2px(24)
46
+			this.top = min + (max - min) * 0.38
47
+			this.inited = true
48
+		},
49
+		getBounds() {
50
+			const sys = uni.getSystemInfoSync()
51
+			const headerH = uni.upx2px(112)
52
+			const footerH = uni.upx2px(128 + 112) + (sys.safeAreaInsets?.bottom || 0)
53
+			const floatH = uni.upx2px(88)
54
+			return {
55
+				min: headerH + uni.upx2px(16),
56
+				max: sys.windowHeight - footerH - floatH - uni.upx2px(16)
57
+			}
58
+		},
59
+		clampTop(value) {
60
+			const { min, max } = this.getBounds()
61
+			return Math.max(min, Math.min(max, value))
62
+		},
63
+		onTouchStart(e) {
64
+			this.dragging = true
65
+			this.moved = false
66
+			this.touchStartY = e.touches[0].clientY
67
+			this.startTop = this.top
68
+		},
69
+		onTouchMove(e) {
70
+			if (!this.dragging) return
71
+			const dy = e.touches[0].clientY - this.touchStartY
72
+			if (Math.abs(dy) > 4) this.moved = true
73
+			this.top = this.clampTop(this.startTop + dy)
74
+		},
75
+		onTouchEnd() {
76
+			this.dragging = false
77
+			this.top = this.clampTop(this.top)
78
+			if (!this.moved) this.$emit('open')
79
+		}
80
+	}
81
+}
82
+</script>
83
+
84
+<style lang="scss" scoped>
85
+.fe-report-float {
86
+	position: fixed;
87
+	left: 0;
88
+	z-index: 98;
89
+	width: 88rpx;
90
+	height: 88rpx;
91
+	border-radius: 0 44rpx 44rpx 0;
92
+	background: linear-gradient(135deg, $fe-primary, $fe-secondary);
93
+	color: #fff;
94
+	display: flex;
95
+	align-items: center;
96
+	justify-content: center;
97
+	box-shadow: 4rpx 4rpx 24rpx rgba(124, 58, 237, 0.35);
98
+}
99
+
100
+.fe-report-float__icon {
101
+	font-size: 40rpx;
102
+	line-height: 1;
103
+}
104
+</style>

+ 122 - 0
huimv-employment/app/components/chat/FeReportSheet.vue

@@ -0,0 +1,122 @@
1
+<template>
2
+	<view class="fe-bottom-sheet" :class="{ 'fe-bottom-sheet--active': active }">
3
+		<view class="fe-sheet-handle" />
4
+		<view class="fe-sheet-header">
5
+			<view class="fe-sheet-title">📊 今日汇报</view>
6
+		</view>
7
+		<view class="fe-sheet-source">{{ report.date }} · AI 自动汇总</view>
8
+		<scroll-view scroll-y class="fe-sheet-body" :show-scrollbar="false">
9
+			<view class="fe-sheet-body__inner">
10
+				<view
11
+					v-for="(item, i) in report.items"
12
+					:key="i"
13
+					class="fe-report-item"
14
+					:class="'fe-report-item--' + item.status"
15
+				>
16
+					<view class="fe-report-item__index">{{ i + 1 }}</view>
17
+					<view class="fe-report-item__body">
18
+						<view class="fe-report-item__title">{{ item.title }}</view>
19
+						<view v-if="item.desc" class="fe-report-item__desc">{{ item.desc }}</view>
20
+					</view>
21
+					<text class="fe-report-item__val" :class="{ 'fe-report-item__val--danger': item.status === 'danger' }">
22
+						{{ item.value }}
23
+					</text>
24
+				</view>
25
+			</view>
26
+		</scroll-view>
27
+		<view class="fe-sheet-footer">
28
+			<view class="fe-btn fe-btn--secondary fe-btn--lg" @tap="toast('已发送催办')">一键催办</view>
29
+			<view class="fe-btn fe-btn--primary fe-btn--lg" @tap="$emit('progress')">查看进度</view>
30
+		</view>
31
+	</view>
32
+</template>
33
+
34
+<script>
35
+import { DAILY_REPORT, showToast } from '@/common/chat-data.js'
36
+
37
+export default {
38
+	name: 'FeReportSheet',
39
+	props: {
40
+		active: { type: Boolean, default: false }
41
+	},
42
+	data() {
43
+		return { report: DAILY_REPORT }
44
+	},
45
+	methods: {
46
+		toast: showToast
47
+	}
48
+}
49
+</script>
50
+
51
+<style lang="scss" scoped>
52
+.fe-report-item {
53
+	display: flex;
54
+	align-items: flex-start;
55
+	gap: 20rpx;
56
+	padding: 24rpx;
57
+	margin-bottom: 16rpx;
58
+	background: #F8FAFC;
59
+	border-radius: $fe-radius-md;
60
+	border-left: 6rpx solid $fe-primary;
61
+}
62
+
63
+.fe-report-item--danger {
64
+	border-left-color: $fe-danger;
65
+	background: #FEF2F2;
66
+}
67
+
68
+.fe-report-item--pending {
69
+	border-left-color: $fe-warning;
70
+}
71
+
72
+.fe-report-item__index {
73
+	width: 44rpx;
74
+	height: 44rpx;
75
+	border-radius: 50%;
76
+	background: $fe-primary;
77
+	color: #fff;
78
+	font-size: 24rpx;
79
+	font-weight: 700;
80
+	display: flex;
81
+	align-items: center;
82
+	justify-content: center;
83
+	flex-shrink: 0;
84
+}
85
+
86
+.fe-report-item--danger .fe-report-item__index {
87
+	background: $fe-danger;
88
+}
89
+
90
+.fe-report-item--pending .fe-report-item__index {
91
+	background: $fe-warning;
92
+}
93
+
94
+.fe-report-item__body {
95
+	flex: 1;
96
+	min-width: 0;
97
+}
98
+
99
+.fe-report-item__title {
100
+	font-size: 28rpx;
101
+	font-weight: 600;
102
+	color: $fe-foreground;
103
+}
104
+
105
+.fe-report-item__desc {
106
+	font-size: 24rpx;
107
+	color: $fe-muted;
108
+	margin-top: 6rpx;
109
+}
110
+
111
+.fe-report-item__val {
112
+	font-size: 26rpx;
113
+	font-weight: 600;
114
+	color: $fe-foreground;
115
+	flex-shrink: 0;
116
+	text-align: right;
117
+}
118
+
119
+.fe-report-item__val--danger {
120
+	color: $fe-danger;
121
+}
122
+</style>

+ 78 - 0
huimv-employment/app/components/chat/FeSettlementSheet.vue

@@ -0,0 +1,78 @@
1
+<template>
2
+	<view class="fe-bottom-sheet" :class="{ 'fe-bottom-sheet--active': active }">
3
+		<view class="fe-sheet-handle" />
4
+		<view class="fe-sheet-header">
5
+			<view class="fe-sheet-title">💳 结算确认单 <text class="fe-sheet-badge fe-sheet-badge--danger">待支付</text></view>
6
+		</view>
7
+		<view class="fe-sheet-source">浦东仓库搬运 BATCH-001</view>
8
+		<scroll-view scroll-y class="fe-sheet-body" :show-scrollbar="false">
9
+			<view class="fe-sheet-body__inner">
10
+				<view class="fe-stat-grid fe-stat-grid--3">
11
+					<view class="fe-stat-box">
12
+						<view class="fe-stat-box__num fe-stat-box__num--lg fe-stat-box__num--primary">¥50,400</view>
13
+						<view class="fe-stat-box__label">实发总额</view>
14
+					</view>
15
+					<view class="fe-stat-box fe-stat-box--danger">
16
+						<view class="fe-stat-box__num fe-stat-box__num--lg fe-stat-box__num--danger">-¥2,400</view>
17
+						<view class="fe-stat-box__label">扣款/个税</view>
18
+					</view>
19
+					<view class="fe-stat-box">
20
+						<view class="fe-stat-box__num fe-stat-box__num--lg fe-stat-box__num--primary">¥52,800</view>
21
+						<view class="fe-stat-box__label">应发总额</view>
22
+					</view>
23
+				</view>
24
+				<view class="fe-settlement-row"><text class="fe-info-row__label">劳务费合计</text><text class="fe-info-row__value">¥48,000</text></view>
25
+				<view class="fe-settlement-row"><text class="fe-info-row__label">平台服务费</text><text class="fe-info-row__value">¥3,840</text></view>
26
+				<view class="fe-settlement-row"><text class="fe-info-row__label">保险费用</text><text class="fe-info-row__value">¥960</text></view>
27
+				<view class="fe-settlement-row fe-settlement-row--total">
28
+					<text>应付总额</text>
29
+					<text class="fe-settlement-row__val--primary">¥52,800</text>
30
+				</view>
31
+			</view>
32
+		</scroll-view>
33
+		<view class="fe-sheet-footer">
34
+			<view class="fe-btn fe-btn--secondary fe-btn--lg" @tap="toast('已导出明细')">导出明细</view>
35
+			<view class="fe-btn fe-btn--primary fe-btn--lg" @tap="$emit('pay')">确认支付</view>
36
+		</view>
37
+	</view>
38
+</template>
39
+
40
+<script>
41
+import { showToast } from '@/common/chat-data.js'
42
+
43
+export default {
44
+	name: 'FeSettlementSheet',
45
+	props: {
46
+		active: { type: Boolean, default: false }
47
+	},
48
+	methods: {
49
+		toast: showToast
50
+	}
51
+}
52
+</script>
53
+
54
+<style lang="scss" scoped>
55
+.fe-sheet-badge--danger {
56
+	background: #EF4444;
57
+}
58
+
59
+.fe-stat-grid--3 {
60
+	grid-template-columns: repeat(3, 1fr);
61
+}
62
+
63
+.fe-stat-box--danger {
64
+	background: #FEE2E2;
65
+}
66
+
67
+.fe-stat-box__num--lg {
68
+	font-size: 36rpx;
69
+}
70
+
71
+.fe-stat-box__num--primary {
72
+	color: #7C3AED;
73
+}
74
+
75
+.fe-stat-box__num--danger {
76
+	color: #EF4444;
77
+}
78
+</style>

Fichier diff supprimé car celui-ci est trop grand
+ 2378 - 0
huimv-employment/app/doc/flexible-employment-demo.html


+ 535 - 0
huimv-employment/app/doc/微信小程序功能需求.md

@@ -0,0 +1,535 @@
1
+# 微信小程序技术沟通文档:灵活用工与用工税筹模块
2
+
3
+## 1. 项目背景
4
+
5
+本项目面向中小微企业建设一个整体智能体。`灵活用工与用工税筹`只是其中一个业务子模块,不是整个产品。
6
+
7
+当企业用户提出用工、临时工、合规用工、用工成本、结算、保险、发票等相关问题时,主智能体会调用该子模块。该子模块负责理解用户意图、生成办理草稿、做成本对比、识别风险、生成提交前数据,并在用户确认后交给后端流程继续处理。
8
+
9
+当前计划增加一个微信小程序,作为企业主和临时工使用的轻量前台。
10
+
11
+## 2. 产品核心原则
12
+
13
+### 2.1 草稿优先
14
+
15
+中小微企业用户通常不希望先回答大量问题。产品体验应当是:
16
+
17
+```text
18
+智能体先帮用户生成草稿 -> 用户确认或修改 -> 后端继续办理
19
+```
20
+
21
+默认逻辑:
22
+
23
+- 能从企业画像推断的字段,不要重复问用户。
24
+- 能从历史偏好推断的字段,优先复用。
25
+- 能使用合理默认值的字段,先生成草稿,再允许修改。
26
+- 只有关键缺失信息或高风险信息,才主动追问。
27
+
28
+例如企业已有一批临时工时,不应一开始要求企业主逐个填写名单。更好的方式是先生成登记批次和二维码,让临时工扫码自行登记。
29
+
30
+### 2.2 弱化第三方平台存在感
31
+
32
+前台用户体验中,不应频繁出现“第三方 API”“连接器 payload”“某某平台接口返回”等技术表达。
33
+
34
+用户看到的是:
35
+
36
+```text
37
+我已帮你生成这批临时工规范办理草稿。
38
+确认后,将继续进入登记、保险、打卡、结算等流程。
39
+```
40
+
41
+内部系统可以记录:
42
+
43
+```text
44
+provider = partner_platform
45
+action = create_requirement
46
+audit_required = true
47
+```
48
+
49
+只有在用户授权、合同、保险、结算、发票、支付、法律合规或用户明确询问时,才需要披露外部服务方或第三方处理信息。
50
+
51
+### 2.3 成本对比优先
52
+
53
+企业主最关心的不是复杂费率,而是三个结果:
54
+
55
+```text
56
+企业总支出
57
+平均每人成本
58
+员工到手金额
59
+```
60
+
61
+灵活用工模块需要展示:
62
+
63
+- 当前处理方式的大致支出
64
+- 方案一
65
+- 方案二
66
+- 方案三
67
+
68
+三种方案的真实名称、费率、适用条件后续应从正式方案目录中配置。当前测试阶段可以使用测试默认值,但必须清楚标注为测试数据。
69
+
70
+## 3. 小程序定位
71
+
72
+微信小程序只作为轻前台,不承载核心智能体逻辑。
73
+
74
+小程序负责:
75
+
76
+- 企业主登录和企业绑定
77
+- 企业主对话入口
78
+- 展示智能体生成的用工草稿
79
+- 展示成本对比
80
+- 让用户确认、修改、选择方案
81
+- 生成或展示临时工扫码登记入口
82
+- 临时工扫码填写个人资料
83
+- 企业主查看登记进度和办理状态
84
+
85
+小程序不负责:
86
+
87
+- 直接调用大模型
88
+- 直接调用合作平台 API
89
+- 保存模型密钥或平台密钥
90
+- 执行复杂风控判断
91
+- 执行核心成本测算逻辑
92
+- 保存不可控的敏感业务规则
93
+
94
+## 4. 建议系统架构
95
+
96
+```mermaid
97
+flowchart LR
98
+    A["微信小程序"] --> B["业务后端 API"]
99
+    B --> C["主智能体 / LangGraph"]
100
+    C --> D["灵活用工与用工税筹子模块"]
101
+    D --> E["模块私有 RAG"]
102
+    D --> F["成本测算服务"]
103
+    D --> G["审计与确认模块"]
104
+    D --> H["第三方连接器模块"]
105
+    H --> I["合作平台 API"]
106
+
107
+    J["临时工扫码登记页"] --> B
108
+    B --> K["业务数据库"]
109
+    B --> L["文件/图片/附件存储"]
110
+```
111
+
112
+## 5. 端到端业务流程
113
+
114
+### 5.1 企业已有临时工,想规范处理
115
+
116
+```mermaid
117
+sequenceDiagram
118
+    participant U as 企业主
119
+    participant MP as 微信小程序
120
+    participant API as 业务后端
121
+    participant Agent as 主智能体
122
+    participant FE as 灵活用工子模块
123
+    participant Worker as 临时工
124
+    participant Conn as 连接器模块
125
+
126
+    U->>MP: 我现在有一批临时工,想规范一下
127
+    MP->>API: 提交用户消息
128
+    API->>Agent: 请求意图识别和处理
129
+    Agent->>FE: 调用灵活用工子模块
130
+    FE->>FE: 生成登记草稿和默认字段
131
+    FE->>FE: 生成成本对比
132
+    FE-->>API: 返回草稿、成本方案、待确认项
133
+    API-->>MP: 展示草稿和成本对比
134
+    U->>MP: 确认或修改,选择方案
135
+    MP->>API: 提交确认结果
136
+    API->>FE: 生成临时工登记批次
137
+    API-->>MP: 返回二维码/登记链接
138
+    Worker->>MP: 扫码填写个人信息
139
+    MP->>API: 提交临时工登记资料
140
+    API-->>MP: 企业主查看登记进度
141
+    U->>MP: 确认提交
142
+    API->>Conn: 内部提交办理请求
143
+```
144
+
145
+### 5.2 企业新增用工需求
146
+
147
+```text
148
+企业主输入自然语言需求
149
+-> 智能体生成用工需求草稿
150
+-> 展示成本对比和风险提示
151
+-> 企业主确认或修改
152
+-> 进入审计与确认
153
+-> 后端提交办理
154
+-> 小程序展示状态进度
155
+```
156
+
157
+## 6. 页面建议
158
+
159
+第一版小程序建议只做最小闭环。
160
+
161
+### 6.1 企业主端
162
+
163
+1. 首页 / 对话页
164
+   - 企业主自然语言输入需求
165
+   - 展示智能体回复
166
+   - 支持继续补充信息
167
+
168
+2. 用工草稿页
169
+   - 展示用工类型、工种、地点、结算方式、保险、打卡、二维码有效期等
170
+   - 字段可修改
171
+   - 明确显示哪些是默认生成
172
+
173
+3. 成本对比页
174
+   - 当前方式
175
+   - 方案一
176
+   - 方案二
177
+   - 方案三
178
+   - 核心列:企业总支出、平均每人成本、员工到手
179
+
180
+4. 确认提交页
181
+   - 展示最终确认信息
182
+   - 用户确认后才继续办理
183
+   - 高风险或授权类动作必须留下确认记录
184
+
185
+5. 登记进度页
186
+   - 已登记人数
187
+   - 待登记人数
188
+   - 资料异常人数
189
+   - 可提醒临时工继续填写
190
+
191
+### 6.2 临时工端
192
+
193
+临时工通过二维码进入轻量登记页。
194
+
195
+需要支持:
196
+
197
+- 手机号验证
198
+- 身份信息填写
199
+- 银行卡信息填写
200
+- 工种或任务确认
201
+- 协议/授权确认
202
+- 提交结果提示
203
+
204
+临时工端不需要看到企业主的成本测算和方案选择。
205
+
206
+## 7. 后端模块边界
207
+
208
+### 7.1 主智能体
209
+
210
+负责:
211
+
212
+- 用户意图识别
213
+- 判断是否进入灵活用工子模块
214
+- 跨模块编排
215
+- 汇总不同子模块返回结果
216
+
217
+建议技术:
218
+
219
+- LangGraph
220
+- Pydantic
221
+- OpenAI Responses API
222
+
223
+### 7.2 灵活用工与用工税筹子模块
224
+
225
+负责:
226
+
227
+- 用工意图识别
228
+- 用工场景分类
229
+- 风险诊断
230
+- 成本测算
231
+- 三方案对比
232
+- 用工草稿生成
233
+- 平台提交前数据准备
234
+- 平台状态解释
235
+
236
+当前已规划技术:
237
+
238
+- FastAPI
239
+- Pydantic
240
+- LangGraph 子图
241
+- Python deterministic rules
242
+- PostgreSQL + pgvector
243
+- LlamaIndex
244
+- pytest
245
+
246
+### 7.3 第三方连接器模块
247
+
248
+负责:
249
+
250
+- 对接合作平台 API
251
+- 处理鉴权、提交、查询、回调
252
+- 保存原始请求和响应
253
+- 把技术状态转成业务状态
254
+
255
+注意:连接器细节不直接暴露给用户。
256
+
257
+### 7.4 审计与确认模块
258
+
259
+负责:
260
+
261
+- 用户确认记录
262
+- 关键字段变更记录
263
+- 授权确认
264
+- 外部提交前确认
265
+- 高风险动作留痕
266
+
267
+## 8. 接口设计原则
268
+
269
+所有模块必须有接收层和输出层。
270
+
271
+```text
272
+外部上下文
273
+-> 接收层转换为模块内部标准格式
274
+-> 模块内部处理
275
+-> 输出层转换为目标方需要的格式
276
+```
277
+
278
+小程序不直接适配灵活用工模块内部结构。推荐路径是:
279
+
280
+```text
281
+小程序
282
+-> 业务后端 API
283
+-> 主智能体
284
+-> 灵活用工子模块
285
+-> 输出给业务后端
286
+-> 小程序展示
287
+```
288
+
289
+## 9. 第一版 API 草案
290
+
291
+这些接口用于技术沟通,不是最终定稿。
292
+
293
+### 9.1 企业主发送消息
294
+
295
+```http
296
+POST /api/v1/agent/messages
297
+```
298
+
299
+请求:
300
+
301
+```json
302
+{
303
+  "enterprise_id": "ent_001",
304
+  "conversation_id": "conv_001",
305
+  "message": "我现在有一批临时工,想规范一下"
306
+}
307
+```
308
+
309
+返回:
310
+
311
+```json
312
+{
313
+  "reply": "我先帮你生成一份临时工规范办理草稿。",
314
+  "next_action": "show_employment_draft",
315
+  "draft_id": "draft_001"
316
+}
317
+```
318
+
319
+### 9.2 获取用工草稿
320
+
321
+```http
322
+GET /api/v1/flexible-employment/drafts/{draft_id}
323
+```
324
+
325
+返回:
326
+
327
+```json
328
+{
329
+  "draft_id": "draft_001",
330
+  "scenario": "existing_worker_regularization",
331
+  "work_location": {
332
+    "value": "企业注册地址",
333
+    "source": "enterprise_registered_address",
334
+    "editable": true
335
+  },
336
+  "insurance_required": true,
337
+  "check_in_required": true,
338
+  "qr_valid_days": 7,
339
+  "missing_fields": ["主要工作内容"],
340
+  "risk_prompts": []
341
+}
342
+```
343
+
344
+### 9.3 获取成本对比
345
+
346
+```http
347
+GET /api/v1/flexible-employment/drafts/{draft_id}/cost-comparison
348
+```
349
+
350
+返回:
351
+
352
+```json
353
+{
354
+  "rows": [
355
+    {
356
+      "plan_id": "current",
357
+      "plan_name": "当前处理方式",
358
+      "employer_total_outflow": 48000,
359
+      "employer_average_cost_per_worker": 6000,
360
+      "worker_take_home_per_worker": 6000
361
+    },
362
+    {
363
+      "plan_id": "plan_1",
364
+      "plan_name": "测试方案一",
365
+      "employer_total_outflow": 51705.6,
366
+      "employer_average_cost_per_worker": 6463.2,
367
+      "worker_take_home_per_worker": 6000,
368
+      "status": "test_only"
369
+    }
370
+  ],
371
+  "notice": "当前方案数据为测试默认值,正式费率以后端方案目录为准。"
372
+}
373
+```
374
+
375
+### 9.4 企业主确认方案
376
+
377
+```http
378
+POST /api/v1/flexible-employment/drafts/{draft_id}/confirm
379
+```
380
+
381
+请求:
382
+
383
+```json
384
+{
385
+  "selected_plan_id": "plan_1",
386
+  "confirmed_fields": {
387
+    "work_location": "某某工业园 3 号仓",
388
+    "work_content": "仓库搬运"
389
+  }
390
+}
391
+```
392
+
393
+返回:
394
+
395
+```json
396
+{
397
+  "batch_id": "batch_001",
398
+  "registration_url": "https://example.com/register/batch_001",
399
+  "qr_code_url": "https://example.com/qrcode/batch_001.png"
400
+}
401
+```
402
+
403
+### 9.5 临时工扫码登记
404
+
405
+```http
406
+POST /api/v1/worker-registration/{batch_id}/submit
407
+```
408
+
409
+请求:
410
+
411
+```json
412
+{
413
+  "name": "张三",
414
+  "mobile": "13800000000",
415
+  "id_card_no": "encrypted_value",
416
+  "bank_account": "encrypted_value",
417
+  "confirmed_work_type": "仓库搬运"
418
+}
419
+```
420
+
421
+返回:
422
+
423
+```json
424
+{
425
+  "status": "submitted",
426
+  "message": "登记已提交,请等待企业确认。"
427
+}
428
+```
429
+
430
+## 10. 数据安全和合规注意点
431
+
432
+第一版就需要重视:
433
+
434
+- AppSecret 只放后端,不放小程序前端。
435
+- OpenAI API Key 只放后端。
436
+- 合作平台 API Key 只放后端。
437
+- 身份证号、银行卡号、手机号等敏感信息需要加密存储或脱敏存储。
438
+- 临时工扫码链接需要有有效期。
439
+- 企业主确认、临时工授权、外部提交动作需要留痕。
440
+- 小程序前端不要保存敏感字段明文缓存。
441
+- 重要操作需要服务端权限校验,不能只依赖前端按钮隐藏。
442
+
443
+## 11. 推荐技术栈
444
+
445
+### 11.1 小程序前端
446
+
447
+- 微信原生小程序
448
+- TypeScript
449
+- TDesign WeChat 或 Vant Weapp
450
+- 微信登录能力
451
+- 后端 Session / Token 鉴权
452
+
453
+第一版不建议上复杂跨端框架,先验证核心业务闭环。
454
+
455
+### 11.2 后端
456
+
457
+- Python
458
+- FastAPI
459
+- Pydantic
460
+- LangGraph
461
+- PostgreSQL
462
+- Redis
463
+- pgvector
464
+- LlamaIndex
465
+- pytest
466
+
467
+### 11.3 模型层
468
+
469
+建议使用后端模型路由:
470
+
471
+- 轻量分类、字段抽取:低成本模型
472
+- 普通对话、草稿生成:主力模型
473
+- 高风险解释、合规提示、复杂总结:更强模型
474
+
475
+模型调用必须放在后端,不在小程序前端直连。
476
+
477
+## 12. 第一阶段交付建议
478
+
479
+第一阶段目标是做最小闭环 Demo,而不是一次性做完整系统。
480
+
481
+建议范围:
482
+
483
+1. 企业主登录和企业选择
484
+2. 企业主输入一句用工需求
485
+3. 后端返回用工草稿
486
+4. 展示成本对比
487
+5. 企业主选择方案并确认
488
+6. 生成模拟二维码
489
+7. 临时工扫码填写模拟登记信息
490
+8. 企业主查看登记进度
491
+
492
+暂不做:
493
+
494
+- 正式合作平台 API 调用
495
+- 正式支付
496
+- 正式电子合同
497
+- 正式发票流程
498
+- 完整企业画像系统
499
+- 完整 GraphRAG
500
+
501
+## 13. 当前未确认事项
502
+
503
+需要后续确认:
504
+
505
+- 小程序主体类型和 AppID
506
+- 企业认证方式
507
+- 三种方案的正式名称、费率、适用条件
508
+- 合作平台正式 API 文档
509
+- 临时工登记所需的法定字段
510
+- 是否需要微信支付
511
+- 是否需要短信服务
512
+- 是否需要电子签
513
+- 敏感信息加密方案
514
+- 小程序类目和合规审核要求
515
+
516
+## 14. 技术结论
517
+
518
+推荐路线:
519
+
520
+```text
521
+微信小程序做轻前台
522
+FastAPI 做业务后端
523
+LangGraph 做主智能体和模块编排
524
+Pydantic 做模块间标准数据契约
525
+灵活用工模块负责草稿、成本、风险、结构化
526
+连接器模块负责合作平台 API
527
+审计确认模块负责用户确认和留痕
528
+```
529
+
530
+核心体验不是让企业主填写复杂系统,而是:
531
+
532
+```text
533
+智能体先生成方案,企业主确认,临时工扫码补资料,后端继续办理。
534
+```
535
+

+ 20 - 0
huimv-employment/app/index.html

@@ -0,0 +1,20 @@
1
+<!DOCTYPE html>
2
+<html lang="zh-CN">
3
+  <head>
4
+    <meta charset="UTF-8" />
5
+    <script>
6
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
7
+        CSS.supports('top: constant(a)'))
8
+      document.write(
9
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
10
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
11
+    </script>
12
+    <title></title>
13
+    <!--preload-links-->
14
+    <!--app-context-->
15
+  </head>
16
+  <body>
17
+    <div id="app"><!--app-html--></div>
18
+    <script type="module" src="/main.js"></script>
19
+  </body>
20
+</html>

+ 34 - 0
huimv-employment/app/main.js

@@ -0,0 +1,34 @@
1
+import App from './App'
2
+import { hideWxHomeButton } from '@/common/mp-nav.js'
3
+
4
+// #ifndef VUE3
5
+import Vue from 'vue'
6
+import './uni.promisify.adaptor'
7
+Vue.config.productionTip = false
8
+Vue.mixin({
9
+	onShow() {
10
+		hideWxHomeButton()
11
+	}
12
+})
13
+App.mpType = 'app'
14
+const app = new Vue({
15
+  ...App
16
+})
17
+app.$mount()
18
+// #endif
19
+
20
+// #ifdef VUE3
21
+import { createSSRApp } from 'vue'
22
+import { hideWxHomeButton } from '@/common/mp-nav.js'
23
+export function createApp() {
24
+  const app = createSSRApp(App)
25
+  app.mixin({
26
+    onShow() {
27
+      hideWxHomeButton()
28
+    }
29
+  })
30
+  return {
31
+    app
32
+  }
33
+}
34
+// #endif

+ 72 - 0
huimv-employment/app/manifest.json

@@ -0,0 +1,72 @@
1
+{
2
+    "name" : "app",
3
+    "appid" : "__UNI__C116706",
4
+    "description" : "",
5
+    "versionName" : "1.0.0",
6
+    "versionCode" : "100",
7
+    "transformPx" : false,
8
+    /* 5+App特有相关 */
9
+    "app-plus" : {
10
+        "usingComponents" : true,
11
+        "nvueStyleCompiler" : "uni-app",
12
+        "compilerVersion" : 3,
13
+        "splashscreen" : {
14
+            "alwaysShowBeforeRender" : true,
15
+            "waiting" : true,
16
+            "autoclose" : true,
17
+            "delay" : 0
18
+        },
19
+        /* 模块配置 */
20
+        "modules" : {},
21
+        /* 应用发布信息 */
22
+        "distribute" : {
23
+            /* android打包配置 */
24
+            "android" : {
25
+                "permissions" : [
26
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
27
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
28
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
29
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
30
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
31
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
32
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
33
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
34
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
35
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
36
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
37
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
38
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
39
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
40
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
41
+                ]
42
+            },
43
+            /* ios打包配置 */
44
+            "ios" : {},
45
+            /* SDK配置 */
46
+            "sdkConfigs" : {}
47
+        }
48
+    },
49
+    /* 快应用特有相关 */
50
+    "quickapp" : {},
51
+    /* 小程序特有相关 */
52
+    "mp-weixin" : {
53
+        "appid" : "",
54
+        "setting" : {
55
+            "urlCheck" : false
56
+        },
57
+        "usingComponents" : true
58
+    },
59
+    "mp-alipay" : {
60
+        "usingComponents" : true
61
+    },
62
+    "mp-baidu" : {
63
+        "usingComponents" : true
64
+    },
65
+    "mp-toutiao" : {
66
+        "usingComponents" : true
67
+    },
68
+    "uniStatistics" : {
69
+        "enable" : false
70
+    },
71
+    "vueVersion" : "2"
72
+}

+ 44 - 0
huimv-employment/app/pages.json

@@ -0,0 +1,44 @@
1
+{
2
+	"pages": [
3
+		{
4
+			"path": "pages/auth/login",
5
+			"style": {
6
+				"navigationBarTitleText": ""
7
+			}
8
+		},
9
+		{
10
+			"path": "pages/chat/index",
11
+			"style": {
12
+				"navigationBarTitleText": "",
13
+				"disableScroll": true
14
+			}
15
+		},
16
+		{
17
+			"path": "pages/worker/register",
18
+			"style": {
19
+				"navigationStyle": "custom",
20
+				"navigationBarTitleText": "临时工登记",
21
+				"disableScroll": true
22
+			}
23
+		},
24
+		{
25
+			"path": "pages/worker/wallet",
26
+			"style": {
27
+				"navigationBarTitleText": "首页"
28
+			}
29
+		},
30
+		{
31
+			"path": "pages/worker/profile",
32
+			"style": {
33
+				"navigationBarTitleText": "我的"
34
+			}
35
+		}
36
+	],
37
+	"globalStyle": {
38
+		"navigationBarTextStyle": "black",
39
+		"navigationBarTitleText": "",
40
+		"navigationBarBackgroundColor": "#FFFFFF",
41
+		"backgroundColor": "#FAF5FF"
42
+	},
43
+	"uniIdRouter": {}
44
+}

+ 152 - 0
huimv-employment/app/pages/auth/login.vue

@@ -0,0 +1,152 @@
1
+<template>
2
+	<view class="fe-page fe-page--white login-page">
3
+		<scroll-view scroll-y class="login-scroll" :show-scrollbar="false">
4
+			<view class="login-inner">
5
+				<view class="fe-login-visual">
6
+					<text class="fe-login-visual__title">智能用工,轻松管理</text>
7
+					<text class="fe-login-visual__desc">AI驱动的企业灵活用工一站式解决方案</text>
8
+					<view class="fe-login-feature">
9
+						<text class="fe-login-feature__check">✓</text>
10
+						<text>对话式操作,无需复杂表单</text>
11
+					</view>
12
+					<view class="fe-login-feature">
13
+						<text class="fe-login-feature__check">✓</text>
14
+						<text>智能草稿生成,一键确认</text>
15
+					</view>
16
+					<view class="fe-login-feature">
17
+						<text class="fe-login-feature__check">✓</text>
18
+						<text>成本透明对比,精打细算</text>
19
+					</view>
20
+					<view class="fe-login-feature">
21
+						<text class="fe-login-feature__check">✓</text>
22
+						<text>全流程合规,安全可追溯</text>
23
+					</view>
24
+				</view>
25
+
26
+				<view class="fe-login-card">
27
+					<text class="fe-login-card__title">欢迎回来</text>
28
+					<text class="fe-login-card__subtitle">请选择您的身份登录</text>
29
+
30
+					<view class="fe-login-tabs">
31
+						<view
32
+							class="fe-login-tab"
33
+							:class="{ 'fe-login-tab--active': tab === 'enterprise' }"
34
+							@tap="tab = 'enterprise'"
35
+						>🏢 企业登录</view>
36
+						<view
37
+							class="fe-login-tab"
38
+							:class="{ 'fe-login-tab--active': tab === 'worker' }"
39
+							@tap="tab = 'worker'"
40
+						>👤 临时工登录</view>
41
+					</view>
42
+
43
+					<view v-if="tab === 'enterprise'">
44
+						<view class="fe-form-group">
45
+							<text class="fe-form-label">企业名称</text>
46
+							<input class="fe-form-input" placeholder="请输入企业名称或统一社会信用代码" />
47
+						</view>
48
+						<view class="fe-form-group">
49
+							<text class="fe-form-label">登录手机号</text>
50
+							<input class="fe-form-input" type="number" placeholder="请输入手机号" />
51
+						</view>
52
+						<view class="fe-form-group">
53
+							<text class="fe-form-label">验证码</text>
54
+							<view class="fe-form-row">
55
+								<input class="fe-form-input" placeholder="请输入验证码" />
56
+								<view class="fe-btn fe-btn--secondary" style="white-space:nowrap;padding:0 24rpx;height:96rpx;" @tap="toast('验证码已发送')">获取验证码</view>
57
+							</view>
58
+						</view>
59
+						<view class="fe-btn fe-btn--primary fe-btn--lg fe-btn--full" style="margin-top:24rpx;" @tap="loginEnterprise">
60
+							登录并绑定企业
61
+						</view>
62
+						<view class="fe-login-divider">或</view>
63
+						<view class="fe-btn fe-btn--wechat fe-btn--lg fe-btn--full" @tap="loginEnterprise">
64
+							微信快捷登录
65
+						</view>
66
+					</view>
67
+
68
+					<view v-else>
69
+						<view class="fe-form-group">
70
+							<text class="fe-form-label">手机号</text>
71
+							<input class="fe-form-input" type="number" placeholder="请输入手机号" />
72
+						</view>
73
+						<view class="fe-form-group">
74
+							<text class="fe-form-label">验证码</text>
75
+							<view class="fe-form-row">
76
+								<input class="fe-form-input" placeholder="请输入验证码" />
77
+								<view class="fe-btn fe-btn--secondary" style="white-space:nowrap;padding:0 24rpx;height:96rpx;" @tap="toast('验证码已发送')">获取验证码</view>
78
+							</view>
79
+						</view>
80
+						<view class="fe-btn fe-btn--primary fe-btn--lg fe-btn--full" style="margin-top:24rpx;" @tap="loginWorker">
81
+							登记并扫码入场
82
+						</view>
83
+						<text class="login-note">首次登录将自动创建临时工档案</text>
84
+					</view>
85
+
86
+					<text class="login-agreement">登录即同意 服务协议 和 隐私政策</text>
87
+				</view>
88
+			</view>
89
+		</scroll-view>
90
+	</view>
91
+</template>
92
+
93
+<script>
94
+import store from '@/common/store.js'
95
+import { showToast } from '@/common/chat-data.js'
96
+
97
+export default {
98
+	data() {
99
+		return { tab: 'enterprise' }
100
+	},
101
+	onShow() {
102
+		if (store.getState().loggedIn) {
103
+			const role = store.getState().role
104
+			uni.redirectTo({
105
+				url: role === 'worker' ? store.getWorkerEntryUrl() : '/pages/chat/index'
106
+			})
107
+		}
108
+	},
109
+	methods: {
110
+		toast: showToast,
111
+		loginEnterprise() {
112
+			store.login('enterprise')
113
+			uni.redirectTo({ url: '/pages/chat/index' })
114
+		},
115
+		loginWorker() {
116
+			store.login('worker')
117
+			uni.redirectTo({ url: store.getWorkerEntryUrl() })
118
+		}
119
+	}
120
+}
121
+</script>
122
+
123
+<style lang="scss" scoped>
124
+.login-page {
125
+	min-height: 100vh;
126
+}
127
+
128
+.login-scroll {
129
+	height: 100%;
130
+}
131
+
132
+.login-inner {
133
+	padding: 48rpx;
134
+	padding-bottom: calc(48rpx + #{$fe-h-safe-bottom});
135
+}
136
+
137
+.login-note {
138
+	display: block;
139
+	text-align: center;
140
+	margin-top: 32rpx;
141
+	font-size: 26rpx;
142
+	color: $fe-gray-500;
143
+}
144
+
145
+.login-agreement {
146
+	display: block;
147
+	text-align: center;
148
+	margin-top: 32rpx;
149
+	font-size: 26rpx;
150
+	color: $fe-gray-500;
151
+}
152
+</style>

+ 624 - 0
huimv-employment/app/pages/chat/index.vue

@@ -0,0 +1,624 @@
1
+<template>
2
+	<view class="chat-page">
3
+		<view class="chat-header">
4
+			<view class="chat-header__row">
5
+				<view class="fe-nav-btn" @tap="openSidebar">
6
+					<text class="fe-nav-icon">☰</text>
7
+				</view>
8
+				<text class="chat-header__title">智能用工助手</text>
9
+				<view class="chat-header__actions">
10
+					<view class="fe-nav-btn" @tap="newChat">
11
+						<text class="fe-nav-icon fe-nav-icon--plus">+</text>
12
+					</view>
13
+					<view class="fe-nav-btn" @tap="openDrawer('message')">
14
+						<text class="fe-nav-icon">🔔</text>
15
+						<view class="fe-nav-badge">2</view>
16
+					</view>
17
+					<view class="fe-nav-avatar" @tap="openDrawer('profile')">{{ user.avatarText }}</view>
18
+				</view>
19
+			</view>
20
+		</view>
21
+
22
+		<!-- Sidebar -->
23
+		<view class="fe-sidebar" :class="{ 'fe-sidebar--open': sidebarOpen }">
24
+			<view class="sidebar-head">
25
+				<text class="sidebar-head__title">对话列表</text>
26
+				<view class="sidebar-head__add" @tap="newChat">+</view>
27
+			</view>
28
+			<scroll-view scroll-y class="sidebar-list" :show-scrollbar="false">
29
+				<view
30
+					v-for="s in sessions"
31
+					:key="s.id"
32
+					class="fe-sidebar-item"
33
+					:class="{ 'fe-sidebar-item--active': currentSession === s.id }"
34
+					@tap="switchSession(s.id)"
35
+				>
36
+					<view class="fe-sidebar-item__title">{{ s.title }}</view>
37
+					<view class="fe-sidebar-item__time">{{ s.time }}</view>
38
+				</view>
39
+			</scroll-view>
40
+		</view>
41
+
42
+		<!-- Chat Area -->
43
+		<scroll-view
44
+			class="chat-area"
45
+			scroll-y
46
+			:scroll-into-view="scrollIntoView"
47
+			:show-scrollbar="false"
48
+			:enhanced="true"
49
+			@scroll="onChatScroll"
50
+		>
51
+			<view class="chat-area__inner">
52
+				<block v-for="(msg, idx) in messages" :key="idx">
53
+					<!-- Plain text -->
54
+					<view v-if="msg.type === 'text'" class="fe-chat-msg" :class="'fe-chat-msg--' + msg.role" :id="'msg-' + idx">
55
+						<view v-if="msg.role === 'ai'" class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
56
+						<view v-if="msg.role === 'user'" class="fe-chat-avatar fe-chat-avatar--user">{{ user.avatarText }}</view>
57
+						<view class="fe-chat-bubble"><text>{{ msg.content }}</text></view>
58
+					</view>
59
+
60
+					<!-- Report card -->
61
+					<view v-else-if="msg.type === 'report'" class="fe-chat-msg fe-chat-msg--ai report-card-anchor" :id="'msg-' + idx">
62
+						<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
63
+						<view class="fe-chat-bubble">
64
+							<view class="fe-biz-card fe-biz-card--report" @tap="openSheet('report')">
65
+								<view class="fe-biz-card__header">📊 今日汇报</view>
66
+								<view class="fe-biz-card__row"><text>1. 浦东仓库</text><text class="fe-biz-card__val">8/10 人已登记</text></view>
67
+								<view class="fe-biz-card__row"><text>2. 王五实名失败</text><text class="fe-biz-card__val fe-biz-card__val--danger">已自动提醒</text></view>
68
+								<view class="fe-biz-card__row"><text>3. 结算单待确认</text><text class="fe-biz-card__val">¥12,800</text></view>
69
+								<view class="fe-biz-card__actions">
70
+									<view class="fe-biz-btn" @tap.stop="toast('已发送催办')">一键催办</view>
71
+									<view class="fe-biz-btn" @tap.stop="openSheet('progress')">查看进度</view>
72
+								</view>
73
+							</view>
74
+						</view>
75
+					</view>
76
+
77
+					<!-- Draft card -->
78
+					<view v-else-if="msg.type === 'draft'" class="fe-chat-msg fe-chat-msg--ai" :id="'msg-' + idx">
79
+						<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
80
+						<view class="fe-chat-bubble">
81
+							<view class="fe-biz-card fe-biz-card--draft" @tap="openSheet('draft')">
82
+								<view class="fe-biz-card__header">📄 用工草稿</view>
83
+								<view class="fe-biz-card__row"><text>岗位类型</text><text class="fe-biz-card__val">搬运工</text></view>
84
+								<view class="fe-biz-card__row"><text>工作地点</text><text class="fe-biz-card__val">浦东新区</text></view>
85
+								<view class="fe-biz-card__row"><text>用工人数</text><text class="fe-biz-card__val">10人 × 2天</text></view>
86
+								<view class="fe-draft-price">¥12,800 <text style="font-size:26rpx;font-weight:500;color:#64748B;">预估总价</text></view>
87
+								<view class="fe-biz-card__actions">
88
+									<view class="fe-biz-btn fe-biz-btn--primary" @tap.stop="openSheet('draft')">查看详情/修改</view>
89
+								</view>
90
+							</view>
91
+						</view>
92
+					</view>
93
+
94
+					<!-- Orders -->
95
+					<view v-else-if="msg.type === 'orders'" class="fe-chat-msg fe-chat-msg--ai" :id="'msg-' + idx">
96
+						<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
97
+						<view class="fe-chat-bubble">
98
+							<view class="fe-biz-card" style="margin-bottom:16rpx;">
99
+								<view class="fe-biz-card__header">📋 订单 #2024-001 · 活动执行</view>
100
+								<view class="fe-biz-card__row"><text>状态</text><text class="fe-biz-card__val" style="color:#10B981;">进行中</text></view>
101
+								<view class="fe-biz-card__row"><text>人数/时间</text><text class="fe-biz-card__val">10人 · 01-20至21</text></view>
102
+								<view class="fe-biz-card__row"><text>已登记</text><text class="fe-biz-card__val">7/10人</text></view>
103
+							</view>
104
+							<view class="fe-biz-card">
105
+								<view class="fe-biz-card__header">📋 订单 #2024-002 · 促销推广</view>
106
+								<view class="fe-biz-card__row"><text>状态</text><text class="fe-biz-card__val" style="color:#F59E0B;">待确认</text></view>
107
+								<view class="fe-biz-card__row"><text>人数/时间</text><text class="fe-biz-card__val">5人 · 01-25至26</text></view>
108
+							</view>
109
+						</view>
110
+					</view>
111
+
112
+					<!-- Summary bubbles -->
113
+					<view v-else-if="msg.type === 'summary1'" class="fe-summary-bubble" @tap="openSheet('draft')" :id="'msg-' + idx">
114
+						<text class="fe-summary-bubble__check">✓</text>
115
+						<text><text class="fe-summary-bubble__text">方案A已确认</text> · 合规优先 · ¥12,800</text>
116
+					</view>
117
+					<view v-else-if="msg.type === 'summary2'" class="fe-summary-bubble" @tap="openSheet('settlement')" :id="'msg-' + idx">
118
+						<text class="fe-summary-bubble__check">✓</text>
119
+						<text><text class="fe-summary-bubble__text">结算已支付</text> · ¥50,400 · BATCH-001</text>
120
+					</view>
121
+
122
+					<!-- Knowledge actions -->
123
+					<view v-else-if="msg.type === 'knowledge_actions'" class="fe-chat-msg fe-chat-msg--ai" :id="'msg-' + idx">
124
+						<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
125
+						<view class="fe-chat-bubble">
126
+							<view class="fe-quick-actions">
127
+								<view class="fe-quick-action" @tap="triggerAI('政策法规')">
128
+									<view class="fe-quick-action__icon" style="background:#DBEAFE;">📜</view>
129
+									<text class="fe-quick-action__label">政策法规</text>
130
+								</view>
131
+								<view class="fe-quick-action" @tap="triggerAI('税务处理')">
132
+									<view class="fe-quick-action__icon" style="background:#FEF3C7;">💰</view>
133
+									<text class="fe-quick-action__label">税务处理</text>
134
+								</view>
135
+								<view class="fe-quick-action" @tap="triggerAI('合同管理')">
136
+									<view class="fe-quick-action__icon" style="background:#D1FAE5;">📄</view>
137
+									<text class="fe-quick-action__label">合同管理</text>
138
+								</view>
139
+								<view class="fe-quick-action" @tap="triggerAI('社保保险')">
140
+									<view class="fe-quick-action__icon" style="background:#FEE2E2;">🛡</view>
141
+									<text class="fe-quick-action__label">社保保险</text>
142
+								</view>
143
+							</view>
144
+						</view>
145
+					</view>
146
+				</block>
147
+
148
+				<view v-if="thinking" class="fe-chat-msg fe-chat-msg--ai">
149
+					<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
150
+					<view class="fe-thinking-dots">
151
+						<view class="fe-thinking-dots__dot" />
152
+						<view class="fe-thinking-dots__dot" />
153
+						<view class="fe-thinking-dots__dot" />
154
+					</view>
155
+				</view>
156
+			</view>
157
+		</scroll-view>
158
+
159
+		<FeReportFloat :visible="showReportFloat" @open="openReportSheet" />
160
+
161
+		<!-- Footer: Quick Actions + Input -->
162
+		<view class="chat-footer">
163
+			<view class="fe-quick-actions fe-quick-actions--dock">
164
+				<view class="fe-quick-action" @tap="triggerAI('发布需求')">
165
+					<view class="fe-quick-action__icon" style="background:#FEF3C7;">➕</view>
166
+					<text class="fe-quick-action__label">发布需求</text>
167
+				</view>
168
+				<view class="fe-quick-action" @tap="openSheet('cost')">
169
+					<view class="fe-quick-action__icon" style="background:#D1FAE5;">💰</view>
170
+					<text class="fe-quick-action__label">成本测算</text>
171
+				</view>
172
+				<view class="fe-quick-action" @tap="openSheet('progress')">
173
+					<view class="fe-quick-action__icon" style="background:#DBEAFE;">📈</view>
174
+					<text class="fe-quick-action__label">进度总览</text>
175
+				</view>
176
+				<view class="fe-quick-action" @tap="openSheet('settlement')">
177
+					<view class="fe-quick-action__icon" style="background:#FEE2E2;">💳</view>
178
+					<text class="fe-quick-action__label">历史订单</text>
179
+				</view>
180
+			</view>
181
+			<view class="fe-input-bar fe-input-bar--embedded">
182
+				<view class="fe-input-bar__btn fe-input-bar__btn--voice" @tap="toast('按住说话...')">🎤</view>
183
+				<input
184
+					class="fe-input-bar__field"
185
+					v-model="inputText"
186
+					placeholder="输入需求或按住说话..."
187
+					confirm-type="send"
188
+					@input="onInput"
189
+					@confirm="sendMsg"
190
+				/>
191
+				<view class="fe-input-bar__btn fe-input-bar__btn--send" :class="{ active: canSend }" @tap="sendMsg">➤</view>
192
+			</view>
193
+		</view>
194
+
195
+		<!-- Overlay -->
196
+		<view class="fe-overlay" :class="{ 'fe-overlay--active': overlayActive, 'fe-overlay--elevated': sheetElevated }" @tap="closeAll" />
197
+
198
+		<!-- Sheets -->
199
+		<FeDraftSheet :active="activeSheet === 'draft'" @confirm="confirmPlan" />
200
+		<FeCostSheet :active="activeSheet === 'cost'" @confirm="confirmPlan" />
201
+		<FeReportSheet :active="activeSheet === 'report'" @progress="openSheet('progress')" />
202
+		<FeProgressSheet :active="activeSheet === 'progress'" :elevated="sheetElevated" />
203
+		<FeSettlementSheet :active="activeSheet === 'settlement'" @pay="showPayment" />
204
+
205
+		<!-- Drawer: Message -->
206
+		<view class="fe-side-drawer" :class="{ 'fe-side-drawer--active': activeDrawer === 'message' }">
207
+			<view class="fe-drawer-header"><view class="fe-drawer-title">🔔 消息与待办</view></view>
208
+			<scroll-view scroll-y class="fe-drawer-body" :show-scrollbar="false">
209
+				<view class="fe-drawer-body__inner">
210
+				<view v-for="(m, i) in inbox" :key="i" class="fe-msg-item">
211
+					<view style="display:flex;justify-content:space-between;margin-bottom:8rpx;">
212
+						<text class="fe-msg-type" :class="'fe-msg-type--' + m.type">{{ m.type === 'pending' ? '待办' : '异常' }}</text>
213
+						<text style="font-size:22rpx;color:#94A3B8;">{{ m.time }}</text>
214
+					</view>
215
+					<view style="font-weight:600;font-size:28rpx;">{{ m.title }}</view>
216
+					<view style="font-size:26rpx;color:#64748B;margin-top:4rpx;">{{ m.desc }}</view>
217
+				</view>
218
+				</view>
219
+			</scroll-view>
220
+		</view>
221
+
222
+		<!-- Drawer: 企业管理中枢 -->
223
+		<view class="fe-side-drawer" :class="{ 'fe-side-drawer--active': activeDrawer === 'profile' }">
224
+			<view class="fe-drawer-header"><view class="fe-drawer-title">企业管理中枢</view></view>
225
+			<scroll-view scroll-y class="fe-drawer-body" :show-scrollbar="false">
226
+				<view class="fe-drawer-body__inner">
227
+				<EnterpriseHubPanel show-logout @logout="logout" />
228
+				</view>
229
+			</scroll-view>
230
+		</view>
231
+
232
+		<!-- Modal Confirm -->
233
+		<view class="fe-modal-overlay" :class="{ 'fe-modal-overlay--active': modal === 'confirm' || modal === 'payment' || modal === 'paySuccess' }" @tap="closeModal" />
234
+		<view class="fe-modal" :class="{ 'fe-modal--active': modal === 'confirm' }">
235
+			<view class="fe-modal-body">
236
+				<view class="fe-modal-icon fe-modal-icon--success">?</view>
237
+				<view class="fe-modal-title">确认选择方案A?</view>
238
+				<view class="fe-modal-desc">合规优先 · 总支出 ¥12,800\n确认后将生成批次二维码</view>
239
+			</view>
240
+			<view class="fe-modal-footer">
241
+				<view class="fe-modal-btn fe-modal-btn--cancel" @tap="closeModal">取消</view>
242
+				<view class="fe-modal-btn fe-modal-btn--confirm" @tap="showQR">确认生成</view>
243
+			</view>
244
+		</view>
245
+
246
+		<FeBatchQrModal :active="modal === 'qr'" @close="closeModal" @progress="openQrProgress" />
247
+
248
+		<!-- Modal Payment -->
249
+		<view class="fe-modal" :class="{ 'fe-modal--active': modal === 'payment' }">
250
+			<view class="fe-modal-body">
251
+				<view class="fe-modal-title">确认支付 ¥52,800</view>
252
+				<view class="fe-modal-desc">将从企业对公账户扣款\n浦东仓库搬运 BATCH-001</view>
253
+			</view>
254
+			<view class="fe-modal-footer">
255
+				<view class="fe-modal-btn fe-modal-btn--cancel" @tap="closeModal">取消</view>
256
+				<view class="fe-modal-btn fe-modal-btn--confirm" @tap="showPaySuccess">确认支付</view>
257
+			</view>
258
+		</view>
259
+
260
+		<!-- Modal Pay Success -->
261
+		<view class="fe-modal" :class="{ 'fe-modal--active': modal === 'paySuccess' }">
262
+			<view class="fe-modal-body">
263
+				<view class="fe-modal-icon fe-modal-icon--success">✓</view>
264
+				<view class="fe-modal-title">支付成功</view>
265
+				<view class="fe-modal-desc">¥52,800 已支付\n结算单 BATCH-001</view>
266
+			</view>
267
+			<view class="fe-modal-footer">
268
+				<view class="fe-modal-btn fe-modal-btn--confirm" style="flex:1;" @tap="closeModal">完成</view>
269
+			</view>
270
+		</view>
271
+	</view>
272
+</template>
273
+
274
+<script>
275
+import EnterpriseHubPanel from '@/components/EnterpriseHubPanel.vue'
276
+import FeDraftSheet from '@/components/chat/FeDraftSheet.vue'
277
+import FeCostSheet from '@/components/chat/FeCostSheet.vue'
278
+import FeProgressSheet from '@/components/chat/FeProgressSheet.vue'
279
+import FeSettlementSheet from '@/components/chat/FeSettlementSheet.vue'
280
+import FeReportSheet from '@/components/chat/FeReportSheet.vue'
281
+import FeReportFloat from '@/components/chat/FeReportFloat.vue'
282
+import FeBatchQrModal from '@/components/chat/FeBatchQrModal.vue'
283
+import store from '@/common/store.js'
284
+import { CHAT_SESSIONS, CHAT_DATA, MESSAGES, showToast } from '@/common/chat-data.js'
285
+
286
+export default {
287
+	components: {
288
+		EnterpriseHubPanel,
289
+		FeDraftSheet,
290
+		FeCostSheet,
291
+		FeProgressSheet,
292
+		FeSettlementSheet,
293
+		FeReportSheet,
294
+		FeReportFloat,
295
+		FeBatchQrModal
296
+	},
297
+	data() {
298
+		return {
299
+			user: {},
300
+			enterprise: {},
301
+			sessions: CHAT_SESSIONS,
302
+			currentSession: 'employment',
303
+			messages: [],
304
+			inputText: '',
305
+			canSend: false,
306
+			thinking: false,
307
+			scrollIntoView: '',
308
+			sidebarOpen: false,
309
+			activeSheet: '',
310
+			activeDrawer: '',
311
+			modal: '',
312
+			inbox: MESSAGES,
313
+			reportCardVisible: true,
314
+			reportObserver: null
315
+		}
316
+	},
317
+	computed: {
318
+		overlayActive() {
319
+			return !!(this.sidebarOpen || this.activeSheet || this.activeDrawer)
320
+		},
321
+		hasReportMessage() {
322
+			return this.messages.some((m) => m.type === 'report')
323
+		},
324
+		showReportFloat() {
325
+			return this.hasReportMessage && !this.reportCardVisible
326
+		},
327
+		sheetElevated() {
328
+			return this.modal === 'qr' && this.activeSheet === 'progress'
329
+		}
330
+	},
331
+	onShow() {
332
+		const s = store.getState()
333
+		if (!s.loggedIn) {
334
+			uni.redirectTo({ url: '/pages/auth/login' })
335
+			return
336
+		}
337
+		if (s.role === 'worker') {
338
+			uni.redirectTo({ url: '/pages/worker/wallet' })
339
+			return
340
+		}
341
+		this.user = s.user
342
+		this.enterprise = s.enterprise
343
+		this.loadSession('employment')
344
+	},
345
+	onReady() {
346
+		this.setupReportObserver()
347
+	},
348
+	beforeDestroy() {
349
+		this.disconnectReportObserver()
350
+	},
351
+	methods: {
352
+		toast: showToast,
353
+		loadSession(id) {
354
+			this.currentSession = id
355
+			this.messages = JSON.parse(JSON.stringify(CHAT_DATA[id] || []))
356
+			this.reportCardVisible = true
357
+			this.scrollBottom()
358
+			this.$nextTick(() => {
359
+				this.setupReportObserver()
360
+				setTimeout(() => this.checkReportVisibility(), 120)
361
+			})
362
+		},
363
+		switchSession(id) {
364
+			this.sidebarOpen = false
365
+			this.loadSession(id)
366
+		},
367
+		newChat() {
368
+			this.sidebarOpen = false
369
+			this.loadSession('employment')
370
+			this.toast('已开启新对话')
371
+		},
372
+		scrollBottom() {
373
+			this.$nextTick(() => {
374
+				const last = this.messages.length - 1
375
+				if (last >= 0) this.scrollIntoView = 'msg-' + last
376
+				setTimeout(() => this.checkReportVisibility(), 120)
377
+			})
378
+		},
379
+		onChatScroll() {
380
+			this.checkReportVisibility()
381
+		},
382
+		setupReportObserver() {
383
+			this.disconnectReportObserver()
384
+			if (!this.hasReportMessage) return
385
+			this.$nextTick(() => {
386
+				this.reportObserver = uni.createIntersectionObserver(this, { thresholds: [0, 0.01, 1] })
387
+				this.reportObserver
388
+					.relativeTo('.chat-area', { top: 0, bottom: 0 })
389
+					.observe('.report-card-anchor', (res) => {
390
+						this.reportCardVisible = res.intersectionRatio > 0
391
+					})
392
+			})
393
+		},
394
+		disconnectReportObserver() {
395
+			if (this.reportObserver) {
396
+				this.reportObserver.disconnect()
397
+				this.reportObserver = null
398
+			}
399
+		},
400
+		checkReportVisibility() {
401
+			if (!this.hasReportMessage) {
402
+				this.reportCardVisible = true
403
+				return
404
+			}
405
+			const query = uni.createSelectorQuery().in(this)
406
+			query.select('.chat-area').boundingClientRect()
407
+			query.select('.report-card-anchor').boundingClientRect()
408
+			query.exec((res) => {
409
+				const area = res[0]
410
+				const card = res[1]
411
+				if (!area || !card) return
412
+				this.reportCardVisible = card.bottom > area.top && card.top < area.bottom
413
+			})
414
+		},
415
+		openReportSheet() {
416
+			this.openSheet('report')
417
+		},
418
+		onInput() {
419
+			this.canSend = this.inputText.trim().length > 0
420
+		},
421
+		sendMsg() {
422
+			const text = this.inputText.trim()
423
+			if (!text) return
424
+			this.messages.push({ type: 'text', role: 'user', content: text })
425
+			this.inputText = ''
426
+			this.canSend = false
427
+			this.thinking = true
428
+			this.scrollBottom()
429
+			setTimeout(() => {
430
+				this.thinking = false
431
+				if (/招|搬运|临时工/.test(text)) {
432
+					this.messages.push({ type: 'text', role: 'ai', content: '好的,已为您分析需求。正在生成用工草稿...' })
433
+					this.scrollBottom()
434
+					setTimeout(() => {
435
+						this.messages.push({ type: 'draft', role: 'ai' })
436
+						this.scrollBottom()
437
+					}, 600)
438
+				} else {
439
+					this.messages.push({ type: 'text', role: 'ai', content: '我理解您的问题,我可以帮您:发布用工需求、测算成本、查看进度、管理订单。请告诉我您具体想做什么?' })
440
+					this.scrollBottom()
441
+				}
442
+			}, 1200)
443
+		},
444
+		triggerAI(text) {
445
+			this.inputText = text
446
+			this.canSend = true
447
+			this.sendMsg()
448
+		},
449
+		openSidebar() {
450
+			if (this.sidebarOpen) {
451
+				this.sidebarOpen = false
452
+				return
453
+			}
454
+			this.activeSheet = ''
455
+			this.activeDrawer = ''
456
+			this.sidebarOpen = true
457
+		},
458
+		openSheet(name) {
459
+			this.closeAll()
460
+			setTimeout(() => { this.activeSheet = name }, 50)
461
+		},
462
+		openDrawer(name) {
463
+			this.closeAll()
464
+			setTimeout(() => { this.activeDrawer = name }, 50)
465
+		},
466
+		closeAll() {
467
+			this.sidebarOpen = false
468
+			this.activeSheet = ''
469
+			this.activeDrawer = ''
470
+		},
471
+		closeModal() {
472
+			this.modal = ''
473
+		},
474
+		confirmPlan() {
475
+			this.closeAll()
476
+			setTimeout(() => { this.modal = 'confirm' }, 400)
477
+		},
478
+		showQR() {
479
+			this.modal = 'qr'
480
+		},
481
+		openQrProgress() {
482
+			this.openSheet('progress')
483
+		},
484
+		showPayment() {
485
+			this.closeAll()
486
+			setTimeout(() => { this.modal = 'payment' }, 300)
487
+		},
488
+		showPaySuccess() {
489
+			this.modal = 'paySuccess'
490
+		},
491
+		logout() {
492
+			store.logout()
493
+			uni.redirectTo({ url: '/pages/auth/login' })
494
+		}
495
+	}
496
+}
497
+</script>
498
+
499
+<style lang="scss" scoped>
500
+.chat-page {
501
+	height: 100%;
502
+	overflow: hidden;
503
+	background: $fe-bg;
504
+}
505
+
506
+.chat-header {
507
+	position: fixed;
508
+	top: 0;
509
+	left: 0;
510
+	right: 0;
511
+	z-index: 99;
512
+	background: #ffffff;
513
+	border-bottom: 1rpx solid $fe-border-light;
514
+	box-sizing: border-box;
515
+}
516
+
517
+.chat-header__row {
518
+	height: $fe-h-page-bar;
519
+	display: flex;
520
+	flex-direction: row;
521
+	align-items: center;
522
+	flex-wrap: nowrap;
523
+	padding: 0 24rpx;
524
+	box-sizing: border-box;
525
+}
526
+
527
+.chat-header__title {
528
+	flex: 1;
529
+	min-width: 0;
530
+	margin: 0 12rpx;
531
+	font-weight: 700;
532
+	font-size: 34rpx;
533
+	color: $fe-foreground;
534
+	overflow: hidden;
535
+	text-overflow: ellipsis;
536
+	white-space: nowrap;
537
+	line-height: $fe-h-page-bar;
538
+}
539
+
540
+.chat-header__actions {
541
+	display: flex;
542
+	flex-direction: row;
543
+	align-items: center;
544
+	flex-wrap: nowrap;
545
+	flex-shrink: 0;
546
+	gap: 4rpx;
547
+}
548
+
549
+.fe-nav-icon {
550
+	font-size: 36rpx;
551
+	line-height: 1;
552
+	color: $fe-gray-500;
553
+}
554
+
555
+.fe-nav-icon--plus {
556
+	font-size: 44rpx;
557
+	font-weight: 300;
558
+	color: $fe-gray-600;
559
+}
560
+
561
+.chat-area {
562
+	position: fixed;
563
+	top: $fe-h-page-bar;
564
+	left: 0;
565
+	right: 0;
566
+	bottom: calc(#{$fe-h-input-bar} + #{$fe-h-quick-bar} + #{$fe-h-safe-bottom});
567
+	box-sizing: border-box;
568
+}
569
+
570
+.chat-footer {
571
+	position: fixed;
572
+	left: 0;
573
+	right: 0;
574
+	bottom: 0;
575
+	z-index: 99;
576
+	background: rgba(255, 255, 255, 0.92);
577
+	border-top: 1rpx solid $fe-border-light;
578
+	padding-bottom: $fe-h-safe-bottom;
579
+	box-sizing: border-box;
580
+}
581
+
582
+.fe-input-bar--embedded {
583
+	position: relative;
584
+	left: auto;
585
+	right: auto;
586
+	bottom: auto;
587
+	border-top: 1rpx solid $fe-border-light;
588
+	background: transparent;
589
+	padding-bottom: 20rpx;
590
+}
591
+
592
+.chat-area__inner {
593
+	padding: 32rpx;
594
+}
595
+
596
+.sidebar-head {
597
+	display: flex;
598
+	align-items: center;
599
+	justify-content: space-between;
600
+	padding: 28rpx 28rpx 20rpx;
601
+	border-bottom: 1rpx solid $fe-border-light;
602
+}
603
+
604
+.sidebar-head__title {
605
+	font-size: 30rpx;
606
+	font-weight: 700;
607
+}
608
+
609
+.sidebar-head__add {
610
+	width: 64rpx;
611
+	height: 64rpx;
612
+	border-radius: $fe-radius-sm;
613
+	background: $fe-primary;
614
+	color: #fff;
615
+	display: flex;
616
+	align-items: center;
617
+	justify-content: center;
618
+	font-size: 32rpx;
619
+}
620
+
621
+.sidebar-list {
622
+	height: calc(100% - 120rpx);
623
+}
624
+</style>

+ 409 - 0
huimv-employment/app/pages/employer/chat.vue

@@ -0,0 +1,409 @@
1
+<template>
2
+	<view class="ds-page chat-page">
3
+		<view class="chat-top ds-surface">
4
+			<view class="chat-top__row">
5
+				<view class="chat-top__info">
6
+					<text class="chat-top__enterprise">{{ enterprise.name }}</text>
7
+					<text class="chat-top__user">{{ user.name }} · 企业主</text>
8
+				</view>
9
+				<view class="chat-top__logout" @tap="logout">退出</view>
10
+			</view>
11
+		</view>
12
+
13
+		<scroll-view
14
+			class="chat-list"
15
+			scroll-y
16
+			:scroll-into-view="scrollIntoView"
17
+			:show-scrollbar="false"
18
+			:enhanced="true"
19
+			:bounces="false"
20
+		>
21
+			<view class="ds-container chat-list__inner">
22
+				<view class="chat-welcome ds-notice ds-notice--soft">
23
+					<text class="chat-welcome__title">草稿优先</text>
24
+					<text class="chat-welcome__text">直接描述用工需求,我会先生成办理草稿和成本方案,你再确认或修改。</text>
25
+				</view>
26
+
27
+				<view
28
+					v-for="msg in messages"
29
+					:key="msg.id"
30
+					:id="'msg-' + msg.id"
31
+					class="chat-item"
32
+					:class="'chat-item--' + msg.role"
33
+				>
34
+					<view class="chat-item__row">
35
+						<view v-if="msg.role === 'agent'" class="ds-avatar">助</view>
36
+						<view class="chat-bubble-wrap">
37
+							<text v-if="msg.role === 'agent'" class="chat-bubble__role">用工助手</text>
38
+							<view class="chat-bubble">
39
+								<text class="chat-bubble__text">{{ msg.content }}</text>
40
+								<text class="chat-bubble__time">{{ msg.time }}</text>
41
+							</view>
42
+							<view v-if="msg.showDraftAction" class="chat-action ds-card" @tap="goDraft">
43
+								<text class="chat-action__eyebrow">NEXT STEP</text>
44
+								<text class="chat-action__title">用工草稿已生成</text>
45
+								<text class="chat-action__desc">查看默认字段,补充工作内容后进入成本对比</text>
46
+								<view class="chat-action__cta">
47
+									<text>查看草稿</text>
48
+									<text class="chat-action__arrow">→</text>
49
+								</view>
50
+							</view>
51
+						</view>
52
+						<view v-if="msg.role === 'user'" class="ds-avatar ds-avatar--user">{{ user.avatarText }}</view>
53
+					</view>
54
+				</view>
55
+			</view>
56
+		</scroll-view>
57
+
58
+		<view class="chat-compose">
59
+			<scroll-view
60
+				class="chat-compose__prompts"
61
+				scroll-x
62
+				:show-scrollbar="false"
63
+				:enhanced="true"
64
+				enable-flex
65
+			>
66
+				<view class="chat-compose__prompts-track">
67
+					<view
68
+						v-for="(item, index) in quickPrompts"
69
+						:key="index"
70
+						class="chat-compose__chip"
71
+						@tap="usePrompt(item)"
72
+					>{{ item }}</view>
73
+				</view>
74
+			</scroll-view>
75
+			<view class="chat-compose__bar">
76
+				<input
77
+					class="chat-compose__input"
78
+					v-model="inputText"
79
+					placeholder="描述你的用工需求…"
80
+					confirm-type="send"
81
+					@confirm="send"
82
+				/>
83
+				<view class="chat-compose__send" @tap="send">发送</view>
84
+			</view>
85
+		</view>
86
+
87
+		<EmployerTabBar current="chat" />
88
+	</view>
89
+</template>
90
+
91
+<script>
92
+import EmployerTabBar from '@/components/EmployerTabBar.vue'
93
+import demoStore from '@/common/demo-store.js'
94
+import { requireEmployerLogin } from '@/common/auth-guard.js'
95
+import { QUICK_PROMPTS } from '@/common/demo-data.js'
96
+
97
+export default {
98
+	components: { EmployerTabBar },
99
+	data() {
100
+		return {
101
+			enterprise: {},
102
+			user: {},
103
+			messages: [],
104
+			quickPrompts: QUICK_PROMPTS,
105
+			inputText: '',
106
+			scrollIntoView: ''
107
+		}
108
+	},
109
+	onShow() {
110
+		if (!requireEmployerLogin()) return
111
+		this.enterprise = demoStore.getEnterprise()
112
+		this.user = demoStore.getUser()
113
+		this.messages = demoStore.getMessages()
114
+		this.scrollToBottom()
115
+	},
116
+	methods: {
117
+		scrollToBottom() {
118
+			this.$nextTick(() => {
119
+				const last = this.messages[this.messages.length - 1]
120
+				if (last) this.scrollIntoView = 'msg-' + last.id
121
+			})
122
+		},
123
+		send() {
124
+			const text = this.inputText.trim()
125
+			if (!text) return
126
+			this.inputText = ''
127
+			demoStore.sendMessage(text)
128
+			this.messages = demoStore.getMessages()
129
+			this.scrollToBottom()
130
+		},
131
+		usePrompt(text) {
132
+			this.inputText = text
133
+			this.send()
134
+		},
135
+		goDraft() {
136
+			uni.navigateTo({ url: '/pages/employer/draft' })
137
+		},
138
+		logout() {
139
+			demoStore.logout()
140
+			uni.redirectTo({ url: '/pages/auth/login' })
141
+		}
142
+	}
143
+}
144
+</script>
145
+
146
+<style lang="scss" scoped>
147
+.chat-page {
148
+	height: 100vh;
149
+	overflow: hidden;
150
+	position: relative;
151
+	box-sizing: border-box;
152
+}
153
+
154
+.chat-top {
155
+	position: fixed;
156
+	top: $ds-h-window-top;
157
+	left: 0;
158
+	right: 0;
159
+	z-index: 101;
160
+	height: $ds-h-chat-top;
161
+	box-sizing: border-box;
162
+	padding: 20rpx 32rpx;
163
+	border-bottom: 1rpx solid #dbeafe;
164
+	background: linear-gradient(180deg, #eff6ff 0%, #ffffff 100%);
165
+}
166
+
167
+.chat-top__row {
168
+	display: flex;
169
+	align-items: center;
170
+	justify-content: space-between;
171
+}
172
+
173
+.chat-top__enterprise {
174
+	display: block;
175
+	font-size: 30rpx;
176
+	font-weight: 600;
177
+	color: #1e40af;
178
+}
179
+
180
+.chat-top__user {
181
+	display: block;
182
+	margin-top: 4rpx;
183
+	font-size: 22rpx;
184
+	color: #737373;
185
+}
186
+
187
+.chat-top__logout {
188
+	font-size: 24rpx;
189
+	color: #737373;
190
+	padding: 12rpx 0 12rpx 24rpx;
191
+}
192
+
193
+.chat-list {
194
+	position: fixed;
195
+	top: calc(#{$ds-h-window-top} + #{$ds-h-chat-top});
196
+	left: 0;
197
+	right: 0;
198
+	width: 100%;
199
+	height: calc(100vh - #{$ds-h-window-top} - #{$ds-h-chat-top} - #{$ds-h-chat-compose} - #{$ds-h-tabbar} - #{$ds-h-safe-bottom});
200
+	box-sizing: border-box;
201
+	overflow: hidden;
202
+}
203
+
204
+.chat-list__inner {
205
+	padding-top: 24rpx;
206
+	padding-bottom: 24rpx;
207
+}
208
+
209
+.chat-welcome {
210
+	margin-bottom: 32rpx;
211
+}
212
+
213
+.chat-welcome__title {
214
+	display: block;
215
+	font-size: 26rpx;
216
+	font-weight: 600;
217
+	color: #0891b2;
218
+	margin-bottom: 8rpx;
219
+}
220
+
221
+.chat-welcome__text {
222
+	display: block;
223
+	font-size: 24rpx;
224
+	color: #525252;
225
+	line-height: 1.6;
226
+}
227
+
228
+.chat-item {
229
+	margin-bottom: 32rpx;
230
+}
231
+
232
+.chat-item__row {
233
+	display: flex;
234
+	align-items: flex-start;
235
+	gap: 16rpx;
236
+}
237
+
238
+.chat-item--user .chat-item__row {
239
+	flex-direction: row-reverse;
240
+}
241
+
242
+.chat-bubble-wrap {
243
+	flex: none;
244
+	width: calc(100% - 80rpx);
245
+	min-width: 0;
246
+}
247
+
248
+.chat-item--user .chat-bubble-wrap {
249
+	display: flex;
250
+	flex-direction: column;
251
+	align-items: flex-end;
252
+}
253
+
254
+.chat-bubble__role {
255
+	display: block;
256
+	font-size: 20rpx;
257
+	color: #a3a3a3;
258
+	margin-bottom: 8rpx;
259
+}
260
+
261
+.chat-bubble {
262
+	max-width: 100%;
263
+	padding: 20rpx 24rpx;
264
+	background: #ffffff;
265
+	border: 1rpx solid #dbeafe;
266
+	border-radius: 4rpx 16rpx 16rpx 16rpx;
267
+}
268
+
269
+.chat-item--user .chat-bubble {
270
+	background: linear-gradient(135deg, #1e40af, #2563eb);
271
+	border-color: transparent;
272
+	border-radius: 16rpx 4rpx 16rpx 16rpx;
273
+}
274
+
275
+.chat-bubble__text {
276
+	display: block;
277
+	font-size: 28rpx;
278
+	line-height: 1.65;
279
+	color: #0a0a0a;
280
+}
281
+
282
+.chat-item--user .chat-bubble__text,
283
+.chat-item--user .chat-bubble__time {
284
+	color: #ffffff;
285
+}
286
+
287
+.chat-bubble__time {
288
+	display: block;
289
+	margin-top: 10rpx;
290
+	font-size: 20rpx;
291
+	color: #a3a3a3;
292
+}
293
+
294
+.chat-action {
295
+	margin-top: 16rpx;
296
+	padding: 24rpx;
297
+	background: linear-gradient(135deg, #eff6ff 0%, #ffffff 100%);
298
+	border-color: #93c5fd;
299
+}
300
+
301
+.chat-action__eyebrow {
302
+	display: block;
303
+	font-size: 20rpx;
304
+	letter-spacing: 0.12em;
305
+	color: #2563eb;
306
+	margin-bottom: 8rpx;
307
+}
308
+
309
+.chat-action__title {
310
+	display: block;
311
+	font-size: 30rpx;
312
+	font-weight: 600;
313
+	color: #0a0a0a;
314
+}
315
+
316
+.chat-action__desc {
317
+	display: block;
318
+	margin-top: 8rpx;
319
+	font-size: 24rpx;
320
+	color: #737373;
321
+	line-height: 1.5;
322
+}
323
+
324
+.chat-action__cta {
325
+	display: flex;
326
+	align-items: center;
327
+	justify-content: space-between;
328
+	margin-top: 20rpx;
329
+	padding-top: 20rpx;
330
+	border-top: 1rpx solid #dbeafe;
331
+	font-size: 28rpx;
332
+	font-weight: 500;
333
+	color: #2563eb;
334
+}
335
+
336
+.chat-action__arrow {
337
+	font-size: 32rpx;
338
+}
339
+
340
+.chat-compose {
341
+	position: fixed;
342
+	left: 0;
343
+	right: 0;
344
+	height: $ds-h-chat-compose;
345
+	bottom: calc(#{$ds-h-tabbar} + #{$ds-h-safe-bottom});
346
+	z-index: 99;
347
+	background: #ffffff;
348
+	border-top: 1rpx solid #ececec;
349
+	box-shadow: 0 -4rpx 24rpx rgba(15, 23, 42, 0.06);
350
+	box-sizing: border-box;
351
+}
352
+
353
+.chat-compose__prompts {
354
+	width: 100%;
355
+	height: 72rpx;
356
+	box-sizing: border-box;
357
+}
358
+
359
+.chat-compose__prompts-track {
360
+	display: flex;
361
+	flex-direction: row;
362
+	flex-wrap: nowrap;
363
+	align-items: center;
364
+	gap: 12rpx;
365
+	height: 72rpx;
366
+	padding: 0 32rpx;
367
+	box-sizing: border-box;
368
+}
369
+
370
+.chat-compose__chip {
371
+	flex: none;
372
+	padding: 12rpx 20rpx;
373
+	font-size: 22rpx;
374
+	color: #1e40af;
375
+	background: #eff6ff;
376
+	border: 1rpx solid #bfdbfe;
377
+	white-space: nowrap;
378
+}
379
+
380
+.chat-compose__bar {
381
+	display: flex;
382
+	align-items: center;
383
+	gap: 16rpx;
384
+	padding: 16rpx 32rpx 20rpx;
385
+}
386
+
387
+.chat-compose__input {
388
+	flex: none;
389
+	width: calc(100% - 136rpx);
390
+	height: 80rpx;
391
+	padding: 0 24rpx;
392
+	font-size: 28rpx;
393
+	background: #f8fafc;
394
+	border: 1rpx solid #cbd5e1;
395
+	box-sizing: border-box;
396
+}
397
+
398
+.chat-compose__send {
399
+	flex: none;
400
+	width: 120rpx;
401
+	height: 80rpx;
402
+	line-height: 80rpx;
403
+	text-align: center;
404
+	font-size: 28rpx;
405
+	font-weight: 500;
406
+	color: #ffffff;
407
+	background: linear-gradient(135deg, #1e40af, #2563eb);
408
+}
409
+</style>

+ 52 - 0
huimv-employment/app/pages/index/index.vue

@@ -0,0 +1,52 @@
1
+<template>
2
+	<view class="content">
3
+		<image class="logo" src="/static/logo.png"></image>
4
+		<view class="text-area">
5
+			<text class="title">{{title}}</text>
6
+		</view>
7
+	</view>
8
+</template>
9
+
10
+<script>
11
+	export default {
12
+		data() {
13
+			return {
14
+				title: 'Hello'
15
+			}
16
+		},
17
+		onLoad() {
18
+
19
+		},
20
+		methods: {
21
+
22
+		}
23
+	}
24
+</script>
25
+
26
+<style>
27
+	.content {
28
+		display: flex;
29
+		flex-direction: column;
30
+		align-items: center;
31
+		justify-content: center;
32
+	}
33
+
34
+	.logo {
35
+		height: 200rpx;
36
+		width: 200rpx;
37
+		margin-top: 200rpx;
38
+		margin-left: auto;
39
+		margin-right: auto;
40
+		margin-bottom: 50rpx;
41
+	}
42
+
43
+	.text-area {
44
+		display: flex;
45
+		justify-content: center;
46
+	}
47
+
48
+	.title {
49
+		font-size: 36rpx;
50
+		color: #8f8f94;
51
+	}
52
+</style>

+ 101 - 0
huimv-employment/app/pages/worker/profile.vue

@@ -0,0 +1,101 @@
1
+<template>
2
+	<view class="fe-page fe-page--tab">
3
+		<scroll-view scroll-y class="page-scroll" :show-scrollbar="false">
4
+			<view class="page-body">
5
+				<view class="fe-profile-card">
6
+					<view class="fe-profile-avatar-lg">{{ avatarText }}</view>
7
+					<view class="worker-profile__name">{{ worker.name }}</view>
8
+					<view class="worker-profile__phone">{{ worker.phone }}</view>
9
+					<view class="worker-profile__badge">✓ 已实名核验</view>
10
+				</view>
11
+
12
+				<view class="fe-btn fe-btn--secondary fe-btn--lg fe-btn--full logout-btn" @tap="logout">退出账号</view>
13
+			</view>
14
+		</scroll-view>
15
+
16
+		<FeBottomNav current="mine" />
17
+	</view>
18
+</template>
19
+
20
+<script>
21
+import FeBottomNav from '@/components/FeBottomNav.vue'
22
+import store from '@/common/store.js'
23
+
24
+export default {
25
+	components: { FeBottomNav },
26
+	data() {
27
+		return {
28
+			worker: {},
29
+			avatarText: ''
30
+		}
31
+	},
32
+	onShow() {
33
+		if (!store.getState().loggedIn || store.getState().role !== 'worker') {
34
+			uni.redirectTo({ url: '/pages/auth/login' })
35
+			return
36
+		}
37
+		if (!store.isWorkerRegistered()) {
38
+			uni.redirectTo({ url: '/pages/worker/register' })
39
+			return
40
+		}
41
+		this.worker = store.getState().worker
42
+		this.avatarText = this.worker.name ? this.worker.name[0] : '工'
43
+	},
44
+	methods: {
45
+		logout() {
46
+			store.logout()
47
+			uni.redirectTo({ url: '/pages/auth/login' })
48
+		}
49
+	}
50
+}
51
+</script>
52
+
53
+<style lang="scss" scoped>
54
+.fe-page--tab {
55
+	display: flex;
56
+	flex-direction: column;
57
+	height: 100%;
58
+	min-height: 100vh;
59
+	box-sizing: border-box;
60
+	padding-bottom: calc(#{$fe-h-tabbar} + #{$fe-h-safe-bottom});
61
+}
62
+
63
+.page-scroll {
64
+	flex: 1;
65
+	height: 0;
66
+}
67
+
68
+.page-body {
69
+	padding: 32rpx;
70
+	padding-bottom: 48rpx;
71
+}
72
+
73
+.worker-profile__name {
74
+	font-size: 32rpx;
75
+	font-weight: 700;
76
+}
77
+
78
+.worker-profile__phone {
79
+	font-size: 26rpx;
80
+	color: #64748B;
81
+	margin-top: 4rpx;
82
+}
83
+
84
+.worker-profile__badge {
85
+	display: inline-flex;
86
+	align-items: center;
87
+	gap: 8rpx;
88
+	padding: 6rpx 20rpx;
89
+	border-radius: 9999rpx;
90
+	background: #D1FAE5;
91
+	color: #065F46;
92
+	font-size: 24rpx;
93
+	font-weight: 600;
94
+	margin-top: 16rpx;
95
+}
96
+
97
+.logout-btn {
98
+	margin-top: 48rpx;
99
+	color: $fe-danger !important;
100
+}
101
+</style>

+ 174 - 0
huimv-employment/app/pages/worker/register.vue

@@ -0,0 +1,174 @@
1
+<template>
2
+	<view class="fe-page">
3
+		<FeMpNavBar title="临时工登记" @back="goBack" />
4
+
5
+		<scroll-view scroll-y class="page-scroll" :show-scrollbar="false">
6
+			<view class="page-body">
7
+			<view class="reg-card">
8
+				<view class="reg-head">
9
+					<view class="reg-head__icon">👤</view>
10
+					<text class="reg-head__desc">请填写您的个人信息完成登记</text>
11
+				</view>
12
+
13
+				<view class="fe-form-group">
14
+					<text class="fe-form-label">真实姓名 <text style="color:#EF4444;">*</text></text>
15
+					<input class="fe-form-input" v-model="form.name" placeholder="请输入身份证上的姓名" />
16
+				</view>
17
+				<view class="fe-form-group">
18
+					<text class="fe-form-label">身份证号 <text style="color:#EF4444;">*</text></text>
19
+					<input class="fe-form-input" placeholder="请输入18位身份证号码" />
20
+				</view>
21
+				<view class="fe-form-group">
22
+					<text class="fe-form-label">手机号 <text style="color:#EF4444;">*</text></text>
23
+					<input class="fe-form-input" type="number" v-model="form.phone" placeholder="请输入手机号" />
24
+				</view>
25
+				<view class="fe-form-group">
26
+					<text class="fe-form-label">验证码 <text style="color:#EF4444;">*</text></text>
27
+					<input class="fe-form-input" placeholder="验证码" />
28
+				</view>
29
+				<view class="fe-form-group">
30
+					<text class="fe-form-label">银行卡号 <text style="color:#EF4444;">*</text></text>
31
+					<input class="fe-form-input" placeholder="请输入银行卡号" />
32
+				</view>
33
+				<view class="fe-form-group">
34
+					<text class="fe-form-label">开户银行 <text style="color:#EF4444;">*</text></text>
35
+					<input class="fe-form-input" placeholder="请输入开户银行" />
36
+				</view>
37
+				<view class="fe-form-group">
38
+					<text class="fe-form-label">支行名称</text>
39
+					<input class="fe-form-input" placeholder="请输入支行名称" />
40
+				</view>
41
+
42
+				<label class="reg-agree">
43
+					<checkbox :checked="agreed" @tap="agreed = !agreed" color="#7C3AED" />
44
+					<text>我已阅读并同意 《电子劳动合同》 和 《隐私政策》</text>
45
+				</label>
46
+
47
+				<view class="fe-btn fe-btn--primary fe-btn--lg fe-btn--full" @tap="submit">✓ 提交登记</view>
48
+				<text class="reg-note">您的信息将被加密存储,仅用于本次用工服务</text>
49
+			</view>
50
+			</view>
51
+		</scroll-view>
52
+	</view>
53
+</template>
54
+
55
+<script>
56
+import FeMpNavBar from '@/components/FeMpNavBar.vue'
57
+import store from '@/common/store.js'
58
+import { showToast } from '@/common/chat-data.js'
59
+
60
+export default {
61
+	components: { FeMpNavBar },
62
+	data() {
63
+		return {
64
+			agreed: false,
65
+			form: { name: '', phone: '' }
66
+		}
67
+	},
68
+	onShow() {
69
+		const s = store.getState()
70
+		if (!s.loggedIn || s.role !== 'worker') {
71
+			uni.redirectTo({ url: '/pages/auth/login' })
72
+			return
73
+		}
74
+		if (store.isWorkerRegistered()) {
75
+			uni.redirectTo({ url: '/pages/worker/wallet' })
76
+		}
77
+	},
78
+	methods: {
79
+		goBack() {
80
+			store.logout()
81
+			uni.redirectTo({ url: '/pages/auth/login' })
82
+		},
83
+		submit() {
84
+			if (!this.form.name.trim()) {
85
+				showToast('请填写真实姓名')
86
+				return
87
+			}
88
+			if (!this.form.phone.trim()) {
89
+				showToast('请填写手机号')
90
+				return
91
+			}
92
+			if (!this.agreed) {
93
+				showToast('请先同意协议')
94
+				return
95
+			}
96
+			store.registerWorker({
97
+				name: this.form.name.trim(),
98
+				phone: this.form.phone.trim()
99
+			})
100
+			showToast('登记成功!')
101
+			setTimeout(() => {
102
+				uni.redirectTo({ url: '/pages/worker/wallet' })
103
+			}, 800)
104
+		}
105
+	}
106
+}
107
+</script>
108
+
109
+<style lang="scss" scoped>
110
+.fe-page {
111
+	display: flex;
112
+	flex-direction: column;
113
+	height: 100vh;
114
+	overflow: hidden;
115
+	box-sizing: border-box;
116
+}
117
+
118
+.page-scroll {
119
+	flex: 1;
120
+	height: 0;
121
+}
122
+
123
+.page-body {
124
+	padding: 32rpx 40rpx 40rpx;
125
+}
126
+
127
+.reg-card {
128
+	background: #fff;
129
+	border-radius: $fe-radius-xl;
130
+	padding: 48rpx;
131
+	box-shadow: 0 20rpx 30rpx rgba(0, 0, 0, 0.08);
132
+}
133
+
134
+.reg-head {
135
+	text-align: center;
136
+	margin-bottom: 48rpx;
137
+}
138
+
139
+.reg-head__icon {
140
+	width: 128rpx;
141
+	height: 128rpx;
142
+	border-radius: 50%;
143
+	background: $fe-primary-bg;
144
+	margin: 0 auto 24rpx;
145
+	display: flex;
146
+	align-items: center;
147
+	justify-content: center;
148
+	font-size: 56rpx;
149
+}
150
+
151
+.reg-head__desc {
152
+	display: block;
153
+	font-size: 28rpx;
154
+	color: $fe-muted;
155
+	margin-top: 8rpx;
156
+}
157
+
158
+.reg-agree {
159
+	display: flex;
160
+	align-items: flex-start;
161
+	gap: 16rpx;
162
+	margin-bottom: 32rpx;
163
+	font-size: 26rpx;
164
+	color: $fe-gray-600;
165
+}
166
+
167
+.reg-note {
168
+	display: block;
169
+	text-align: center;
170
+	margin-top: 24rpx;
171
+	font-size: 24rpx;
172
+	color: $fe-gray-400;
173
+}
174
+</style>

+ 142 - 0
huimv-employment/app/pages/worker/wallet.vue

@@ -0,0 +1,142 @@
1
+<template>
2
+	<view class="fe-page fe-page--tab">
3
+		<scroll-view scroll-y class="page-scroll" :show-scrollbar="false">
4
+			<view class="page-body">
5
+			<view class="fe-wallet-header">
6
+				<view class="wallet-header__top">
7
+					<view>
8
+						<view style="font-size:36rpx;font-weight:700;">{{ worker.name }}</view>
9
+						<view style="font-size:26rpx;opacity:0.8;margin-top:4rpx;">{{ worker.phone }}</view>
10
+					</view>
11
+					<view class="wallet-refresh" @tap="toast('已刷新')">↻</view>
12
+				</view>
13
+				<view style="display:inline-flex;align-items:center;gap:8rpx;padding:6rpx 20rpx;border-radius:9999rpx;background:rgba(255,255,255,0.2);font-size:24rpx;font-weight:600;margin-top:16rpx;">✓ 已实名核验</view>
14
+				<view style="margin-top:32rpx;">
15
+					<view style="font-size:26rpx;opacity:0.8;margin-bottom:8rpx;">累计薪资(元)</view>
16
+					<view class="fe-wallet-balance-amount">¥{{ salaryDisplay }}</view>
17
+				</view>
18
+			</view>
19
+
20
+			<view class="fe-wallet-section">
21
+				<view style="font-size:28rpx;font-weight:700;padding:28rpx 32rpx 16rpx;">💼 我的钱包</view>
22
+				<view class="fe-wallet-menu-item" @tap="toast('电子合同详情')">
23
+					<view class="fe-profile-menu-icon" style="background:#DBEAFE;">📄</view>
24
+					<view style="flex:1;"><view class="fe-profile-menu-title">电子合同</view><view class="fe-profile-menu-desc">2份已签署</view></view>
25
+					<text style="font-size:22rpx;font-weight:700;padding:4rpx 16rpx;border-radius:9999rpx;background:#F5F3FF;color:#7C3AED;">2份</text>
26
+					<text style="color:#94A3B8;">›</text>
27
+				</view>
28
+				<view class="fe-wallet-menu-item" @tap="toast('薪资明细详情')">
29
+					<view class="fe-profile-menu-icon" style="background:#D1FAE5;">💰</view>
30
+					<view style="flex:1;"><view class="fe-profile-menu-title">薪资明细</view><view class="fe-profile-menu-desc">累计 ¥3,500</view></view>
31
+					<text style="color:#94A3B8;">›</text>
32
+				</view>
33
+				<view class="fe-wallet-menu-item" @tap="toast('完税证明下载')">
34
+					<view class="fe-profile-menu-icon" style="background:#FEF3C7;">📋</view>
35
+					<view style="flex:1;"><view class="fe-profile-menu-title">完税证明</view><view class="fe-profile-menu-desc">2024年度</view></view>
36
+					<text style="color:#94A3B8;">›</text>
37
+				</view>
38
+			</view>
39
+
40
+			<view class="fe-wallet-section">
41
+				<view style="font-size:28rpx;font-weight:700;padding:28rpx 32rpx 16rpx;">📋 我的做工记录</view>
42
+				<view v-for="r in records" :key="r.title" class="fe-wallet-record">
43
+					<view style="width:64rpx;height:64rpx;border-radius:50%;background:#D1FAE5;display:flex;align-items:center;justify-content:center;color:#10B981;">✓</view>
44
+					<view style="flex:1;"><view style="font-weight:600;">{{ r.title }}</view><view style="font-size:24rpx;color:#64748B;">{{ r.meta }}</view></view>
45
+					<view style="font-size:30rpx;font-weight:700;color:#10B981;">{{ r.amount }}</view>
46
+				</view>
47
+			</view>
48
+
49
+			<view class="fe-wallet-help">
50
+				<view style="font-size:28rpx;font-weight:700;">💡 有劳资纠纷或疑问?</view>
51
+				<view class="fe-btn fe-btn--primary fe-btn--sm" style="margin-top:16rpx;" @tap="toast('已转接平台客服')">联系平台客服</view>
52
+			</view>
53
+			</view>
54
+		</scroll-view>
55
+
56
+		<FeBottomNav current="home" />
57
+	</view>
58
+</template>
59
+
60
+<script>
61
+import FeBottomNav from '@/components/FeBottomNav.vue'
62
+import store from '@/common/store.js'
63
+import { WALLET_RECORDS, showToast } from '@/common/chat-data.js'
64
+
65
+export default {
66
+	components: { FeBottomNav },
67
+	data() {
68
+		return {
69
+			worker: {},
70
+			records: WALLET_RECORDS,
71
+			salaryDisplay: '0'
72
+		}
73
+	},
74
+	onShow() {
75
+		const s = store.getState()
76
+		if (!s.loggedIn || s.role !== 'worker') {
77
+			uni.redirectTo({ url: '/pages/auth/login' })
78
+			return
79
+		}
80
+		if (!store.isWorkerRegistered()) {
81
+			uni.redirectTo({ url: '/pages/worker/register' })
82
+			return
83
+		}
84
+		this.worker = s.worker
85
+		this.animateSalary()
86
+	},
87
+	methods: {
88
+		toast: showToast,
89
+		animateSalary() {
90
+			const target = 3500
91
+			const steps = 30
92
+			let step = 0
93
+			const timer = setInterval(() => {
94
+				step++
95
+				const val = Math.round((step / steps) * target)
96
+				this.salaryDisplay = val.toLocaleString()
97
+				if (step >= steps) clearInterval(timer)
98
+			}, 40)
99
+		}
100
+	}
101
+}
102
+</script>
103
+
104
+<style lang="scss" scoped>
105
+.fe-page--tab {
106
+	display: flex;
107
+	flex-direction: column;
108
+	height: 100%;
109
+	min-height: 100vh;
110
+	box-sizing: border-box;
111
+	padding-bottom: calc(#{$fe-h-tabbar} + #{$fe-h-safe-bottom});
112
+}
113
+
114
+.page-scroll {
115
+	flex: 1;
116
+	height: 0;
117
+}
118
+
119
+.page-body {
120
+	padding: 32rpx;
121
+	padding-bottom: 48rpx;
122
+}
123
+
124
+.wallet-header__top {
125
+	display: flex;
126
+	align-items: flex-start;
127
+	justify-content: space-between;
128
+	gap: 24rpx;
129
+}
130
+
131
+.wallet-refresh {
132
+	width: 72rpx;
133
+	height: 72rpx;
134
+	border-radius: 50%;
135
+	background: rgba(255, 255, 255, 0.2);
136
+	display: flex;
137
+	align-items: center;
138
+	justify-content: center;
139
+	font-size: 36rpx;
140
+	flex-shrink: 0;
141
+}
142
+</style>

BIN
huimv-employment/app/static/logo.png


+ 13 - 0
huimv-employment/app/uni.promisify.adaptor.js

@@ -0,0 +1,13 @@
1
+uni.addInterceptor({
2
+  returnValue (res) {
3
+    if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
4
+      return res;
5
+    }
6
+    return new Promise((resolve, reject) => {
7
+      res.then((res) => {
8
+        if (!res) return resolve(res) 
9
+        return res[0] ? reject(res[0]) : resolve(res[1])
10
+      });
11
+    });
12
+  },
13
+});

+ 56 - 0
huimv-employment/app/uni.scss

@@ -0,0 +1,56 @@
1
+/**
2
+ * 灵活用工小程序 — 设计令牌(对齐 flexible-employment-demo.html)
3
+ */
4
+
5
+$fe-primary: #7C3AED;
6
+$fe-primary-light: #A78BFA;
7
+$fe-primary-dark: #5B21B6;
8
+$fe-primary-bg: #F5F3FF;
9
+$fe-primary-100: #EDE9FE;
10
+$fe-secondary: #6366F1;
11
+$fe-accent: #EC4899;
12
+$fe-success: #10B981;
13
+$fe-success-light: #D1FAE5;
14
+$fe-warning: #F59E0B;
15
+$fe-warning-light: #FEF3C7;
16
+$fe-danger: #EF4444;
17
+$fe-danger-light: #FEE2E2;
18
+$fe-info: #3B82F6;
19
+$fe-info-light: #DBEAFE;
20
+$fe-avatar-user-from: #3B82F6;
21
+$fe-avatar-user-to: #0EA5E9;
22
+$fe-bg: #FAF5FF;
23
+$fe-surface: #FFFFFF;
24
+$fe-foreground: #0F172A;
25
+$fe-muted: #64748B;
26
+$fe-muted-light: #94A3B8;
27
+$fe-border: #E2E8F0;
28
+$fe-border-light: #F1F5F9;
29
+$fe-gray-50: #F9FAFB;
30
+$fe-gray-100: #F3F4F6;
31
+$fe-gray-200: #E5E7EB;
32
+$fe-gray-300: #D1D5DB;
33
+$fe-gray-400: #9CA3AF;
34
+$fe-gray-500: #6B7280;
35
+$fe-gray-600: #4B5563;
36
+$fe-gray-700: #374151;
37
+$fe-gray-800: #1F2937;
38
+$fe-gray-900: #111827;
39
+
40
+$fe-radius-sm: 12rpx;
41
+$fe-radius-md: 20rpx;
42
+$fe-radius-lg: 28rpx;
43
+$fe-radius-xl: 40rpx;
44
+$fe-radius-full: 9999rpx;
45
+
46
+$fe-h-page-bar: 112rpx;
47
+$fe-h-input-bar: 128rpx;
48
+$fe-h-quick-bar: 112rpx;
49
+$fe-h-tabbar: 108rpx;
50
+$fe-h-safe-bottom: env(safe-area-inset-bottom);
51
+
52
+$uni-color-primary: $fe-primary;
53
+$uni-bg-color-grey: $fe-bg;
54
+$uni-text-color: $fe-foreground;
55
+$uni-text-color-grey: $fe-muted;
56
+$uni-border-color: $fe-border;

+ 0 - 8
huimv-employment/fe-miniapp/README.md

@@ -1,8 +0,0 @@
1
-# fe-miniapp
2
-
3
-微信小程序源码目录(**非 Maven 模块**)。
4
-
5
-- API 前缀:`/api/v1/mp/**`
6
-- 技术栈建议:微信原生 + TypeScript + TDesign / Vant
7
-
8
-后续在此目录初始化小程序工程。