Browse Source

网页端:优化预算和现场开支

移动端:更新仪器设备路径
余思翰 1 day ago
parent
commit
4812c78adb

+ 7
- 0
oa-ui-app/pages.json View File

@@ -160,6 +160,13 @@
160 160
 				"navigationBarTitleText" : "设备作业记录"
161 161
 			}
162 162
 		},
163
+		{
164
+			"path" : "pages/oa/device/instrumentsList",
165
+			"style" : 
166
+			{
167
+				"navigationBarTitleText" : "仪器设备管理"
168
+			}
169
+		},
163 170
 		{
164 171
 			"path" : "pages/components/formInfo",
165 172
 			"style" : 

+ 940
- 0
oa-ui-app/pages/oa/device/instrumentsList.vue View File

@@ -0,0 +1,940 @@
1
+<!--
2
+ * @Author: ysh
3
+ * @Date: 2025-07-08 14:00:47
4
+ * @LastEditors: Please set LastEditors
5
+ * @LastEditTime: 2025-07-08 15:50:20
6
+-->
7
+<template>
8
+  <view class="device-container">
9
+    <!-- 顶部搜索栏 -->
10
+    <view class="search-section">
11
+      <view class="search-bar">
12
+        <view class="search-input-wrapper">
13
+          <uni-icons type="search" size="16" color="#999"></uni-icons>
14
+          <input class="search-input" v-model="queryParams.name" placeholder="搜索设备名称" @confirm="handleQuery"
15
+            @input="handleSearchInput" />
16
+          <uni-icons v-if="queryParams.name" type="clear" size="16" color="#999" @click="clearSearch"></uni-icons>
17
+        </view>
18
+      </view>
19
+
20
+      <!-- 筛选条件 -->
21
+      <view class="filter-section" v-if="showFilter">
22
+        <view class="filter-row">
23
+          <view class="filter-item">
24
+            <text class="filter-label">设备状态</text>
25
+            <uni-data-select v-model="queryParams.status" :localdata="statusOptions" placeholder="全部状态"
26
+              @change="handleQuery" />
27
+          </view>
28
+          <view class="filter-item">
29
+            <text class="filter-label">设备品牌</text>
30
+            <input v-model="queryParams.brand" placeholder="品牌筛选" @confirm="handleQuery" />
31
+          </view>
32
+        </view>
33
+        <view class="filter-row">
34
+          <view class="filter-item">
35
+            <text class="filter-label">出厂编号</text>
36
+            <input v-model="queryParams.code" placeholder="编号筛选" @confirm="handleQuery" />
37
+          </view>
38
+          <view class="filter-item">
39
+            <text class="filter-label">规格型号</text>
40
+            <input v-model="queryParams.series" placeholder="型号筛选" @confirm="handleQuery" />
41
+          </view>
42
+        </view>
43
+      </view>
44
+
45
+      <!-- 筛选按钮 -->
46
+      <view class="filter-toggle" @click="toggleFilter">
47
+        <text>筛选</text>
48
+        <uni-icons :type="showFilter ? 'up' : 'down'" size="14" color="#666"></uni-icons>
49
+      </view>
50
+    </view>
51
+
52
+    <!-- 设备列表 -->
53
+    <scroll-view :scroll-top="scrollTop" scroll-y="true" class="device-list" @scrolltoupper="upper"
54
+      @scrolltolower="lower" @scroll="scroll" :refresher-enabled="true" :refresher-triggered="isRefreshing"
55
+      @refresherrefresh="onRefresh" :lower-threshold="50" :upper-threshold="50">
56
+      <view class="content-wrapper">
57
+        <template v-if="deviceList.length > 0">
58
+          <view class="device-card" v-for="(item, index) in deviceList" :key="index" @click="handleViewDetail(item)">
59
+            <view class="device-header">
60
+              <view class="device-info">
61
+                <text class="device-name">{{ item.name }}</text>
62
+                <text class="device-brand">{{ item.brand }}</text>
63
+              </view>
64
+              <view class="device-status">
65
+                <uni-tag :text="statusTypeText(item.status)" :type="statusTypeStyle(item.status)" size="small" />
66
+              </view>
67
+            </view>
68
+
69
+            <view class="device-content">
70
+              <view class="info-row">
71
+                <text class="info-label">设备编号:</text>
72
+                <text class="info-value">{{ item.deviceNumber || '暂无' }}</text>
73
+              </view>
74
+              <view class="info-row">
75
+                <text class="info-label">规格型号:</text>
76
+                <text class="info-value">{{ item.series || '暂无' }}</text>
77
+              </view>
78
+              <view class="info-row">
79
+                <text class="info-label">出厂编号:</text>
80
+                <text class="info-value">{{ item.code || '暂无' }}</text>
81
+              </view>
82
+              <view class="info-row">
83
+                <text class="info-label">购置时间:</text>
84
+                <text class="info-value">{{ formatDate(item.acquisitionTime) }}</text>
85
+              </view>
86
+              <view class="info-row">
87
+                <text class="info-label">购买价格:</text>
88
+                <text class="info-value price">¥{{ item.cost || '0' }}</text>
89
+              </view>
90
+              <view class="info-row">
91
+                <text class="info-label">单日成本:</text>
92
+                <text class="info-value cost">¥{{ item.dayCost || '0' }}</text>
93
+              </view>
94
+            </view>
95
+
96
+            <view class="device-actions">
97
+              <button class="action-btn view" @click.stop="handleViewDetail(item)">
98
+                <uni-icons type="eye" size="14" color="#007AFF"></uni-icons>
99
+                <text>查看</text>
100
+              </button>
101
+              <button class="action-btn edit" @click.stop="handleUpdate(item)" v-if="hasPermission('oa:device:edit')">
102
+                <uni-icons type="compose" size="14" color="#FF9500"></uni-icons>
103
+                <text>编辑</text>
104
+              </button>
105
+              <button class="action-btn delete" @click.stop="handleDelete(item)"
106
+                v-if="hasPermission('oa:device:remove')">
107
+                <uni-icons type="trash" size="14" color="#FF3B30"></uni-icons>
108
+                <text>删除</text>
109
+              </button>
110
+            </view>
111
+          </view>
112
+
113
+          <!-- 加载更多 -->
114
+          <view class="loading-more" v-if="hasMore">
115
+            <text class="loading-text">加载中...</text>
116
+          </view>
117
+          <view class="no-more" v-else>
118
+            <text class="no-more-text">没有更多数据了</text>
119
+          </view>
120
+        </template>
121
+
122
+        <!-- 空状态 -->
123
+        <view v-else class="empty-state">
124
+          <image src="/static/images/empty.png" mode="aspectFit" class="empty-image"></image>
125
+          <text class="empty-text">暂无设备数据</text>
126
+          <text class="empty-subtext">点击下方按钮添加设备</text>
127
+        </view>
128
+      </view>
129
+    </scroll-view>
130
+
131
+    <!-- 悬浮按钮 -->
132
+    <view class="fab-button" @click="handleAdd" v-if="hasPermission('oa:device:add')">
133
+      <uni-icons type="plusempty" size="24" color="#fff"></uni-icons>
134
+    </view>
135
+
136
+    <!-- 设备详情弹窗 -->
137
+    <uni-popup ref="detailPopup" type="center">
138
+      <view class="detail-popup">
139
+        <view class="popup-header">
140
+          <text class="title">设备详情</text>
141
+          <uni-icons type="close" size="20" @click="closeDetailPopup"></uni-icons>
142
+        </view>
143
+        <view class="popup-body">
144
+          <view class="detail-item">
145
+            <text class="detail-label">设备名称</text>
146
+            <text class="detail-value">{{ currentDevice.name }}</text>
147
+          </view>
148
+          <view class="detail-item">
149
+            <text class="detail-label">设备品牌</text>
150
+            <text class="detail-value">{{ currentDevice.brand }}</text>
151
+          </view>
152
+          <view class="detail-item">
153
+            <text class="detail-label">设备编号</text>
154
+            <text class="detail-value">{{ currentDevice.deviceNumber }}</text>
155
+          </view>
156
+          <view class="detail-item">
157
+            <text class="detail-label">规格型号</text>
158
+            <text class="detail-value">{{ currentDevice.series }}</text>
159
+          </view>
160
+          <view class="detail-item">
161
+            <text class="detail-label">出厂编号</text>
162
+            <text class="detail-value">{{ currentDevice.code }}</text>
163
+          </view>
164
+          <view class="detail-item">
165
+            <text class="detail-label">设备状态</text>
166
+            <text class="detail-value">
167
+              <uni-tag :text="statusTypeText(currentDevice.status)" :type="statusTypeStyle(currentDevice.status)"
168
+                size="small" />
169
+            </text>
170
+          </view>
171
+          <view class="detail-item">
172
+            <text class="detail-label">购置时间</text>
173
+            <text class="detail-value">{{ formatDate(currentDevice.acquisitionTime) }}</text>
174
+          </view>
175
+          <view class="detail-item">
176
+            <text class="detail-label">购买价格</text>
177
+            <text class="detail-value price">¥{{ currentDevice.cost }}</text>
178
+          </view>
179
+          <view class="detail-item">
180
+            <text class="detail-label">预计使用年限</text>
181
+            <text class="detail-value">{{ currentDevice.expectLife }}年</text>
182
+          </view>
183
+          <view class="detail-item">
184
+            <text class="detail-label">单日成本</text>
185
+            <text class="detail-value cost">¥{{ currentDevice.dayCost }}</text>
186
+          </view>
187
+          <view class="detail-item">
188
+            <text class="detail-label">存放地点</text>
189
+            <text class="detail-value">{{ currentDevice.place || '暂无' }}</text>
190
+          </view>
191
+          <view class="detail-item">
192
+            <text class="detail-label">备注</text>
193
+            <text class="detail-value">{{ currentDevice.remark || '暂无' }}</text>
194
+          </view>
195
+        </view>
196
+        <view class="popup-footer">
197
+          <button class="popup-btn cancel" @click="closeDetailPopup">关闭</button>
198
+          <button class="popup-btn primary" @click="handleViewLogs">查看记录</button>
199
+        </view>
200
+      </view>
201
+    </uni-popup>
202
+
203
+    <!-- 新增/编辑设备弹窗 -->
204
+    <uni-popup ref="formPopup" type="center">
205
+      <view class="form-popup">
206
+        <view class="popup-header">
207
+          <text class="title">{{ isEdit ? '编辑设备' : '新增设备' }}</text>
208
+          <uni-icons type="close" size="20" @click="closeFormPopup"></uni-icons>
209
+        </view>
210
+        <scroll-view scroll-y="true" class="popup-body">
211
+          <view class="form-item">
212
+            <text class="label">设备品牌 *</text>
213
+            <input v-model="form.brand" placeholder="请输入设备品牌" />
214
+          </view>
215
+          <view class="form-item">
216
+            <text class="label">设备名称 *</text>
217
+            <input v-model="form.name" placeholder="请输入设备名称" />
218
+          </view>
219
+          <view class="form-item">
220
+            <text class="label">设备类别</text>
221
+            <uni-data-select v-model="form.type" :localdata="typeOptions" placeholder="请选择设备类别" />
222
+          </view>
223
+          <view class="form-item">
224
+            <text class="label">规格型号</text>
225
+            <input v-model="form.series" placeholder="请输入规格型号" />
226
+          </view>
227
+          <view class="form-item">
228
+            <text class="label">购置时间</text>
229
+            <view class="date-picker" @click="openDatePicker">
230
+              <text v-if="form.acquisitionTime">{{ form.acquisitionTime }}</text>
231
+              <text v-else class="placeholder">请选择购置时间</text>
232
+            </view>
233
+          </view>
234
+          <view class="form-item">
235
+            <text class="label">存放地点</text>
236
+            <input v-model="form.place" placeholder="请输入存放地点" />
237
+          </view>
238
+          <view class="form-item">
239
+            <text class="label">购买价格(元)</text>
240
+            <input v-model="form.cost" type="number" placeholder="请输入购买价格" />
241
+          </view>
242
+          <view class="form-item">
243
+            <text class="label">预计使用年限(年)</text>
244
+            <input v-model="form.expectLife" type="number" placeholder="请输入使用年限" />
245
+          </view>
246
+          <view class="form-item">
247
+            <text class="label">出厂编号</text>
248
+            <textarea v-model="form.code" placeholder="请输入出厂编号" />
249
+          </view>
250
+          <view class="form-item">
251
+            <text class="label">单日成本(元)</text>
252
+            <input v-model="form.dayCost" type="number" placeholder="请输入单日成本" />
253
+          </view>
254
+          <view class="form-item">
255
+            <text class="label">管理部门</text>
256
+            <uni-data-select v-model="form.manageDept" :localdata="deptOptions" placeholder="请选择管理部门" />
257
+          </view>
258
+          <view class="form-item">
259
+            <text class="label">设备状态</text>
260
+            <uni-data-select v-model="form.status" :localdata="statusOptions" placeholder="请选择设备状态" />
261
+          </view>
262
+          <view class="form-item">
263
+            <text class="label">设备编号</text>
264
+            <input v-model="form.deviceNumber" placeholder="请输入设备编号" />
265
+          </view>
266
+          <view class="form-item">
267
+            <text class="label">备注</text>
268
+            <textarea v-model="form.remark" placeholder="请输入备注信息" />
269
+          </view>
270
+        </scroll-view>
271
+        <view class="popup-footer">
272
+          <button class="popup-btn cancel" @click="closeFormPopup">取消</button>
273
+          <button class="popup-btn primary" @click="submitForm">确定</button>
274
+        </view>
275
+      </view>
276
+    </uni-popup>
277
+
278
+    <!-- 日期选择器 -->
279
+    <uv-calendar ref="calendar" mode="single" @confirm="confirmDate"></uv-calendar>
280
+  </view>
281
+</template>
282
+
283
+<script>
284
+import { listDevice, getDevice, delDevice, addDevice, updateDevice } from "@/api/oa/device/device";
285
+
286
+export default {
287
+  name: "InstrumentsList",
288
+  data() {
289
+    return {
290
+      // 滚动相关
291
+      scrollTop: 0,
292
+      isRefreshing: false,
293
+      hasMore: true,
294
+
295
+      // 筛选相关
296
+      showFilter: false,
297
+
298
+      // 列表数据
299
+      deviceList: [],
300
+      total: 0,
301
+
302
+      // 查询参数
303
+      queryParams: {
304
+        pageNum: 1,
305
+        pageSize: 20,
306
+        name: '',
307
+        status: '',
308
+        brand: '',
309
+        code: '',
310
+        series: ''
311
+      },
312
+
313
+      // 表单数据
314
+      form: {},
315
+      isEdit: false,
316
+
317
+      // 当前查看的设备
318
+      currentDevice: {},
319
+
320
+      // 选项数据
321
+      statusOptions: [
322
+        { value: '0', text: '被领用' },
323
+        { value: '1', text: '可领用' },
324
+        { value: '2', text: '维修中' },
325
+        { value: '3', text: '已停用' },
326
+        { value: '4', text: '已报废' }
327
+      ],
328
+      typeOptions: [
329
+        { value: '仪器设备', text: '仪器设备' },
330
+        { value: '办公设备', text: '办公设备' }
331
+      ],
332
+      deptOptions: []
333
+    };
334
+  },
335
+
336
+  onLoad() {
337
+    this.getList();
338
+    this.getDeptOptions();
339
+  },
340
+
341
+  methods: {
342
+    // 获取设备列表
343
+    async getList() {
344
+      try {
345
+        const response = await listDevice(this.queryParams);
346
+        if (this.queryParams.pageNum === 1) {
347
+          this.deviceList = response.rows || [];
348
+        } else {
349
+          this.deviceList = [...this.deviceList, ...(response.rows || [])];
350
+        }
351
+        this.total = response.total || 0;
352
+        this.hasMore = this.deviceList.length < this.total;
353
+      } catch (error) {
354
+        console.error('获取设备列表失败:', error);
355
+        uni.showToast({
356
+          title: '获取数据失败',
357
+          icon: 'none'
358
+        });
359
+      }
360
+    },
361
+
362
+    // 获取部门选项
363
+    async getDeptOptions() {
364
+      try {
365
+        // 这里需要根据实际的部门API进行调整
366
+        const deptList = this.$store.state.user.deptList || [];
367
+        this.deptOptions = deptList.map(dept => ({
368
+          value: dept.deptId,
369
+          text: dept.deptName
370
+        }));
371
+      } catch (error) {
372
+        console.error('获取部门列表失败:', error);
373
+      }
374
+    },
375
+
376
+    // 搜索相关方法
377
+    handleSearchInput() {
378
+      // 可以在这里添加防抖搜索
379
+    },
380
+
381
+    handleQuery() {
382
+      this.queryParams.pageNum = 1;
383
+      this.getList();
384
+    },
385
+
386
+    clearSearch() {
387
+      this.queryParams.name = '';
388
+      this.handleQuery();
389
+    },
390
+
391
+    toggleFilter() {
392
+      this.showFilter = !this.showFilter;
393
+    },
394
+
395
+    // 滚动相关方法
396
+    upper() {
397
+      // 下拉刷新
398
+    },
399
+
400
+    lower() {
401
+      if (this.hasMore) {
402
+        this.queryParams.pageNum++;
403
+        this.getList();
404
+      }
405
+    },
406
+
407
+    scroll(e) {
408
+      this.scrollTop = e.detail.scrollTop;
409
+    },
410
+
411
+    async onRefresh() {
412
+      this.isRefreshing = true;
413
+      this.queryParams.pageNum = 1;
414
+      await this.getList();
415
+      this.isRefreshing = false;
416
+    },
417
+
418
+    // 设备操作方法
419
+    handleViewDetail(item) {
420
+      this.currentDevice = item;
421
+      this.$refs.detailPopup.open();
422
+    },
423
+
424
+    closeDetailPopup() {
425
+      this.$refs.detailPopup.close();
426
+    },
427
+
428
+    handleViewLogs() {
429
+      this.closeDetailPopup();
430
+      uni.navigateTo({
431
+        url: `/pages/oa/device/deviceLog?deviceId=${this.currentDevice.deviceId}`
432
+      });
433
+    },
434
+
435
+    handleAdd() {
436
+      this.isEdit = false;
437
+      this.resetForm();
438
+      this.$refs.formPopup.open();
439
+    },
440
+
441
+    async handleUpdate(item) {
442
+      this.isEdit = true;
443
+      try {
444
+        const response = await getDevice(item.deviceId);
445
+        this.form = response.data || {};
446
+        this.$refs.formPopup.open();
447
+      } catch (error) {
448
+        console.error('获取设备详情失败:', error);
449
+        uni.showToast({
450
+          title: '获取设备详情失败',
451
+          icon: 'none'
452
+        });
453
+      }
454
+    },
455
+
456
+    closeFormPopup() {
457
+      this.$refs.formPopup.close();
458
+    },
459
+
460
+    async handleDelete(item) {
461
+      uni.showModal({
462
+        title: '确认删除',
463
+        content: `是否确认删除设备"${item.name}"?`,
464
+        success: async (res) => {
465
+          if (res.confirm) {
466
+            try {
467
+              await delDevice(item.deviceId);
468
+              uni.showToast({
469
+                title: '删除成功',
470
+                icon: 'success'
471
+              });
472
+              this.getList();
473
+            } catch (error) {
474
+              console.error('删除设备失败:', error);
475
+              uni.showToast({
476
+                title: '删除失败',
477
+                icon: 'none'
478
+              });
479
+            }
480
+          }
481
+        }
482
+      });
483
+    },
484
+
485
+    // 表单相关方法
486
+    resetForm() {
487
+      this.form = {
488
+        deviceId: null,
489
+        brand: '',
490
+        name: '',
491
+        type: '仪器设备',
492
+        series: '',
493
+        acquisitionTime: '',
494
+        place: '',
495
+        cost: '',
496
+        expectLife: '',
497
+        code: '',
498
+        dayCost: '',
499
+        manageDept: '',
500
+        status: '1',
501
+        deviceNumber: '',
502
+        remark: ''
503
+      };
504
+    },
505
+
506
+    openDatePicker() {
507
+      this.$refs.calendar.open();
508
+    },
509
+
510
+    confirmDate(e) {
511
+      this.form.acquisitionTime = e.result;
512
+    },
513
+
514
+    async submitForm() {
515
+      // 表单验证
516
+      if (!this.form.name) {
517
+        uni.showToast({
518
+          title: '请输入设备名称',
519
+          icon: 'none'
520
+        });
521
+        return;
522
+      }
523
+
524
+      try {
525
+        if (this.isEdit) {
526
+          await updateDevice(this.form);
527
+          uni.showToast({
528
+            title: '修改成功',
529
+            icon: 'success'
530
+          });
531
+        } else {
532
+          await addDevice(this.form);
533
+          uni.showToast({
534
+            title: '新增成功',
535
+            icon: 'success'
536
+          });
537
+        }
538
+
539
+        this.closeFormPopup();
540
+        this.getList();
541
+      } catch (error) {
542
+        console.error('保存设备失败:', error);
543
+        uni.showToast({
544
+          title: '保存失败',
545
+          icon: 'none'
546
+        });
547
+      }
548
+    },
549
+
550
+    // 工具方法
551
+    formatDate(date) {
552
+      if (!date) return '暂无';
553
+      return new Date(date).toLocaleDateString();
554
+    },
555
+
556
+    statusTypeText(status) {
557
+      const statusMap = {
558
+        '0': '被领用',
559
+        '1': '可领用',
560
+        '2': '维修中',
561
+        '3': '已停用',
562
+        '4': '已报废'
563
+      };
564
+      return statusMap[status] || '未知';
565
+    },
566
+
567
+    statusTypeStyle(status) {
568
+      const styleMap = {
569
+        '0': 'warning',
570
+        '1': 'success',
571
+        '2': 'primary',
572
+        '3': 'error',
573
+        '4': 'info'
574
+      };
575
+      return styleMap[status] || 'default';
576
+    },
577
+
578
+    hasPermission(permission) {
579
+      // 这里需要根据实际的权限系统进行调整
580
+      return true;
581
+    }
582
+  }
583
+};
584
+</script>
585
+
586
+<style lang="scss" scoped>
587
+.device-container {
588
+  height: 100vh;
589
+  background-color: #f5f5f5;
590
+  display: flex;
591
+  flex-direction: column;
592
+}
593
+
594
+.search-section {
595
+  background: #fff;
596
+  padding: 10px 15px;
597
+  border-bottom: 1px solid #eee;
598
+}
599
+
600
+.search-bar {
601
+  margin-bottom: 10px;
602
+}
603
+
604
+.search-input-wrapper {
605
+  display: flex;
606
+  align-items: center;
607
+  background: #f8f8f8;
608
+  border-radius: 20px;
609
+  padding: 8px 15px;
610
+
611
+  .search-input {
612
+    flex: 1;
613
+    margin-left: 8px;
614
+    font-size: 14px;
615
+  }
616
+}
617
+
618
+.filter-section {
619
+  background: #f8f8f8;
620
+  border-radius: 8px;
621
+  padding: 10px;
622
+  margin-bottom: 10px;
623
+}
624
+
625
+.filter-row {
626
+  display: flex;
627
+  gap: 10px;
628
+  margin-bottom: 10px;
629
+
630
+  &:last-child {
631
+    margin-bottom: 0;
632
+  }
633
+}
634
+
635
+.filter-item {
636
+  flex: 1;
637
+
638
+  .filter-label {
639
+    font-size: 12px;
640
+    color: #666;
641
+    margin-bottom: 5px;
642
+    display: block;
643
+  }
644
+
645
+  input {
646
+    width: 100%;
647
+    padding: 6px 10px;
648
+    border: 1px solid #ddd;
649
+    border-radius: 4px;
650
+    font-size: 12px;
651
+  }
652
+}
653
+
654
+.filter-toggle {
655
+  display: flex;
656
+  align-items: center;
657
+  justify-content: center;
658
+  gap: 5px;
659
+  font-size: 14px;
660
+  color: #666;
661
+  padding: 8px;
662
+}
663
+
664
+.device-list {
665
+  flex: 1;
666
+}
667
+
668
+.content-wrapper {
669
+  padding: 15px;
670
+}
671
+
672
+.device-card {
673
+  background: #fff;
674
+  border-radius: 12px;
675
+  margin-bottom: 15px;
676
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
677
+  overflow: hidden;
678
+}
679
+
680
+.device-header {
681
+  display: flex;
682
+  justify-content: space-between;
683
+  align-items: center;
684
+  padding: 15px;
685
+  border-bottom: 1px solid #f0f0f0;
686
+}
687
+
688
+.device-info {
689
+  flex: 1;
690
+
691
+  .device-name {
692
+    font-size: 16px;
693
+    font-weight: 600;
694
+    color: #333;
695
+    display: block;
696
+    margin-bottom: 4px;
697
+  }
698
+
699
+  .device-brand {
700
+    font-size: 12px;
701
+    color: #666;
702
+  }
703
+}
704
+
705
+.device-content {
706
+  padding: 15px;
707
+}
708
+
709
+.info-row {
710
+  display: flex;
711
+  margin-bottom: 8px;
712
+
713
+  &:last-child {
714
+    margin-bottom: 0;
715
+  }
716
+
717
+  .info-label {
718
+    width: 80px;
719
+    font-size: 13px;
720
+    color: #666;
721
+    flex-shrink: 0;
722
+  }
723
+
724
+  .info-value {
725
+    flex: 1;
726
+    font-size: 13px;
727
+    color: #333;
728
+
729
+    &.price,
730
+    &.cost {
731
+      color: #ff6b35;
732
+      font-weight: 600;
733
+    }
734
+  }
735
+}
736
+
737
+.device-actions {
738
+  display: flex;
739
+  border-top: 1px solid #f0f0f0;
740
+
741
+  .action-btn {
742
+    flex: 1;
743
+    display: flex;
744
+    align-items: center;
745
+    justify-content: center;
746
+    gap: 4px;
747
+    padding: 12px;
748
+    background: none;
749
+    border: none;
750
+    font-size: 13px;
751
+
752
+    &.view {
753
+      color: #007AFF;
754
+    }
755
+
756
+    &.edit {
757
+      color: #FF9500;
758
+      border-left: 1px solid #f0f0f0;
759
+    }
760
+
761
+    &.delete {
762
+      color: #FF3B30;
763
+      border-left: 1px solid #f0f0f0;
764
+    }
765
+  }
766
+}
767
+
768
+.loading-more,
769
+.no-more {
770
+  text-align: center;
771
+  padding: 20px;
772
+
773
+  .loading-text,
774
+  .no-more-text {
775
+    font-size: 14px;
776
+    color: #999;
777
+  }
778
+}
779
+
780
+.empty-state {
781
+  text-align: center;
782
+  padding: 60px 20px;
783
+
784
+  .empty-image {
785
+    width: 120px;
786
+    height: 120px;
787
+    margin-bottom: 20px;
788
+  }
789
+
790
+  .empty-text {
791
+    font-size: 16px;
792
+    color: #666;
793
+    display: block;
794
+    margin-bottom: 8px;
795
+  }
796
+
797
+  .empty-subtext {
798
+    font-size: 14px;
799
+    color: #999;
800
+  }
801
+}
802
+
803
+.fab-button {
804
+  position: fixed;
805
+  right: 20px;
806
+  bottom: 30px;
807
+  width: 56px;
808
+  height: 56px;
809
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
810
+  border-radius: 50%;
811
+  display: flex;
812
+  align-items: center;
813
+  justify-content: center;
814
+  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
815
+  z-index: 999;
816
+}
817
+
818
+// 弹窗样式
819
+.detail-popup,
820
+.form-popup {
821
+  background: #fff;
822
+  border-radius: 12px;
823
+  width: 90vw;
824
+  max-height: 80vh;
825
+  display: flex;
826
+  flex-direction: column;
827
+}
828
+
829
+.popup-header {
830
+  display: flex;
831
+  justify-content: space-between;
832
+  align-items: center;
833
+  padding: 20px;
834
+  border-bottom: 1px solid #f0f0f0;
835
+
836
+  .title {
837
+    font-size: 18px;
838
+    font-weight: 600;
839
+    color: #333;
840
+  }
841
+}
842
+
843
+.popup-body {
844
+  flex: 1;
845
+  padding: 20px;
846
+  max-height: 60vh;
847
+}
848
+
849
+.detail-item {
850
+  display: flex;
851
+  margin-bottom: 15px;
852
+
853
+  .detail-label {
854
+    width: 80px;
855
+    font-size: 14px;
856
+    color: #666;
857
+    flex-shrink: 0;
858
+  }
859
+
860
+  .detail-value {
861
+    flex: 1;
862
+    font-size: 14px;
863
+    color: #333;
864
+
865
+    &.price,
866
+    &.cost {
867
+      color: #ff6b35;
868
+      font-weight: 600;
869
+    }
870
+  }
871
+}
872
+
873
+.form-item {
874
+  margin-bottom: 20px;
875
+
876
+  .label {
877
+    font-size: 14px;
878
+    color: #333;
879
+    margin-bottom: 8px;
880
+    display: block;
881
+  }
882
+
883
+  input,
884
+  textarea {
885
+    width: 100%;
886
+    padding: 12px;
887
+    border: 1px solid #ddd;
888
+    border-radius: 8px;
889
+    font-size: 14px;
890
+    background: #f8f8f8;
891
+
892
+    &:focus {
893
+      border-color: #667eea;
894
+      background: #fff;
895
+    }
896
+  }
897
+
898
+  textarea {
899
+    min-height: 80px;
900
+    resize: none;
901
+  }
902
+
903
+  .date-picker {
904
+    padding: 12px;
905
+    border: 1px solid #ddd;
906
+    border-radius: 8px;
907
+    background: #f8f8f8;
908
+    font-size: 14px;
909
+
910
+    .placeholder {
911
+      color: #999;
912
+    }
913
+  }
914
+}
915
+
916
+.popup-footer {
917
+  display: flex;
918
+  gap: 15px;
919
+  padding: 20px;
920
+  border-top: 1px solid #f0f0f0;
921
+
922
+  .popup-btn {
923
+    flex: 1;
924
+    padding: 12px;
925
+    border-radius: 8px;
926
+    font-size: 16px;
927
+    border: none;
928
+
929
+    &.cancel {
930
+      background: #f5f5f5;
931
+      color: #666;
932
+    }
933
+
934
+    &.primary {
935
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
936
+      color: #fff;
937
+    }
938
+  }
939
+}
940
+</style>

+ 7
- 3
oa-ui-app/pages/work/index.vue View File

@@ -2,7 +2,7 @@
2 2
  * @Author: ysh
3 3
  * @Date: 2025-01-16 11:17:08
4 4
  * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-06-23 09:55:12
5
+ * @LastEditTime: 2025-07-08 14:42:51
6 6
 -->
7 7
 <template>
8 8
   <view class="work-container">
@@ -80,9 +80,9 @@ export default {
80 80
           hasPermi: checkPermi(['oa:car:list'])
81 81
         },
82 82
         {
83
-          name: '设备管理',
83
+          name: '仪器设备',
84 84
           icon: '/static/images/work/device.png',
85
-          url: 'device',
85
+          url: 'instruments',
86 86
           hasPermi: checkPermi(['oa:device:list'])
87 87
         },
88 88
         {
@@ -138,6 +138,10 @@ export default {
138 138
         uni.navigateTo({
139 139
           url: '/pages/oa/car/carList'
140 140
         })
141
+      } else if (type == 'instruments') {
142
+        uni.navigateTo({
143
+          url: '/pages/oa/device/instrumentsList'
144
+        })
141 145
       }
142 146
       else {
143 147
         this.$modal.showToast('模块建设中~')

+ 11
- 7
oa-ui/src/views/flowable/form/budget/adjust/newBudgetInfo.vue View File

@@ -2,7 +2,7 @@
2 2
  * @Author: ysh
3 3
  * @Date: 2025-05-07 11:01:39
4 4
  * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-05-29 14:36:02
5
+ * @LastEditTime: 2025-07-10 15:47:58
6 6
 -->
7 7
 <template>
8 8
   <div class="main" v-loading="loading">
@@ -195,7 +195,11 @@
195 195
             <td class="remark-cell">{{ staff.remark }}</td>
196 196
           </tr>
197 197
           <tr>
198
-            <td colspan="6" class="head amount">外业人员成本合计</td>
198
+            <td colspan="4" class="head amount">外业人员成本合计</td>
199
+            <td class="head amount" style="text-align:right;">{{outerStaffList.reduce((sum, staff) => sum +
200
+              (Number(staff.days) || 0),
201
+              0).toFixed(2)}}</td>
202
+            <td>——</td>
199 203
             <td class="head amount" style="text-align:right;">{{outerStaffList.reduce((sum, staff) => sum +
200 204
               (Number(staff.staffCost) || 0),
201 205
               0).toFixed(2)}}</td>
@@ -271,8 +275,8 @@
271 275
       <el-descriptions-item label="现场开支" :span="3" v-if="siteList.length > 0">
272 276
         <table border="1" style="width:100%;">
273 277
           <tr style="background-color:#f8f8f9">
274
-            <td>序号</td>
275
-            <td>开支项</td>
278
+            <td style="min-width:50px">序号</td>
279
+            <td style="min-width:120px">开支项</td>
276 280
             <td style="min-width:120px">金额</td>
277 281
             <td>备注</td>
278 282
           </tr>
@@ -302,9 +306,9 @@
302 306
         </div>
303 307
         <table border="1" style="width:100%;" :class="{ 'business-section': taskName == '经营审核' }">
304 308
           <tr style="background-color:#f8f8f9">
305
-            <td>序号</td>
306
-            <td>名称</td>
307
-            <td>金额</td>
309
+            <td style="min-width:50px">序号</td>
310
+            <td style="min-width:120px">名称</td>
311
+            <td style="min-width:120px">金额</td>
308 312
             <td>备注</td>
309 313
           </tr>
310 314
           <tr>

+ 1
- 1
oa-ui/src/views/flowable/form/budget/siteExpenses.vue View File

@@ -159,7 +159,7 @@ export default {
159 159
     handleExpenseChange(value) {
160 160
       if (value === 'new_expense') {
161 161
         this.open = true;
162
-      } else if (value === '食宿') {
162
+      } else if (value === '食宿') {
163 163
         const currentIndex = this.expensesList.findIndex(item => item.name === value);
164 164
         if (currentIndex !== -1) {
165 165
           const totalDays = this.outerUsers.reduce((sum, user) => sum + (user.days || 0), 0);

+ 5
- 3
oa-ui/src/views/flowable/form/budget/staffTable.vue View File

@@ -54,8 +54,8 @@
54 54
             </td>
55 55
           </tr>
56 56
           <tr>
57
-            <td colspan="5">合计</td>
58
-            <!-- <td>{{ form.innerDays }}</td> -->
57
+            <td colspan="4">合计</td>
58
+            <td>{{ form.innerDays }}</td>
59 59
             <td>{{ form.innerSettle }}</td>
60 60
             <td>{{ form.innerStaffCost }}</td>
61 61
           </tr>
@@ -118,7 +118,9 @@
118 118
             </td>
119 119
           </tr>
120 120
           <tr>
121
-            <td colspan="7">合计</td>
121
+            <td colspan="5">合计</td>
122
+            <td>{{ form.outerDays }}</td>
123
+            <td>——</td>
122 124
             <td>{{ form.outerSettle }}</td>
123 125
             <td>{{ form.outerStaffCost }}</td>
124 126
           </tr>

Loading…
Cancel
Save