瀏覽代碼

feat: add field permissions configuration page and navigation

Yannay 2 周之前
父節點
當前提交
3feb61f9ab
共有 6 個文件被更改,包括 443 次插入1 次删除
  1. 8 0
      pages.json
  2. 407 0
      pages/wareHouse/fieldPermissions.vue
  3. 6 0
      pages/wareHouse/index.vue
  4. 2 1
      store/getters.js
  5. 12 0
      store/modules/user.js
  6. 8 0
      utils/api.js

+ 8 - 0
pages.json

@@ -198,6 +198,14 @@
198 198
 			}
199 199
 		},
200 200
 		{
201
+			"path": "pages/wareHouse/fieldPermissions",
202
+			"style": {
203
+				"navigationBarTitleText": "字段权限",
204
+				"enablePullDownRefresh": true,
205
+				"navigationStyle": "custom"
206
+			}
207
+		},
208
+		{
201 209
 			"path": "pages/privateClue/index",
202 210
 			"style": {
203 211
 				"navigationBarTitleText": "销售线索",

+ 407 - 0
pages/wareHouse/fieldPermissions.vue

@@ -0,0 +1,407 @@
1
+<template>
2
+	<view class="page">
3
+		<u-navbar class="nav-bar" title="字段权限配置" :autoBack="true" :placeholder="true" v-hideNav></u-navbar>
4
+		<view class="content">
5
+			<view class="tip-card">
6
+				<u-icon name="setting" size="18" color="#108cff"></u-icon>
7
+				<text class="tip-text">为指定角色配置仓库各字段的「可读」「可编辑」权限,保存后该角色用户将按此配置在列表/详情中看到并可编辑对应字段。</text>
8
+			</view>
9
+
10
+			<!-- 选择角色 -->
11
+			<view class="section role-section">
12
+				<view class="section-title">选择角色</view>
13
+				<view v-if="roleList.length > 0" class="role-select-wrap">
14
+					<picker :value="roleIndex" :range="roleList" range-key="roleName" @change="onRoleChange">
15
+						<view class="picker-inner">
16
+							<text class="picker-text">{{ currentRoleName }}</text>
17
+							<u-icon name="arrow-down" size="14" color="#999"></u-icon>
18
+						</view>
19
+					</picker>
20
+				</view>
21
+				<view v-else class="role-input-wrap">
22
+					<view class="input-row">
23
+						<text class="input-label">角色ID</text>
24
+						<u--input v-model="formRoleId" type="number" placeholder="请输入角色ID" border="surround"></u--input>
25
+					</view>
26
+					<view class="input-row">
27
+						<text class="input-label">角色Key</text>
28
+						<u--input v-model="formRoleKey" placeholder="如 WAREHOUSER" border="surround"></u--input>
29
+					</view>
30
+				</view>
31
+				<view class="btn-row">
32
+					<u-button type="primary" size="small" :loading="loadConfigLoading" @click="loadConfig">加载配置</u-button>
33
+					<u-button type="error" plain size="small" :disabled="!currentRoleId" @click="clearConfig">清空该角色配置</u-button>
34
+				</view>
35
+			</view>
36
+
37
+			<!-- 字段权限列表(可编辑) -->
38
+			<view class="section">
39
+				<view class="section-head">
40
+					<text class="section-title">字段权限</text>
41
+					<u-button type="primary" size="mini" :loading="saveLoading" :disabled="!currentRoleId || permissionList.length === 0" @click="saveConfig">保存配置</u-button>
42
+				</view>
43
+				<view v-if="loadConfigLoading" class="loading-wrap">
44
+					<u-loading-icon mode="circle" size="36"></u-loading-icon>
45
+					<text class="loading-text">加载中...</text>
46
+				</view>
47
+				<scroll-view v-else class="list-wrap" scroll-y enable-back-to-top>
48
+					<view class="field-list">
49
+						<view v-for="(item, index) in permissionList" :key="item.fieldName" class="field-item">
50
+							<view class="field-name-wrap">
51
+								<text class="field-name">{{ fieldLabel(item.fieldName) }}</text>
52
+								<text class="field-key">{{ item.fieldName }}</text>
53
+							</view>
54
+							<view class="field-switches">
55
+								<view class="switch-cell">
56
+									<text class="switch-label">读</text>
57
+									<u-switch v-model="item.read" size="20" active-color="#108cff"></u-switch>
58
+								</view>
59
+								<view class="switch-cell">
60
+									<text class="switch-label">编辑</text>
61
+									<u-switch v-model="item.edit" size="20" active-color="#09bb07"></u-switch>
62
+								</view>
63
+							</view>
64
+						</view>
65
+					</view>
66
+					<view v-if="permissionList.length === 0 && !loadConfigLoading" class="empty-wrap">
67
+						<u-empty text="请先选择角色并点击「加载配置」" mode="list"></u-empty>
68
+					</view>
69
+				</scroll-view>
70
+			</view>
71
+		</view>
72
+	</view>
73
+</template>
74
+
75
+<script>
76
+const FIELD_LABELS = {
77
+	dictLabel: '品牌/商品名', dictValue: '品牌ID', model: '型号', code: '独立编码', warehouseDate: '入库日期',
78
+	payType: '付款方式', note: '备注', originalCost: '原始成本', additionalCost: '附加成本', agentPrice: '代理价格',
79
+	suggestedPrice: '建议价格', costPrice: '成本价', salesPrice: '销售价', peerPrice: '同行价', actualPrice: '实价',
80
+	stock: '库存数量', location: '商品位置', recyclePerson: '回收人员', recyclePersonId: '回收人员ID',
81
+	identifyingPerson: '鉴定人员', identifyingPersonId: '鉴定人员ID', lockStatus: '锁单状态', downStatus: '下架状态',
82
+	imgsUrl: '图片', goodPicFileList: '商品主图', goodPicHash: '主图哈希', category: '品类', recycleTime: '回收时间',
83
+	productAttribute: '产品属性', desc: '描述', series: '系列', dialType: '机芯类型', caseMaterial: '表壳材质',
84
+	dialDiameter: '表盘直径', material: '材质', size: '尺寸', yardage: '尺码', price: '官方指导价',
85
+	productCondition: '商品成色', detailPicFileList: '细节图', title: '商品标题', productNo: '商品货号', watchYear: '手表年份',
86
+	recycleType: '回收类型', recycleSituation: '回收情况', recycleBottomDesc: '回收留底描述', recycleBottomFileList: '回收留底图',
87
+	targetAudience: '适用人群', productCard: '商品保卡', cardYear: '保卡年份', cardDate: '保卡日期',
88
+	productCardPicFileList: '保卡图片', productTag: '商品标签', productAttachment: '商品附件', productDesc: '商品备注',
89
+	productDescPicFileList: '备注图片', continuousWarehousing: '连续入库', label: '标签', stockStatus: '库存状态',
90
+	origin: '来源', delFlag: '删除标志',
91
+};
92
+
93
+export default {
94
+	data() {
95
+		return {
96
+			roleList: [],
97
+			roleIndex: 0,
98
+			formRoleId: '',
99
+			formRoleKey: '',
100
+			loadConfigLoading: false,
101
+			saveLoading: false,
102
+			permissionList: [],
103
+		};
104
+	},
105
+	computed: {
106
+		currentRoleId() {
107
+			if (this.roleList.length > 0) {
108
+				const r = this.roleList[this.roleIndex];
109
+				return r ? r.roleId : null;
110
+			}
111
+			const id = parseInt(this.formRoleId, 10);
112
+			return isNaN(id) ? null : id;
113
+		},
114
+		currentRoleKey() {
115
+			if (this.roleList.length > 0) {
116
+				const r = this.roleList[this.roleIndex];
117
+				return r ? r.roleKey || '' : '';
118
+			}
119
+			return this.formRoleKey || '';
120
+		},
121
+		currentRoleName() {
122
+			if (this.roleList.length > 0) {
123
+				const r = this.roleList[this.roleIndex];
124
+				return r ? (r.roleName || '请选择角色') : '请选择角色';
125
+			}
126
+			return this.currentRoleId ? `角色ID: ${this.currentRoleId}` : '请填写角色ID并加载';
127
+		},
128
+	},
129
+	onLoad() {},
130
+	onShow() {
131
+		this.fetchRoleList();
132
+	},
133
+	methods: {
134
+		fieldLabel(name) {
135
+			return FIELD_LABELS[name] || name;
136
+		},
137
+		fetchRoleList() {
138
+			uni.$u.api.getRoleOptionSelect({ custom: { loading: false } })
139
+				.then((res) => {
140
+					const list = (res && res.data) ? (Array.isArray(res.data) ? res.data : []) : [];
141
+					this.roleList = list.map((r) => ({ roleId: r.roleId, roleName: r.roleName || r.roleKey || '', roleKey: r.roleKey || '' }));
142
+					this.roleIndex = 0;
143
+				})
144
+				.catch(() => {
145
+					this.roleList = [];
146
+				});
147
+		},
148
+		onRoleChange(e) {
149
+			this.roleIndex = e.detail.value;
150
+			this.permissionList = [];
151
+		},
152
+		loadConfig() {
153
+			const roleId = this.currentRoleId;
154
+			if (roleId == null) {
155
+				uni.$u.toast('请选择或填写角色');
156
+				return;
157
+			}
158
+			this.loadConfigLoading = true;
159
+			uni.$u.api.wareHouseFieldPermissionsConfigGet({ roleId })
160
+				.then((res) => {
161
+					if (res && res.data && Array.isArray(res.data)) {
162
+						this.permissionList = res.data.map((p) => ({
163
+							fieldName: p.fieldName,
164
+							read: !!p.read,
165
+							edit: !!p.edit,
166
+						}));
167
+					} else {
168
+						this.permissionList = [];
169
+					}
170
+				})
171
+				.catch(() => {
172
+					this.permissionList = [];
173
+					uni.$u.toast('加载失败');
174
+				})
175
+				.finally(() => {
176
+					this.loadConfigLoading = false;
177
+				});
178
+		},
179
+		saveConfig() {
180
+			const roleId = this.currentRoleId;
181
+			const roleKey = this.currentRoleKey;
182
+			if (roleId == null) {
183
+				uni.$u.toast('请选择或填写角色');
184
+				return;
185
+			}
186
+			if (!this.permissionList.length) {
187
+				uni.$u.toast('请先加载配置');
188
+				return;
189
+			}
190
+			this.saveLoading = true;
191
+			uni.$u.api.wareHouseFieldPermissionsConfigSave({
192
+				roleId,
193
+				roleKey,
194
+				permissions: this.permissionList.map((p) => ({ fieldName: p.fieldName, read: p.read, edit: p.edit })),
195
+			})
196
+				.then(() => {
197
+					uni.$u.toast('保存成功');
198
+				})
199
+				.catch(() => {
200
+					uni.$u.toast('保存失败');
201
+				})
202
+				.finally(() => {
203
+					this.saveLoading = false;
204
+				});
205
+		},
206
+		clearConfig() {
207
+			if (!this.currentRoleId) return;
208
+			uni.showModal({
209
+				title: '确认清空',
210
+				content: '将删除该角色下所有字段权限配置,确定吗?',
211
+				success: (res) => {
212
+					if (res.confirm) {
213
+						uni.$u.api.wareHouseFieldPermissionsConfigDeleteByRole(this.currentRoleId).then(() => {
214
+							uni.$u.toast('已清空');
215
+							this.permissionList = [];
216
+						}).catch(() => uni.$u.toast('清空失败'));
217
+					}
218
+				},
219
+			});
220
+		},
221
+	},
222
+};
223
+</script>
224
+
225
+<style lang="scss" scoped>
226
+.page {
227
+	min-height: 100vh;
228
+	background: #f5f6f8;
229
+	display: flex;
230
+	flex-direction: column;
231
+}
232
+
233
+.nav-bar {
234
+	border-bottom: 1px solid #eee;
235
+}
236
+
237
+.content {
238
+	flex: 1;
239
+	display: flex;
240
+	flex-direction: column;
241
+	padding: 24rpx;
242
+	box-sizing: border-box;
243
+}
244
+
245
+.tip-card {
246
+	display: flex;
247
+	align-items: flex-start;
248
+	gap: 12rpx;
249
+	background: #e8f4ff;
250
+	border-radius: 16rpx;
251
+	padding: 20rpx 24rpx;
252
+	margin-bottom: 24rpx;
253
+	border: 1px solid rgba(16, 140, 255, 0.2);
254
+
255
+	.tip-text {
256
+		font-size: 26rpx;
257
+		color: #333;
258
+		line-height: 1.5;
259
+		flex: 1;
260
+	}
261
+}
262
+
263
+.section {
264
+	background: #fff;
265
+	border-radius: 16rpx;
266
+	padding: 24rpx;
267
+	margin-bottom: 24rpx;
268
+	box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
269
+}
270
+
271
+.section-title {
272
+	font-size: 30rpx;
273
+	font-weight: 600;
274
+	color: #333;
275
+	margin-bottom: 20rpx;
276
+}
277
+
278
+.section-head {
279
+	display: flex;
280
+	align-items: center;
281
+	justify-content: space-between;
282
+	margin-bottom: 20rpx;
283
+
284
+	.section-title {
285
+		margin-bottom: 0;
286
+	}
287
+}
288
+
289
+.role-section {
290
+	.role-select-wrap {
291
+		margin-bottom: 20rpx;
292
+	}
293
+
294
+	.picker-inner {
295
+		display: flex;
296
+		align-items: center;
297
+		justify-content: space-between;
298
+		padding: 20rpx 24rpx;
299
+		background: #f9fafb;
300
+		border-radius: 12rpx;
301
+		border: 1px solid #eee;
302
+
303
+		.picker-text {
304
+			font-size: 28rpx;
305
+			color: #333;
306
+		}
307
+	}
308
+
309
+	.role-input-wrap {
310
+		margin-bottom: 20rpx;
311
+
312
+		.input-row {
313
+			display: flex;
314
+			align-items: center;
315
+			margin-bottom: 16rpx;
316
+
317
+			.input-label {
318
+				width: 140rpx;
319
+				font-size: 28rpx;
320
+				color: #666;
321
+			}
322
+		}
323
+	}
324
+
325
+	.btn-row {
326
+		display: flex;
327
+		gap: 20rpx;
328
+		flex-wrap: wrap;
329
+	}
330
+}
331
+
332
+.list-wrap {
333
+	max-height: 60vh;
334
+}
335
+
336
+.loading-wrap {
337
+	display: flex;
338
+	flex-direction: column;
339
+	align-items: center;
340
+	justify-content: center;
341
+	padding: 60rpx 0;
342
+
343
+	.loading-text {
344
+		margin-top: 16rpx;
345
+		font-size: 26rpx;
346
+		color: #999;
347
+	}
348
+}
349
+
350
+.field-list {
351
+	display: flex;
352
+	flex-direction: column;
353
+	gap: 12rpx;
354
+	padding-bottom: 24rpx;
355
+}
356
+
357
+.field-item {
358
+	display: flex;
359
+	align-items: center;
360
+	justify-content: space-between;
361
+	padding: 20rpx 24rpx;
362
+	background: #fafafa;
363
+	border-radius: 12rpx;
364
+	border: 1px solid #f0f0f0;
365
+}
366
+
367
+.field-name-wrap {
368
+	flex: 1;
369
+	min-width: 0;
370
+	display: flex;
371
+	flex-direction: column;
372
+	gap: 4rpx;
373
+}
374
+
375
+.field-name {
376
+	font-size: 28rpx;
377
+	font-weight: 600;
378
+	color: #333;
379
+}
380
+
381
+.field-key {
382
+	font-size: 22rpx;
383
+	color: #999;
384
+}
385
+
386
+.field-switches {
387
+	display: flex;
388
+	align-items: center;
389
+	gap: 32rpx;
390
+	flex-shrink: 0;
391
+}
392
+
393
+.switch-cell {
394
+	display: flex;
395
+	align-items: center;
396
+	gap: 12rpx;
397
+
398
+	.switch-label {
399
+		font-size: 24rpx;
400
+		color: #666;
401
+	}
402
+}
403
+
404
+.empty-wrap {
405
+	padding: 60rpx 0;
406
+}
407
+</style>

+ 6 - 0
pages/wareHouse/index.vue

@@ -12,6 +12,7 @@
12 12
 					<text class="asset-value">{{ wareHouseCard.totalCost }}</text>
13 13
 				</view>
14 14
 				<view class="btn-group">
15
+					<u-button type="primary" shape="circle" plain size="mini" @click="navigateToFieldPermissions">字段权限</u-button>
15 16
 					<u-button type="success" shape="circle" plain size="mini" @click="openOrderList">开单记录</u-button>
16 17
 					<order-list ref="orderListRef"></order-list>
17 18
 					<u-button type="primary" shape="circle" plain size="mini" @click="navigateToFakeRegistration">假货登记</u-button>
@@ -466,6 +467,11 @@ export default {
466 467
 		openOrderList() {
467 468
 			this.$refs.orderListRef.openList();
468 469
 		},
470
+		navigateToFieldPermissions() {
471
+			uni.navigateTo({
472
+				url: '/pages/wareHouse/fieldPermissions',
473
+			});
474
+		},
469 475
 		handleScroll(e) {
470 476
 			this.pageState.scrollTop = e.detail.scrollTop;
471 477
 		},

+ 2 - 1
store/getters.js

@@ -15,6 +15,7 @@ const getters = {
15 15
   recordId : (state) => state.app.recordId,
16 16
   dialing : (state) => state.app.dialing,
17 17
   permissions: (state) => state.user.permissions,
18
-  roles: (state) => state.user.userInfo.roles || []
18
+  roles: (state) => state.user.userInfo.roles || [],
19
+  warehouseFieldPermissions: (state) => state.user.warehouseFieldPermissions || []
19 20
 }
20 21
 export default getters

+ 12 - 0
store/modules/user.js

@@ -46,6 +46,7 @@ export default {
46 46
 		availableRoles: [], // 可用的角色列表
47 47
 		currentRoleIndex: 0, // 当前选中的角色索引
48 48
 		showRoleSwitch: false, // 是否显示切换权限按钮
49
+		warehouseFieldPermissions: [], // 仓库字段权限 [{ fieldName, read, edit }, ...],登录后按当前用户角色合并
49 50
 	},
50 51
 	mutations: {
51 52
 		SET_REDIRECTURL(state, data) {
@@ -173,6 +174,9 @@ export default {
173 174
 		SET_PERMISSIONS: (state, permissions) => {
174 175
 			state.permissions = permissions || [];
175 176
 		},
177
+		SET_WAREHOUSE_FIELD_PERMISSIONS: (state, list) => {
178
+			state.warehouseFieldPermissions = Array.isArray(list) ? list : [];
179
+		},
176 180
 		SWITCH_ROLE({}, role) {
177 181
 			setTimeout(() => {
178 182
 				const allIndices = [0, 1, 2, 3, 4, 5, 6];
@@ -222,6 +226,13 @@ export default {
222 226
 					} else {
223 227
 						commit("SET_PERMISSIONS", []);
224 228
 					}
229
+					// 登录后拉取仓库字段权限并存到 store(按当前用户角色合并)
230
+					uni.$u.api.wareHouseFieldPermissions({}).then((permRes) => {
231
+						const list = (permRes && permRes.data && Array.isArray(permRes.data)) ? permRes.data : [];
232
+						commit("SET_WAREHOUSE_FIELD_PERMISSIONS", list);
233
+					}).catch(() => {
234
+						commit("SET_WAREHOUSE_FIELD_PERMISSIONS", []);
235
+					});
225 236
 					commit("SET_BELONGSYSTEM", {
226 237
 						code: state.system.value,
227 238
 						callback: (flag) => {
@@ -264,6 +275,7 @@ export default {
264 275
 				commit("SET_TOKEN", "");
265 276
 				commit("SET_USERINFO", {});
266 277
 				commit("SET_PERMISSIONS", []); // 清空权限列表
278
+				commit("SET_WAREHOUSE_FIELD_PERMISSIONS", []); // 清空仓库字段权限
267 279
 				dispatch("app/logoutCloseData", null, {
268 280
 					root: true
269 281
 				});

+ 8 - 0
utils/api.js

@@ -111,6 +111,8 @@ const install = (Vue, vm) => {
111 111
 		clueCommissionById: (id, config = {}) => http.get(store.state.user.path + '/clueCommissionForm/' + id),
112 112
 		clueCommissionRemove: (ids, config = {}) => http.post(store.state.user.path + '/clueCommissionForm/remove', ids),
113 113
 		getCustomerManagerAllList: () => http.post('/system/user/getCustomerManagerAllList'),
114
+		/** 角色下拉列表(系统模块,用于仓库字段权限配置选择角色) */
115
+		getRoleOptionSelect: (config = {}) => http.get('/system/role/optionselect', config),
114 116
 		getClueSendFormVoByOrderId: (params) => http.get(store.state.user.path + '/clueSendForm/getClueSendFormVoByOrderId?' + qs.stringify(params)),
115 117
 		statisticsSendStatus: (data, config = {}) => http.post(store.state.user.path + '/clueSendForm/statisticsSendStatus', data), // 统计线索阶段
116 118
 
@@ -128,6 +130,12 @@ const install = (Vue, vm) => {
128 130
 		inquiryVerificationList:(params,data)=>http.post(store.state.user.path+'/inquiryCenter/inquiryVerificationList?' + qs.stringify(params), data),//  询价/核价列表
129 131
 		wareHouseList:(params,data)=>http.post(store.state.user.path+'/warehouse/wareHouseList?' + qs.stringify(params), data),//仓库中心-获取仓库列表
130 132
 		wareHouseDetail:(params)=>http.get(store.state.user.path+'/warehouse/wareHouseDetail',{ params }),//仓库中心-获取仓库详情
133
+		/** 仓库字段权限:不传参时按当前登录用户 roles 合并返回 [{ fieldName, read, edit }, ...] */
134
+		wareHouseFieldPermissions:(params)=>http.get(store.state.user.path+'/warehouse/fieldPermissions',{ params }),//仓库中心-字段读/编辑权限
135
+		/** 仓库字段权限-配置:按角色查询/保存(管理端) */
136
+		wareHouseFieldPermissionsConfigGet:(params)=>http.get(store.state.user.path+'/warehouse/fieldPermissions/config',{ params }),
137
+		wareHouseFieldPermissionsConfigSave:(data)=>http.post(store.state.user.path+'/warehouse/fieldPermissions/config', data),
138
+		wareHouseFieldPermissionsConfigDeleteByRole:(roleId)=>http.delete(store.state.user.path+'/warehouse/fieldPermissions/role/' + roleId),
131 139
 		wareHouseLog:(params)=>http.get(store.state.user.path+'/warehouse/wareHouseLog',{ params }),//仓库中心-获取仓库操作日志
132 140
 		wareHouseUpdate:(data)=>http.post(store.state.user.path+'/warehouse/wareHouseUpdate',data),//仓库中心-编辑商品库存
133 141
 		wareHouseLock:(data)=>http.post(store.state.user.path+'/warehouse/wareHouseLock',data),//仓库中心-锁单