business_tms_program 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintrc-auto-import.json +113 -0
  3. package/.eslintrc.js +121 -0
  4. package/.prettierrc.js +9 -0
  5. package/.stylelintignore +4 -0
  6. package/README.md +43 -0
  7. package/components.d.ts +23 -0
  8. package/index.html +20 -0
  9. package/package.json +70 -0
  10. package/shims-uni.d.ts +10 -0
  11. package/src/App.vue +81 -0
  12. package/src/api/afterSale.ts +184 -0
  13. package/src/api/context.ts +26 -0
  14. package/src/api/device.ts +134 -0
  15. package/src/api/index.ts +80 -0
  16. package/src/api/installtion.ts +155 -0
  17. package/src/api/model/index.ts +15 -0
  18. package/src/api/model/userModel.ts +62 -0
  19. package/src/api/order.ts +49 -0
  20. package/src/api/system.ts +19 -0
  21. package/src/api/user.ts +171 -0
  22. package/src/auto-imports.d.ts +108 -0
  23. package/src/components/ConfirmDialog.vue +101 -0
  24. package/src/components/DaySelect.vue +212 -0
  25. package/src/components/Drawer.vue +104 -0
  26. package/src/components/DrawerSelect.vue +105 -0
  27. package/src/components/DropMenu.vue +144 -0
  28. package/src/components/Empty.vue +49 -0
  29. package/src/components/Loading.vue +41 -0
  30. package/src/components/RippleBtn.vue +159 -0
  31. package/src/components/SinglePick.vue +120 -0
  32. package/src/components/Skeleton.vue +43 -0
  33. package/src/components/Timeline.vue +85 -0
  34. package/src/components/Upload.vue +217 -0
  35. package/src/config/app.ts +32 -0
  36. package/src/config/env.ts +29 -0
  37. package/src/dict/afterSale.ts +161 -0
  38. package/src/dict/device.ts +29 -0
  39. package/src/dict/installtion.ts +141 -0
  40. package/src/dict/systems.ts +4 -0
  41. package/src/env.d.ts +8 -0
  42. package/src/hooks/useForm.ts +222 -0
  43. package/src/hooks/useUpload.ts +80 -0
  44. package/src/main.ts +8 -0
  45. package/src/manifest.json +39 -0
  46. package/src/pages/acceptance/DeviceInfo.vue +132 -0
  47. package/src/pages/acceptance/list.vue +276 -0
  48. package/src/pages/afterSale/DeviceInfo.vue +128 -0
  49. package/src/pages/afterSale/Step.vue +0 -0
  50. package/src/pages/afterSale/faultReport.vue +552 -0
  51. package/src/pages/afterSale/orderDetail.vue +327 -0
  52. package/src/pages/afterSale/orderFinish.vue +517 -0
  53. package/src/pages/afterSale/orderList.vue +305 -0
  54. package/src/pages/afterSale/returnVisit.vue +288 -0
  55. package/src/pages/afterSale/searchDeviceList.vue +148 -0
  56. package/src/pages/device/Search.vue +201 -0
  57. package/src/pages/device/acceptance.vue +270 -0
  58. package/src/pages/device/detail.vue +165 -0
  59. package/src/pages/device/index.vue +322 -0
  60. package/src/pages/device/info.vue +140 -0
  61. package/src/pages/device/list.vue +219 -0
  62. package/src/pages/device/materialTowerCode.vue +589 -0
  63. package/src/pages/device/searchList.vue +224 -0
  64. package/src/pages/installtion/Record.vue +145 -0
  65. package/src/pages/installtion/StatusTimeline.vue +85 -0
  66. package/src/pages/installtion/addAcceptance.vue +409 -0
  67. package/src/pages/installtion/addRecord.vue +338 -0
  68. package/src/pages/installtion/orderDetail.vue +220 -0
  69. package/src/pages/installtion/orderList.vue +100 -0
  70. package/src/pages/user/component/PersonAgree.vue +226 -0
  71. package/src/pages/user/component/PrivayAgree.vue +221 -0
  72. package/src/pages/user/component/SliderCode.vue +173 -0
  73. package/src/pages/user/forgetPassword.vue +249 -0
  74. package/src/pages/user/index.vue +139 -0
  75. package/src/pages/user/login.vue +342 -0
  76. package/src/pages/user/register.vue +348 -0
  77. package/src/pages/user/repassword.vue +329 -0
  78. package/src/pages/user/utils/mcaptcha.js +75 -0
  79. package/src/pages/user/utils/verifyCode.ts +41 -0
  80. package/src/pages/workspace/index.vue +225 -0
  81. package/src/pages.json +203 -0
  82. package/src/shime-uni.d.ts +6 -0
  83. package/src/static/icon/system/breeder_icon.png +0 -0
  84. package/src/static/icon/system/check.png +0 -0
  85. package/src/static/icon/system/factory_icon.png +0 -0
  86. package/src/static/icon/system/plus.png +0 -0
  87. package/src/static/icon/system/right.png +0 -0
  88. package/src/static/icon/system/unCheck.png +0 -0
  89. package/src/static/icon/tab/search.png +0 -0
  90. package/src/static/icon/tab/user.png +0 -0
  91. package/src/static/icon/tab/user_active.png +0 -0
  92. package/src/static/icon/tab/workspace.png +0 -0
  93. package/src/static/icon/tab/workspace_active.png +0 -0
  94. package/src/static/img/active_dot.png +0 -0
  95. package/src/static/img/afterSale_icon.png +0 -0
  96. package/src/static/img/check.png +0 -0
  97. package/src/static/img/close.png +0 -0
  98. package/src/static/img/confirm.png +0 -0
  99. package/src/static/img/empty.png +0 -0
  100. package/src/static/img/equipment_icon.png +0 -0
  101. package/src/static/img/fault_icon.png +0 -0
  102. package/src/static/img/install_icon.png +0 -0
  103. package/src/static/img/login_bg2.png +0 -0
  104. package/src/static/img/movable_right.png +0 -0
  105. package/src/static/img/navigation.png +0 -0
  106. package/src/static/img/psw_off.png +0 -0
  107. package/src/static/img/psw_on.png +0 -0
  108. package/src/static/img/scan.png +0 -0
  109. package/src/static/img/scan_icon.png +0 -0
  110. package/src/static/img/search.png +0 -0
  111. package/src/static/img/turn_right.png +0 -0
  112. package/src/static/img/unActive_dot.png +0 -0
  113. package/src/static/img/verifyBg.png +0 -0
  114. package/src/stores/index.ts +11 -0
  115. package/src/stores/modules/customer.ts +146 -0
  116. package/src/stores/modules/installtion.ts +30 -0
  117. package/src/stores/modules/system.ts +56 -0
  118. package/src/stores/modules/user.ts +133 -0
  119. package/src/stores/types.ts +16 -0
  120. package/src/stores/utils.ts +6 -0
  121. package/src/styles/index.less +63 -0
  122. package/src/types/chengyiApi.d.ts +36 -0
  123. package/src/types/index.d.ts +95 -0
  124. package/src/utils/address.ts +17 -0
  125. package/src/utils/cipher.ts +61 -0
  126. package/src/utils/form.ts +155 -0
  127. package/src/utils/httpEnum.ts +31 -0
  128. package/src/utils/image.ts +21 -0
  129. package/src/utils/index.ts +111 -0
  130. package/src/utils/request.ts +139 -0
  131. package/src/utils/requestCancelHandle.ts +67 -0
  132. package/stylelint.config.js +87 -0
  133. package/tsconfig.docs.json +11 -0
  134. package/tsconfig.json +30 -0
  135. package/typedoc.json +6 -0
  136. package/vite.config.ts +55 -0
@@ -0,0 +1,338 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { type addAcceptanceParams, addRecord, updateRecord } from '@/api/installtion';
4
+ import { type ValidationRules } from '@/utils/form';
5
+ import { acceptanceStatusEnum, installWorkNodeOptions, installWorkNodeEnum
6
+ } from '@/dict/installtion';
7
+ import { useInstalltionStoreHook } from '@/stores/modules/installtion';
8
+ import { isArray } from 'lodash-es';
9
+ import { useForm } from '@/hooks/useForm';
10
+ import SinglePick from '@/components/SinglePick.vue';
11
+ import Upload from '@/components/Upload.vue';
12
+ import RippleBtn from '@/components/RippleBtn.vue';
13
+ import { FILE_UPLOAD_CONFIG, scrollIdPrefix } from '@/config/app';
14
+ defineOptions({
15
+ name: "addRecord"
16
+ });
17
+ const InstalltionStore = useInstalltionStoreHook();
18
+ const followInfo = computed(() => InstalltionStore.info);// 获取工单信息,响应式
19
+ const workNodeOptions = installWorkNodeOptions.filter((item: any) => item.value !== installWorkNodeEnum.ACCEPT)
20
+ // 定义校验规则
21
+ const validationRules: ValidationRules<Partial<addAcceptanceParams>> = {
22
+ description: {
23
+ required: true,
24
+ message: '请填写记录说明',
25
+ },
26
+ status: {
27
+ required: true,
28
+ message: '请选择状态',
29
+ },
30
+ imageList: { required: false, validatFunc: (d): any => {
31
+ return new Promise((resolve, reject) => {
32
+ if(isArray(d) && d.length > 0) {
33
+ resolve(true)
34
+ } else {
35
+ reject('请上传至少一张图片')
36
+ }
37
+ })
38
+ }, message: '请上传至少一张图片' }
39
+ }
40
+
41
+ const initialData = {
42
+ status: acceptanceStatusEnum.YES
43
+ };
44
+ let timer: number | null = null
45
+ const focusState = reactive({
46
+ description: false,
47
+ })
48
+ const state = reactive({
49
+ deviceId: '',
50
+ workOrderCode: '',
51
+ deviceCode: '',
52
+ location: {
53
+ latitude: 0,
54
+ longitude: 0,
55
+ address: '',
56
+ name: ''
57
+ },
58
+ successSubmit: false,
59
+ showLocationTips: false,
60
+ })
61
+ const { formData, errState, setFormItem, validateForm } = useForm<Omit<addAcceptanceParams, 'id'>>(
62
+ initialData as Omit<addAcceptanceParams, 'id'>,
63
+ validationRules,
64
+ false,
65
+ )
66
+ const changeValue = (key: keyof Omit<addAcceptanceParams, 'id'>, detail: any) => {
67
+ setFormItem(key, detail.value);
68
+ }
69
+ const getLocation = () => {
70
+ uni.chooseLocation({
71
+ success: (res) => {
72
+ state.location = {
73
+ latitude: res.latitude,
74
+ longitude: res.longitude,
75
+ address: res.address,
76
+ name: res.name
77
+ }
78
+ state.showLocationTips = false;
79
+ },
80
+ fail: (err) => {
81
+ console.log(err);
82
+ uni.showToast({ title: '获取位置失败', icon: 'error' })
83
+ }
84
+ })
85
+ }
86
+ const submitForm = async () => {
87
+ if (state.successSubmit) {
88
+ uni.showToast({ title: '请勿重复提交', icon: 'error' })
89
+ return;
90
+ }
91
+ const validateResult = await validateForm();
92
+ if (!state.location.address) {
93
+ state.showLocationTips = true;
94
+ return;
95
+ }
96
+ let res;
97
+ const params = {
98
+ ...formData, planDeviceId: state.deviceId,
99
+ imageList: formData.imageList?.map((item: any) => item.id) || [],
100
+ deviceLocation: JSON.stringify(state.location),
101
+ workOrderCode: state.workOrderCode,
102
+ deviceCode: state.deviceCode,
103
+ }
104
+ if (!validateResult) {
105
+ return;
106
+ }
107
+ // 如果工单status为已完成,则编辑
108
+ if (followInfo.value.status >= formData.workNodeFlag) {
109
+ //查找编辑ID
110
+ const item = followInfo.value.recordList.find((item: any) => item.workNodeFlag === formData.workNodeFlag)
111
+ res = await updateRecord({
112
+ ...params,
113
+ id: item?.id
114
+ })
115
+ } else {
116
+ res = await addRecord(params, {
117
+ permActionName: formData.workNodeFlag === installWorkNodeEnum.READY ? 'installWorkReady' : 'installWorkComplete',
118
+ })
119
+ }
120
+ if (res) {
121
+ uni.showToast({ title: '提交成功', icon: 'success' })
122
+ state.successSubmit = true;
123
+ timer = setTimeout(() => {
124
+ uni.navigateBack()
125
+ }, 2000)
126
+ uni.$emit('installtionOrderDetail-updated');
127
+ }
128
+ }
129
+ onLoad(async (event: any) => {
130
+ const { deviceId, workOrderCode, deviceCode } = event
131
+ state.deviceId = deviceId;
132
+ state.workOrderCode = workOrderCode;
133
+ state.deviceCode = deviceCode
134
+ state.successSubmit = false;
135
+ setFormItem('workNodeFlag', parseInt(event.status))
136
+ })
137
+ onUnmounted(() => {
138
+ if (timer) {
139
+ clearInterval(timer);
140
+ timer = null;
141
+ }
142
+ })
143
+ </script>
144
+
145
+ <template>
146
+ <view class="content">
147
+ <view class="form-content">
148
+ <view class="uni-form-item flex-space">
149
+ <view class="l-label">
150
+ 工作节点
151
+ </view>
152
+ <SinglePick class="select-box"
153
+ :value="formData.workNodeFlag"
154
+ :range="workNodeOptions"
155
+ value-key="value" disabled
156
+ label-key="label" showRightIcon placeholder="请选择" @change="(v: any) => changeValue('workNodeFlag', { value: v })" />
157
+ </view>
158
+ <view class="uni-form-item flex-space">
159
+ <view class="l-label">
160
+ 状态
161
+ </view>
162
+ <SinglePick class="select-box" :value="formData.status" disabled
163
+ :range="[{ label: '正常', value: acceptanceStatusEnum.YES }]" value-key="value"
164
+ label-key="label" showRightIcon placeholder="请选择" @change="(v: any) => changeValue('status', { value: v })" />
165
+ </view>
166
+ <view class="err-text" v-if="errState.status">{{ errState.status }}</view>
167
+ </view>
168
+ <view class="form-content" :id="`${scrollIdPrefix}description`">
169
+ <view class="l-label">
170
+ <view class="flag">*</view>记录说明
171
+ </view>
172
+ <textarea class="uni-textarea" placeholder="请填写" :value="formData.description" maxlength="1000"
173
+ @input="(e: any) => changeValue('description', e.detail)" @focus="focusState.description = true"
174
+ @blur="focusState.description = false" placeholder-class="holderClass" />
175
+ <view class="annotation">{{ formData.description?.length || 0 }}/1000</view>
176
+ <view class="err-text" v-if="errState.description">{{ errState.description }}</view>
177
+ </view>
178
+ <view class="form-content">
179
+ <view class="uni-form-item flex-space flex-col">
180
+ <view class="l-label"><view class="flag">*</view>照片上传</view>
181
+ <upload class="upload-box" :class="errState.imageList ? 'errorUpload': ''" :files="formData.imageList" :maxCount="6" :showUpload="true" :size="176"
182
+ :moduleName="FILE_UPLOAD_CONFIG.after_order.moduleName"
183
+ @change="(files: any) => changeValue('imageList', { value: files })" />
184
+ <view class="err-text" v-if="errState.imageList">{{ errState.imageList }}</view>
185
+ </view>
186
+ </view>
187
+ <view class="form-content">
188
+ <view class="uni-form-item flex-space">
189
+ <view class="l-label">
190
+ <view class="flag">*</view>设备定位
191
+ </view>
192
+ <view class="form-value" @click="getLocation">
193
+ {{ state.location.address ? `${state.location.address}${state.location.name}` : '点击上传设备位置'}}</view>
194
+ </view>
195
+ <view class="err-text" v-if="state.showLocationTips">请定位设备位置</view>
196
+ </view>
197
+ <RippleBtn @click="submitForm" :customStyle="'margin-top: 48rpx;padding-bottom: 10rpx'">
198
+ <view class="submit-btn">提交</view>
199
+ </RippleBtn>
200
+ </view>
201
+ </template>
202
+ <style scoped>
203
+ .content {
204
+ background-color: #F5F5F5;
205
+ min-height: 100vh;
206
+ padding: 32rpx;
207
+
208
+ .form-content {
209
+ margin-bottom: 32rpx;
210
+ background-color: #fff;
211
+ border-radius: 16rpx;
212
+ padding: 0 32rpx;
213
+ position: relative;
214
+ }
215
+
216
+ .form-value {
217
+ margin-right: 24rpx;
218
+ }
219
+
220
+ .uni-form-item {
221
+ display: flex;
222
+ }
223
+
224
+ .flex-space {
225
+ display: flex;
226
+ justify-content: space-between;
227
+ align-items: center;
228
+ color: rgba(0, 0, 0, 0.6);
229
+
230
+ &:not(:last-child) {
231
+ border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
232
+ }
233
+ }
234
+
235
+ .l-label {
236
+ display: flex;
237
+ align-items: center;
238
+ padding: 32rpx 0;
239
+ font-size: 32rpx;
240
+ color: rgba(0, 0, 0, 0.6);
241
+ line-height: 48rpx;
242
+ min-width: 188rpx;
243
+ white-space: nowrap;
244
+ .flag {
245
+ font-size: 32rpx;
246
+ color: #E34D59;
247
+ line-height: 38rpx;
248
+ }
249
+ }
250
+
251
+ .annotation {
252
+ position: absolute;
253
+ bottom: 32rpx;
254
+ right: 32rpx;
255
+ font-weight: 400;
256
+ font-size: 24rpx;
257
+ color: rgba(0, 0, 0, 0.26);
258
+ line-height: 28rpx;
259
+ text-align: right;
260
+ }
261
+
262
+ .flex-col {
263
+ flex-direction: column;
264
+ align-items: flex-start;
265
+ }
266
+
267
+ .uni-textarea {
268
+ width: 100%;
269
+ line-height: 48rpx;
270
+ height: 288rpx;
271
+ font-size: 28rpx;
272
+ }
273
+
274
+ .select-box {
275
+ /* 确保SinglePick组件内的容器占满剩余空间 */
276
+ flex: 1;
277
+ min-width: 0;
278
+ /* 防止内容溢出 */
279
+ }
280
+
281
+ .upload-box {
282
+ display: flex;
283
+ flex-wrap: wrap;
284
+ gap: 20rpx;
285
+ padding-bottom: 32rpx;
286
+
287
+ .view-img {
288
+ width: 176rpx;
289
+ height: 176rpx;
290
+ }
291
+ }
292
+ .errorUpload{
293
+ padding-bottom: 0;
294
+ }
295
+ .common-picker {
296
+ flex: 1;
297
+ text-align: right;
298
+ }
299
+
300
+ .dateSelect-box {
301
+ display: flex;
302
+ align-items: center;
303
+ flex-direction: row-reverse;
304
+ }
305
+
306
+ .right-icon {
307
+ margin-left: 16rpx;
308
+ width: 32rpx;
309
+ height: 32rpx;
310
+ text-align: right;
311
+ }
312
+
313
+ .err-text {
314
+ padding: 16rpx 0 16rpx 0;
315
+ font-size: 24rpx;
316
+ color: rgb(250, 100, 100);
317
+ }
318
+
319
+ .submit-btn {
320
+ box-sizing: border-box;
321
+ width: 686rpx;
322
+ padding: 24rpx 0 26rpx 0;
323
+ line-height: 58rpx;
324
+ font-family: Source Han Sans SC, Source Han Sans SC;
325
+ font-weight: 400;
326
+ font-size: 40rpx;
327
+ color: rgba(255, 255, 255, 0.9);
328
+ background: linear-gradient(270deg, #1D9DE9 0%, #1D6FE9 100%);
329
+ box-shadow: 0px 16rpx 16rpx 2rpx rgba(29, 157, 233, 0.1);
330
+ border-radius: 52rpx;
331
+ }
332
+ }
333
+
334
+ .holderClass {
335
+ font-size: 28rpx;
336
+ color: rgba(0, 0, 0, 0.25);
337
+ }
338
+ </style>
@@ -0,0 +1,220 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import Record from './Record.vue'
4
+ import { installWorkNodeMap, acceptanceStatusEnum,
5
+ acceptanceStatusMap, deviceStatusEnum } from '@/dict/installtion'
6
+ import { getInstalltionDeviceDetail } from '@/api/installtion'
7
+ import { useInstalltionStoreHook } from '@/stores/modules/installtion';
8
+ import RippleBtn from '@/components/RippleBtn.vue';
9
+ import Timeline from '@/components/Timeline.vue';
10
+ import { UPLOAD_HOST } from '@/config/app'
11
+ import { formatDateTime } from '@/utils/index'
12
+ import { useUserStore } from '@/stores/modules/user';
13
+
14
+ type deviceRecord = { deviceCode: string; deviceName: string; address: string; status: number; }
15
+ defineOptions({
16
+ name: "orderDetail"
17
+ });
18
+ const InstalltionStore = useInstalltionStoreHook();
19
+ const useStore = useUserStore();
20
+ const state = reactive({
21
+ info: {
22
+ recordList: [] as any,
23
+ deviceList: [] as Array<deviceRecord>,
24
+ status: deviceStatusEnum.ORDER_BEGIN,
25
+ id: '',
26
+ workOrderCode: '',
27
+ deviceCode: '',
28
+ } as any,
29
+ deviceId: '',
30
+ type: '',
31
+ })
32
+ const toAddRecord = () => {
33
+ if(!hasSubmitPerms()) {
34
+ uni.showToast({ title: '暂无新增工作记录权限,请向管理员申请', icon: 'none'})
35
+ return
36
+ }
37
+ const { id, status, workOrderCode, deviceCode } = state.info;
38
+ const params = `deviceId=${id}&status=${Number(status) + 1}&workOrderCode=${workOrderCode}&deviceCode=${deviceCode}`
39
+ // 前往验收
40
+ if (Number(status) === deviceStatusEnum.ORDER_COMPLETE) {
41
+ uni.redirectTo({ url: `/pages/installtion/addAcceptance?${params}` })
42
+ return;
43
+ }
44
+ uni.redirectTo({
45
+ url: `/pages/installtion/addRecord?${params}`
46
+ })
47
+ }
48
+ const fetchData = async (id: number | string) => {
49
+ const res = await getInstalltionDeviceDetail({ id });
50
+ const { deviceCode, deviceName, address, status,...other } = res;
51
+ state.info = {
52
+ ...other,
53
+ status,
54
+ deviceCode,
55
+ deviceList: [{
56
+ deviceCode,
57
+ deviceName,
58
+ address,
59
+ status,
60
+ }],
61
+ };
62
+ InstalltionStore.setSystemInfo(res);
63
+ }
64
+ onLoad( async(event: any) => {
65
+ fetchData(event.deviceId);
66
+ state.deviceId = event.deviceId;
67
+ state.type = event.type;
68
+ })
69
+ onMounted(async () => {
70
+ uni.$on('installtionOrderDetail-updated', () => {
71
+ fetchData(state.deviceId); // 刷新数据
72
+ uni.$emit('installtionList-updated');
73
+ });
74
+ })
75
+ const hasSubmitPerms = () => {
76
+ const { status } = state.info;
77
+ if (status === deviceStatusEnum.BEGIN) {
78
+ return useStore.hasPerms('installWorkReady');
79
+ }
80
+ if (status === deviceStatusEnum.READY) {
81
+ return useStore.hasPerms('installWorkComplete');
82
+ }
83
+ if (status === deviceStatusEnum.ORDER_COMPLETE) {
84
+ return useStore.hasPerms('installWorkAcceptance');
85
+ }
86
+ return false
87
+ }
88
+ const submitText = computed(() => {
89
+ const { status } = state.info;
90
+ if (status === deviceStatusEnum.COMPLETE) {
91
+ return '新增验收记录';
92
+ }
93
+ return '新增工作记录';
94
+ })
95
+ const showSubmitBtn = computed(() => {
96
+ const { status } = state.info;
97
+ if (state.type === 'toAcceptance' && status === deviceStatusEnum.COMPLETE) {
98
+ return true;
99
+ }
100
+ if (status === deviceStatusEnum.COMPLETE) {
101
+ return false;
102
+ }
103
+ if (status === deviceStatusEnum.ACCEPT) {
104
+ return false;
105
+ }
106
+ return true;
107
+ })
108
+ const handlePreview = (list: [], index: number) => {
109
+ const urls = list.map(file => UPLOAD_HOST + file.attachmentUrl)
110
+ uni.previewImage({
111
+ current: index, // 当前显示图片的索引
112
+ urls: urls, // 需要预览的图片链接列表
113
+ indicator: 'number', // 显示页码指示器
114
+ loop: false // 不循环预览
115
+ })
116
+ }
117
+ </script>
118
+
119
+ <template>
120
+ <view class="content">
121
+ <view class="record-box">
122
+ <Record :data="state.info" type="positioning" />
123
+ </view>
124
+ <view class="timeline-box" v-if="state.info.recordList.length > 0">
125
+ <Timeline :items="state.info.recordList">
126
+ <template #content="{ item }">
127
+ <view class="title">{{ installWorkNodeMap[item.workNodeFlag]?.label }}
128
+ <view class="status">{{ acceptanceStatusMap[1]}}</view>
129
+ </view>
130
+ <view class="desc">{{item.userName && `${item.userName}: `}}{{ item.description }}</view>
131
+ <img v-for="(imgItem, index) in item?.imageList" :key="imgItem.attachmentId"
132
+ class="img" :src="UPLOAD_HOST + imgItem.attachmentUrl"
133
+ @click="handlePreview(item.imageList, index)"
134
+ />
135
+ <view class="time">{{ formatDateTime(item.createTime) }}</view>
136
+ </template>
137
+ </Timeline>
138
+ </view>
139
+ <view class="footer">
140
+ <RippleBtn :customStyle="'padding-bottom: 10rpx'"
141
+ v-if="showSubmitBtn">
142
+ <view class="submit-btn" @click="toAddRecord">{{ submitText }}</view>
143
+ </RippleBtn>
144
+ </view>
145
+ </view>
146
+ </template>
147
+ <style scoped lang="less">
148
+ .content {
149
+ padding-bottom: 120rpx;
150
+ background-color: #F5F5F5;
151
+ min-height: 100vh;
152
+
153
+ .footer {
154
+ display: flex;
155
+ justify-content: center;
156
+ margin-top: 334rpx;
157
+ }
158
+
159
+ .record-box {
160
+ margin-bottom: 32rpx;
161
+ padding: 12rpx;
162
+ background-color: #fff;
163
+ }
164
+ .timeline-box {
165
+ padding: 48rpx;
166
+ background-color: #fff;
167
+ .title {
168
+ display: flex;
169
+ align-items: center;
170
+ margin-bottom: 16rpx;
171
+ font-size: 28rpx;
172
+ color: #000000;
173
+ line-height: 40rpx;
174
+ .status{
175
+ display: block;
176
+ margin-left: 16rpx;
177
+ padding: 4rpx 16rpx 6rpx 16rpx;
178
+ line-height: 30rpx;
179
+ font-size: 20rpx;
180
+ background: #337DEB;
181
+ border-radius: 8rpx;
182
+ color: #fff;
183
+ }
184
+ }
185
+ .desc{
186
+ margin-bottom: 16rpx;
187
+ font-size: 28rpx;
188
+ color: #000000;
189
+ line-height: 40rpx;
190
+ }
191
+ .img{
192
+ margin-bottom: 16rpx;
193
+ margin-right: 16rpx;
194
+ width: 176rpx;
195
+ height: 176rpx;
196
+ border-radius: 8rpx;
197
+ }
198
+ .time{
199
+ padding-bottom: 36rpx;
200
+ font-size: 28rpx;
201
+ color: rgba(0, 0, 0, 0.4);
202
+ line-height: 34rpx;
203
+ }
204
+ }
205
+ .submit-btn {
206
+ box-sizing: border-box;
207
+ width: 686rpx;
208
+ padding: 24rpx 0 26rpx 0;
209
+ line-height: 58rpx;
210
+ font-family: Source Han Sans SC, Source Han Sans SC;
211
+ font-weight: 600;
212
+ font-size: 40rpx;
213
+ color: rgba(255, 255, 255, 0.9);
214
+ background: linear-gradient(270deg, #1D9DE9 0%, #1D6FE9 100%);
215
+ box-shadow: 0px 16rpx 16rpx 2rpx rgba(29, 157, 233, 0.1);
216
+ border-radius: 52rpx;
217
+ }
218
+ }
219
+
220
+ </style>
@@ -0,0 +1,100 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { getInstalltionOrderList } from '@/api/installtion'
4
+ import Record from './Record.vue'
5
+ import LoadingView from '@/components/Loading.vue';
6
+ import Skeleton from '@/components/Skeleton.vue';
7
+ import Empty from '@/components/Empty.vue';
8
+ import { throttle } from 'lodash-es';
9
+ defineOptions({
10
+ name: "installtionList"
11
+ });
12
+ const state = reactive({
13
+ itemDatas: [],
14
+ current: 1,
15
+ size: 5,
16
+ loadMore: false,
17
+ hasMore: true,
18
+ screenHeight: uni.getSystemInfoSync().windowHeight,
19
+ triggerRatio: 0.3, // 设置触发比例为屏幕高度的30%
20
+ loadingTop: false, // 顶部下拉刷新
21
+ customerCode: '',
22
+ hasSearch: false, // 是否有搜索过
23
+ });
24
+ const loadData = async (page?: number) => {
25
+ if (state.loadMore) return;
26
+ state.loadMore = true;
27
+ const { records, current } = await getInstalltionOrderList({
28
+ size: state.size,
29
+ current: page || state.current,
30
+ });
31
+ state.loadMore = false;
32
+ if (!records) {
33
+ return
34
+ }
35
+ if (page === 1) {
36
+ state.itemDatas = records;
37
+ } else {
38
+ state.itemDatas = [...state.itemDatas,...records];
39
+ }
40
+ state.current = current;
41
+ state.hasMore = records.length > 0;
42
+ if (!state.hasSearch) {
43
+ state.hasSearch = true;
44
+ }
45
+ }
46
+ // 滚动到底部加载更多
47
+ const onScrollToLower = throttle(() => {
48
+ if (!state.loadMore && state.hasMore) {
49
+ state.current++;
50
+ nextTick(() => {
51
+ loadData(state.current);
52
+ })
53
+ }
54
+ }, 500);
55
+ const toDetail = (item: any) => {
56
+ uni.navigateTo({ url: `/pages/installtion/orderDetail?deviceId=${item.id}` })
57
+ }
58
+ onMounted(async () => {
59
+ loadData(1);
60
+ uni.$on('installtionList-updated', () => {
61
+ loadData(1); // 刷新数据
62
+ });
63
+ })
64
+ </script>
65
+
66
+ <template>
67
+ <view class="content">
68
+ <scroll-view class="scroll-view" scroll-y style="height: 100vh;" bounces show-scrollbar scroll-with-animation
69
+ @scrolltolower="onScrollToLower" :lower-threshold="`${state.screenHeight * state.triggerRatio}`">
70
+ <view class="scroll-box">
71
+ <view v-for="item in state.itemDatas" :key="item.planId" class="record-box">
72
+ <Record :data="item" @clickDevice="toDetail" />
73
+ </view>
74
+ <view class="skeleton-box" v-if="!state.hasSearch">
75
+ <Skeleton class="skeleton-container"></Skeleton>
76
+ <Skeleton class="skeleton-container"></Skeleton>
77
+ <Skeleton class="skeleton-container"></Skeleton>
78
+ </view>
79
+ <Empty v-if="state.itemDatas.length === 0 && state.hasSearch" />
80
+ <LoadingView v-if="state.loadMore" class="loading-indicator" :size="50"></LoadingView>
81
+ <view v-if="state.itemDatas.length > 0 && !state.hasMore" class="noMore">没有更多数据了</view>
82
+ </view>
83
+ </scroll-view>
84
+ </view>
85
+ </template>
86
+ <style scoped lang="less">
87
+ .content {
88
+ background-color: #F5F5F5;
89
+ min-height: 100vh;
90
+ box-sizing: border-box;
91
+ .scroll-box{
92
+ padding: 32rpx;
93
+ }
94
+ .record-box{
95
+ border-radius: 16rpx;
96
+ overflow: hidden;
97
+ margin-bottom: 32rpx;
98
+ }
99
+ }
100
+ </style>