af-mobile-client-vue3 1.0.70 → 1.0.71

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.
@@ -14,6 +14,7 @@ import { defineEmits, nextTick, reactive, ref, watch } from 'vue'
14
14
  import { getConfigByNameWithoutIndexedDB } from '@af-mobile-client-vue3/services/api/common'
15
15
  import Uploader from '@af-mobile-client-vue3/components/core/Uploader/index.vue'
16
16
  import XReportFormJsonRender from '@af-mobile-client-vue3/components/data/XReportForm/XReportFormJsonRender.vue'
17
+ import XSignature from '@af-mobile-client-vue3/components/data/XSignature/index.vue'
17
18
 
18
19
  // ------------------------- 类型定义 -------------------------
19
20
  interface configDefine {
@@ -416,6 +417,11 @@ function formatConfigToForm(config: configDefine): void {
416
417
  tempObj.text = row[j].text
417
418
  tempObj.type = 'curDateInput'
418
419
  break
420
+ case 'signature' :
421
+ tempObj.dataIndex = row[j].dataIndex
422
+ tempObj.text = row[j].text
423
+ tempObj.type = 'signature'
424
+ break
419
425
  case 'inputs' :
420
426
  if (!tempObj.format)
421
427
  tempObj.format = ''
@@ -824,6 +830,19 @@ function getNow() {
824
830
  </template>
825
831
  </van-field>
826
832
  </van-cell-group>
833
+ <!-- signature -->
834
+ <van-cell-group v-else-if="row.type === 'signature'" inset style="margin-top: 4vh">
835
+ <van-field
836
+ :label="row.valueText ? `${row.valueText}:` : ''"
837
+ readonly
838
+ rows="1"
839
+ autosize
840
+ >
841
+ <template #button>
842
+ <XSignature v-model="activatedConfig.data[row.dataIndex]" />
843
+ </template>
844
+ </van-field>
845
+ </van-cell-group>
827
846
  <!-- inputs -->
828
847
  <van-cell-group v-else-if="row.type === 'inputs'" inset :title="row.label">
829
848
  <template v-if="row.inputReadOnly === true">
@@ -0,0 +1,285 @@
1
+ <script setup lang="ts">
2
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
3
+ import {
4
+ Button as VanButton,
5
+ Icon as VanIcon,
6
+ Popup as VanPopup,
7
+ Signature as VanSignature,
8
+ showToast,
9
+ } from 'vant'
10
+
11
+ interface Props {
12
+ /** 签名图片的 base64 字符串 */
13
+ modelValue?: string
14
+ /** 签名笔画的宽度,默认为 3 */
15
+ lineWidth?: number
16
+ /** 签名笔画的颜色,默认为黑色 '#000' */
17
+ strokeStyle?: string
18
+ /** 签名画布的背景颜色,默认为白色 '#fff' */
19
+ backgroundColor?: string
20
+ /** 未签名时按钮显示的文字,默认为 '签名' */
21
+ buttonText?: string
22
+ /** 自定义 CSS 类名 */
23
+ customClass?: string
24
+ /** 是否隐藏底部按钮组,默认为 false */
25
+ hideButtons?: boolean
26
+ /** 按钮尺寸,可选值为 'small' | 'normal' | 'large',默认为 'normal' */
27
+ buttonSize?: 'small' | 'normal' | 'large'
28
+ /** 确认按钮文字,默认为 '确认' */
29
+ confirmButtonText?: string
30
+ /** 清除按钮文字,默认为 '清除' */
31
+ clearButtonText?: string
32
+ /** 是否显示签名预览图,默认为 true */
33
+ showPreview?: boolean
34
+ /** 签名完成后是否显示重新签名按钮,默认为 false */
35
+ showButtonAfterSigned?: boolean
36
+ }
37
+
38
+ withDefaults(defineProps<Props>(), {
39
+ modelValue: '',
40
+ lineWidth: 3,
41
+ strokeStyle: '#000',
42
+ backgroundColor: '#fff',
43
+ buttonText: '签名',
44
+ customClass: '',
45
+ hideButtons: false,
46
+ buttonSize: 'normal',
47
+ confirmButtonText: '确认',
48
+ clearButtonText: '清除',
49
+ showPreview: true,
50
+ showButtonAfterSigned: false,
51
+ })
52
+
53
+ const emit = defineEmits<{
54
+ 'update:modelValue': [value: string]
55
+ 'save': [image: string]
56
+ 'clear': []
57
+ }>()
58
+
59
+ interface SignatureResult {
60
+ image: string
61
+ }
62
+
63
+ const showSignature = ref(false)
64
+
65
+ function onSubmit({ image }: SignatureResult) {
66
+ if (!image) {
67
+ showToast('获取签名失败')
68
+ return
69
+ }
70
+ emit('update:modelValue', image)
71
+ emit('save', image)
72
+ showSignature.value = false
73
+ }
74
+
75
+ function onClear() {
76
+ emit('update:modelValue', '')
77
+ emit('clear')
78
+ showSignature.value = false
79
+ }
80
+
81
+ const signatureRef = ref()
82
+
83
+ function handleSubmit() {
84
+ try {
85
+ signatureRef.value?.submit()
86
+ }
87
+ catch (error) {
88
+ showToast('签名提交失败')
89
+ console.error('Signature submit error:', error)
90
+ }
91
+ }
92
+
93
+ const popupStyle = computed(() => ({
94
+ width: '100%',
95
+ height: window.innerHeight > window.innerWidth ? 'auto' : '100%',
96
+ }))
97
+
98
+ function handleOrientationChange() {
99
+ if (showSignature.value) {
100
+ nextTick(() => {
101
+ signatureRef.value?.resize()
102
+ })
103
+ }
104
+ }
105
+
106
+ onMounted(() => {
107
+ window.addEventListener('orientationchange', handleOrientationChange)
108
+ })
109
+
110
+ onBeforeUnmount(() => {
111
+ window.removeEventListener('orientationchange', handleOrientationChange)
112
+ })
113
+ </script>
114
+
115
+ <template>
116
+ <div class="x-signature" role="application" aria-label="签名面板">
117
+ <div class="signature-content">
118
+ <div v-if="modelValue && showPreview" class="signature-preview" @click="showSignature = true">
119
+ <img :src="modelValue" alt="签名预览">
120
+ </div>
121
+ <VanButton
122
+ v-if="!modelValue || showButtonAfterSigned"
123
+ type="primary"
124
+ size="small"
125
+ aria-label="打开签名面板"
126
+ @click="showSignature = true"
127
+ >
128
+ {{ modelValue ? '重新签名' : buttonText }}
129
+ </VanButton>
130
+ </div>
131
+
132
+ <VanPopup v-model:show="showSignature" class="signature-popup" :style="popupStyle">
133
+ <div class="signature-wrapper">
134
+ <VanSignature
135
+ ref="signatureRef"
136
+ :line-width="lineWidth"
137
+ :stroke-style="strokeStyle"
138
+ :background-color="backgroundColor"
139
+ @submit="onSubmit"
140
+ @clear="onClear"
141
+ />
142
+ <!-- 自定义浮动按钮,仅在横屏时显示 -->
143
+ <div class="custom-buttons">
144
+ <VanButton round size="small" aria-label="清除签名" @click="onClear">
145
+ <VanIcon name="delete" />
146
+ </VanButton>
147
+ <VanButton round type="primary" size="small" @click="handleSubmit">
148
+ <VanIcon name="success" />
149
+ </VanButton>
150
+ </div>
151
+ </div>
152
+ </VanPopup>
153
+ </div>
154
+ </template>
155
+
156
+ <style scoped lang="less">
157
+ .x-signature {
158
+ width: 100%;
159
+
160
+ .signature-content {
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 12px;
164
+ }
165
+
166
+ .signature-preview {
167
+ flex: 1;
168
+ border: 1px solid #eee;
169
+ border-radius: 4px;
170
+ padding: 8px;
171
+ display: flex;
172
+ align-items: center;
173
+
174
+ img {
175
+ max-width: 100%;
176
+ max-height: 36px;
177
+ height: auto;
178
+ display: block;
179
+ }
180
+ }
181
+
182
+ .van-button {
183
+ flex-shrink: 0;
184
+ }
185
+
186
+ :deep(.van-popup) {
187
+ max-width: none !important;
188
+ width: 100% !important;
189
+ top: 20% !important;
190
+ transform: none !important;
191
+ transition: all 0.3s ease-in-out;
192
+ }
193
+
194
+ .signature-wrapper {
195
+ width: 100%;
196
+ height: 100%;
197
+ position: relative;
198
+
199
+ :deep(.van-signature) {
200
+ width: 100% !important;
201
+ height: 100% !important;
202
+ max-width: none !important;
203
+ }
204
+
205
+ // 默认隐藏自定义按钮
206
+ .custom-buttons {
207
+ display: none;
208
+ }
209
+ }
210
+
211
+ // 横屏模式样式
212
+ @media screen and (orientation: landscape) {
213
+ :deep(.van-popup) {
214
+ top: 0 !important;
215
+ right: 0 !important;
216
+ bottom: 0 !important;
217
+ left: 0 !important;
218
+ width: 100% !important;
219
+ height: 100% !important;
220
+ display: flex;
221
+ flex-direction: column;
222
+ }
223
+
224
+ .signature-wrapper {
225
+ flex: 1;
226
+ display: flex;
227
+ justify-content: center;
228
+ align-items: center;
229
+ padding: env(safe-area-inset-top) env(safe-area-inset-right)
230
+ env(safe-area-inset-bottom) env(safe-area-inset-left);
231
+
232
+ :deep(.van-signature) {
233
+ // 隐藏原有的底部按钮
234
+ .van-signature__footer {
235
+ display: none;
236
+ }
237
+ }
238
+
239
+ :deep(.van-signature__content) {
240
+ height: 95% !important;
241
+ }
242
+
243
+ // 显示自定义浮动按钮
244
+ .custom-buttons {
245
+ position: absolute;
246
+ right: 32px;
247
+ bottom: 32px;
248
+ display: flex;
249
+ gap: 16px;
250
+
251
+ .van-button {
252
+ width: 48px !important;
253
+ height: 48px !important;
254
+ border-radius: 50% !important;
255
+ padding: 0 !important;
256
+ display: flex;
257
+ align-items: center;
258
+ justify-content: center;
259
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
260
+ transition: transform 0.2s ease;
261
+
262
+ &--default {
263
+ background: #fff;
264
+ }
265
+
266
+ &:active {
267
+ transform: scale(0.95);
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ // 优化按钮触摸反馈
275
+ .custom-buttons {
276
+ .van-button {
277
+ transition: transform 0.2s ease;
278
+
279
+ &:active {
280
+ transform: scale(0.95);
281
+ }
282
+ }
283
+ }
284
+ }
285
+ </style>
@@ -10,6 +10,8 @@ import PageLayout from '@af-mobile-client-vue3/layout/PageLayout.vue'
10
10
  import login from '@af-mobile-client-vue3/views/user/login/index.vue'
11
11
  import NotFound from '@af-mobile-client-vue3/views/common/NotFound.vue'
12
12
  import SingleLayout from '@af-mobile-client-vue3/layout/SingleLayout.vue'
13
+ import XFormGroupView from '@af-mobile-client-vue3/views/component/XFormGroupView/index.vue'
14
+ import XSignatureView from '@af-mobile-client-vue3/views/component/XSignatureView/index.vue'
13
15
 
14
16
  const routes: Array<RouteRecordRaw> = [
15
17
  {
@@ -46,6 +48,11 @@ const routes: Array<RouteRecordRaw> = [
46
48
  name: 'XCellDetailView',
47
49
  component: XCellDetailView,
48
50
  },
51
+ {
52
+ path: '/Component/XFormGroupView',
53
+ name: 'XFormGroupView',
54
+ component: XFormGroupView,
55
+ },
49
56
  {
50
57
  path: '/Component/XReportFormView',
51
58
  name: 'XReportFormView',
@@ -61,6 +68,11 @@ const routes: Array<RouteRecordRaw> = [
61
68
  name: 'EvaluateRecordView',
62
69
  component: EvaluateRecordView,
63
70
  },
71
+ {
72
+ path: '/Component/XSignatureView',
73
+ name: 'XSignatureView',
74
+ component: XSignatureView,
75
+ },
64
76
  ],
65
77
  },
66
78
  {
@@ -48,7 +48,6 @@ function hasAnyItem(required, source, filter): boolean {
48
48
  * @param route 路由
49
49
  * @param permissions 用户权限集合
50
50
  * @param roles 用户角色集合
51
- * @returns {boolean}
52
51
  */
53
52
  function hasAuthority(route, permissions, roles) {
54
53
  // TODO 此处判断可能有问题
@@ -0,0 +1,13 @@
1
+ export function executeStrFunction(funcString, args) {
2
+ // 使用 eval 执行传入的函数字符串
3
+ // eslint-disable-next-line no-eval
4
+ return eval(`(${funcString})`)(...args)
5
+ }
6
+
7
+ export function executeStrFunctionByContext(context, fnStr, args) {
8
+ // 使用 new Function 创建函数,并绑定 context 作为 this
9
+ // eslint-disable-next-line no-new-func
10
+ const fn = new Function(`return (${fnStr});`)()
11
+ // 使用 bind 绑定 context,并立即调用
12
+ return fn.bind(context)(...args)
13
+ }
@@ -1,57 +1,96 @@
1
- <script setup lang="ts">
2
- import { ref } from 'vue'
3
- import XCellList from '@af-mobile-client-vue3/components/data/XCellList/index.vue'
4
- import NormalDataLayout from '@af-mobile-client-vue3/components/layout/NormalDataLayout/index.vue'
5
- import { useRouter } from 'vue-router'
6
-
7
- const router = useRouter()
8
-
9
- const idKey = ref('o_id')
10
- const operNameKey = ref('o_f_oper_name')
11
- const methodKey = ref('o_f_method')
12
- const requestMethodKey = ref('o_f_request_method')
13
- const operatorTypeKey = ref('o_f_operator_type')
14
- const operUrlKey = ref('o_f_oper_url')
15
- const operIpKey = ref('o_f_oper_ip')
16
- const costTimeKey = ref('o_f_cost_time')
17
- const operTimeKey = ref('o_f_oper_time')
18
-
19
- const titleKey = ref('o_f_title')
20
- const businessTypeKey = ref('o_f_business_type')
21
- const statusKey = ref('o_f_status')
22
-
23
-
24
- // const idKey = ref('t_id')
25
-
26
- // const configName = ref('测试crud-移动端')
27
- const configName = ref('lngPurchaseOrderMobileCRUD')
28
- const serviceName = ref('af-system')
29
- function toDetail(item) {
30
- console.log('item=====',item)
31
- router.push({
32
- name:'XFormView',
33
- params:{id: item.od_id,openid: 1},
34
- query:{formConfigName: configName, formServiceName: serviceName ,mode:'新增'},
35
- })
36
- }
37
- function event4(result){
38
- console.log(result)
39
- }
40
- </script>
41
-
42
- <template>
43
- <NormalDataLayout id="XCellListView" title="工作计划">
44
- <template #layout_content>
45
- <XCellList
46
- :config-name="configName"
47
- service-name="af-gaslink"
48
- :id-key="idKey"
49
- @audit="toDetail"
50
- @event4="event4"
51
- />
52
- </template>
53
- </NormalDataLayout>
54
- </template>
55
-
56
- <style scoped lang="less">
57
- </style>
1
+ <script setup lang="ts">
2
+ import { defineEmits, ref } from 'vue'
3
+ import XCellList from '@af-mobile-client-vue3/components/data/XCellList/index.vue'
4
+ import NormalDataLayout from '@af-mobile-client-vue3/components/layout/NormalDataLayout/index.vue'
5
+ import { useRouter } from 'vue-router'
6
+
7
+ // 定义事件
8
+ const emit = defineEmits(['deleteRow'])
9
+ // 访问路由
10
+ const router = useRouter()
11
+ // 获取默认值
12
+ const idKey = ref('o_id')
13
+ const configName = ref('crud_oper_log_manage')
14
+ const serviceName = ref('af-system')
15
+
16
+ // 跳转到详情页面
17
+ // function toDetail(item) {
18
+ // router.push({
19
+ // name: 'XCellDetailView',
20
+ // params: { id: item[idKey.value] }, // 如果使用命名路由,推荐使用路由参数而不是直接构建 URL
21
+ // query: {
22
+ // operName: item[operNameKey.value],
23
+ // method:item[methodKey.value],
24
+ // requestMethod:item[requestMethodKey.value],
25
+ // operatorType:item[operatorTypeKey.value],
26
+ // operUrl:item[operUrlKey.value],
27
+ // operIp:item[operIpKey.value],
28
+ // costTime:item[costTimeKey.value],
29
+ // operTime:item[operTimeKey.value],
30
+ //
31
+ // title: item[titleKey.value],
32
+ // businessType: item[businessTypeKey.value],
33
+ // status:item[statusKey.value]
34
+ // }
35
+ // })
36
+ // }
37
+
38
+ // 跳转到表单——以表单组来渲染纯表单
39
+ function toDetail(item) {
40
+ router.push({
41
+ name: 'XFormGroupView',
42
+ query: {
43
+ id: item[idKey.value],
44
+ },
45
+ })
46
+ }
47
+
48
+ // 新增功能
49
+ function addOption(totalCount) {
50
+ router.push({
51
+ name: 'XFormView',
52
+ params: { id: totalCount, openid: totalCount },
53
+ query: {
54
+ configName: configName.value,
55
+ serviceName: serviceName.value,
56
+ mode: '新增',
57
+ },
58
+ })
59
+ }
60
+
61
+ // 修改功能
62
+ function updateRow(result) {
63
+ router.push({
64
+ name: 'XFormView',
65
+ params: { id: result.o_id, openid: result.o_id },
66
+ query: {
67
+ configName: configName.value,
68
+ serviceName: serviceName.value,
69
+ mode: '修改',
70
+ },
71
+ })
72
+ }
73
+
74
+ // 删除功能
75
+ function deleteRow(result) {
76
+ emit('deleteRow', result.o_id)
77
+ }
78
+ </script>
79
+
80
+ <template>
81
+ <NormalDataLayout id="XCellListView" title="工作计划">
82
+ <template #layout_content>
83
+ <XCellList
84
+ :config-name="configName"
85
+ :id-key="idKey"
86
+ @to-detail="toDetail"
87
+ @add-option="addOption"
88
+ @update-row="updateRow"
89
+ @delete-row="deleteRow"
90
+ />
91
+ </template>
92
+ </NormalDataLayout>
93
+ </template>
94
+
95
+ <style scoped lang="less">
96
+ </style>
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ import XFormGroup from '@af-mobile-client-vue3/components/data/XFormGroup/index.vue'
3
+ import NormalDataLayout from '@af-mobile-client-vue3/components/layout/NormalDataLayout/index.vue'
4
+ import { onBeforeMount, ref } from 'vue'
5
+ import { showDialog } from 'vant'
6
+
7
+ // 纯表单
8
+ const serviceName = ref('af-linepatrol')
9
+
10
+ // 表单组
11
+ const configName = ref('lngChargeAuditMobileFormGroup')
12
+ // const serviceName = ref("af-gaslink")
13
+
14
+ const formData = ref({})
15
+ function submit(_result) {
16
+ showDialog({ message: '提交成功' }).then(() => {
17
+ history.back()
18
+ })
19
+ }
20
+
21
+ // 表单组——数据
22
+ // function initComponents () {
23
+ // runLogic('getlngChargeAuditMobileFormGroupData', {id: 29}, 'af-gaslink').then((res) => {
24
+ // formData.value = {...res}
25
+ // })
26
+ // }
27
+
28
+ // 纯表单——数据
29
+ function initComponents() {
30
+ formData.value = { plan_name: 'af-llllll', plan_point: '1号点位', plan_single: '1号点位', plan_range: '2024-12-12' }
31
+ }
32
+
33
+ onBeforeMount(() => {
34
+ initComponents()
35
+ })
36
+ </script>
37
+
38
+ <template>
39
+ <NormalDataLayout id="XFormGroupView" title="纯表单">
40
+ <template #layout_content>
41
+ <XFormGroup
42
+ :config-name="configName"
43
+ :service-name="serviceName"
44
+ :group-form-data="formData"
45
+ mode="查询"
46
+ @submit="submit"
47
+ />
48
+ </template>
49
+ </NormalDataLayout>
50
+ </template>
51
+
52
+ <style scoped lang="less">
53
+
54
+ </style>