af-mobile-client-vue3 1.4.14 → 1.4.15
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.
- package/package.json +1 -1
- package/src/components/core/Signature/SignatureComponent.vue +312 -0
- package/src/components/core/Signature/signature.ts +38 -0
- package/src/components/core/XSelect/index.vue +211 -211
- package/src/components/data/XBadge/index.vue +1 -1
- package/src/components/data/XFormItem/index.vue +0 -2
- package/src/components/data/XOlMap/types.ts +1 -1
- package/src/stores/modules/setting.ts +1 -0
- package/src/stores/modules/user.ts +1 -1
- package/src/views/component/XCellListView/index.vue +4 -78
- package/src/views/component/XFormView/index.vue +0 -1
- package/src/views/component/XOlMapView/XLocationPicker/index.vue +118 -118
- package/src/views/user/login/LoginForm.vue +5 -9
- package/vite.config.ts +2 -9
package/package.json
CHANGED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SignatureComponentExpose, SignatureComponentProps } from '@af-mobile-client-vue3/components/core/Signature/signature'
|
|
3
|
+
import { mobileUtil } from '@af-mobile-client-vue3/utils/mobileUtil'
|
|
4
|
+
import { showImagePreview, showToast } from 'vant'
|
|
5
|
+
import { defineEmits, defineProps, inject, ref } from 'vue'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<SignatureComponentProps>(), {
|
|
8
|
+
label: '用户签字',
|
|
9
|
+
required: false,
|
|
10
|
+
disabled: false,
|
|
11
|
+
uploadMode: 'server',
|
|
12
|
+
imageList: null,
|
|
13
|
+
formReadonly: false,
|
|
14
|
+
isAsyncUpload: false,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const emit = defineEmits<{
|
|
18
|
+
signatureComplete: [data: any]
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const signatureImage = ref<string>('')
|
|
22
|
+
const rawBase64Data = ref<string>('') // 保存原始base64数据
|
|
23
|
+
const hasSignature = ref(false)
|
|
24
|
+
const parentData: any = inject('provideParent')
|
|
25
|
+
const imageList = ref<Array<any>>(props.imageList ?? [])
|
|
26
|
+
console.log('imageList', imageList.value)
|
|
27
|
+
console.log('props.imageList', props.imageList)
|
|
28
|
+
// 处理签字后的上传
|
|
29
|
+
function getImageMimeType(fileName: string): string {
|
|
30
|
+
const ext = fileName.split('.').pop()?.toLowerCase()
|
|
31
|
+
if (ext === 'jpg' || ext === 'jpeg')
|
|
32
|
+
return 'image/jpeg'
|
|
33
|
+
if (ext === 'png')
|
|
34
|
+
return 'image/png'
|
|
35
|
+
if (ext === 'gif')
|
|
36
|
+
return 'image/gif'
|
|
37
|
+
return 'image/png' // 默认
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleSignature() {
|
|
41
|
+
if (props.disabled)
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
mobileUtil.execute({
|
|
45
|
+
funcName: 'showSignaturePad',
|
|
46
|
+
param: {},
|
|
47
|
+
callbackFunc: (result: any) => {
|
|
48
|
+
console.log('签字结果:', result)
|
|
49
|
+
if (result.status === 'success') {
|
|
50
|
+
const mimeType = getImageMimeType(result.data.filePath)
|
|
51
|
+
// 保存原始base64数据
|
|
52
|
+
rawBase64Data.value = result.data.base64
|
|
53
|
+
// 添加base64图片前缀用于显示
|
|
54
|
+
signatureImage.value = `data:${mimeType};base64,${result.data.base64}`
|
|
55
|
+
hasSignature.value = true
|
|
56
|
+
const tempFile = {
|
|
57
|
+
uid: Date.now() + Math.random().toString(36).substr(2, 5),
|
|
58
|
+
name: result.data.filePath.split('/').pop(),
|
|
59
|
+
status: 'uploading',
|
|
60
|
+
message: '上传中...',
|
|
61
|
+
url: `data:${mimeType};base64,${result.data.base64}`,
|
|
62
|
+
isImage: true,
|
|
63
|
+
type: mimeType,
|
|
64
|
+
}
|
|
65
|
+
if (imageList.value) {
|
|
66
|
+
imageList.value = [tempFile]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const param = {
|
|
70
|
+
resUploadMode: props.uploadMode,
|
|
71
|
+
pathKey: 'Default',
|
|
72
|
+
formType: 'image',
|
|
73
|
+
useType: 'Default',
|
|
74
|
+
resUploadStock: '1',
|
|
75
|
+
filename: tempFile.name,
|
|
76
|
+
// 暂无size
|
|
77
|
+
// filesize: photoData.size,
|
|
78
|
+
f_operator: 'server',
|
|
79
|
+
imgPath: result.data.filePath,
|
|
80
|
+
urlPath: `/api/${import.meta.env.VITE_APP_SYSTEM_NAME}/resource/upload`,
|
|
81
|
+
commonId: parentData?.commonId.value ?? '',
|
|
82
|
+
}
|
|
83
|
+
if (props.isAsyncUpload) {
|
|
84
|
+
// 添加上传队列
|
|
85
|
+
mobileUtil.execute({
|
|
86
|
+
funcName: 'queueUpload',
|
|
87
|
+
param,
|
|
88
|
+
callbackFunc: (res: any) => {
|
|
89
|
+
console.warn('上传结果', res)
|
|
90
|
+
let resStatus = 'success'
|
|
91
|
+
// 成功
|
|
92
|
+
if (res.data && res.data.enqueued) {
|
|
93
|
+
const index = imageList.value.findIndex(item => item.uid === tempFile.uid)
|
|
94
|
+
if (index !== -1) {
|
|
95
|
+
delete imageList.value[index].message
|
|
96
|
+
imageList.value[index].status = 'done'
|
|
97
|
+
imageList.value[index].photo_name = tempFile.name
|
|
98
|
+
imageList.value[index].type = mimeType
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
resStatus = 'error'
|
|
103
|
+
// 上传失败,显示错误提示并清除图片
|
|
104
|
+
const errorMessage = res.message || res.error || '上传失败,请重试'
|
|
105
|
+
showToast({
|
|
106
|
+
message: errorMessage,
|
|
107
|
+
type: 'fail',
|
|
108
|
+
duration: 3000,
|
|
109
|
+
})
|
|
110
|
+
clearSignature()
|
|
111
|
+
}
|
|
112
|
+
emit('signatureComplete', {
|
|
113
|
+
status: resStatus,
|
|
114
|
+
data: imageList.value?.length > 0 ? imageList.value[0] : null,
|
|
115
|
+
})
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// 上传到服务器
|
|
121
|
+
mobileUtil.execute({
|
|
122
|
+
funcName: 'uploadResource',
|
|
123
|
+
param,
|
|
124
|
+
callbackFunc: (result: any) => {
|
|
125
|
+
const index = imageList.value.findIndex(item => item.uid === tempFile.uid)
|
|
126
|
+
if (result.status === 'success') {
|
|
127
|
+
if (index !== -1) {
|
|
128
|
+
imageList.value[index].uid = result.data.id
|
|
129
|
+
imageList.value[index].id = result.data.id
|
|
130
|
+
imageList.value[index].photo_name = result.data.f_filename
|
|
131
|
+
imageList.value[index].f_downloadpath = result.data.f_downloadpath
|
|
132
|
+
delete imageList.value[index].message
|
|
133
|
+
imageList.value[index].status = 'done'
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// 上传失败,显示错误提示并清除图片
|
|
138
|
+
const errorMessage = result.message || result.error || '上传失败,请重试'
|
|
139
|
+
showToast({
|
|
140
|
+
message: errorMessage,
|
|
141
|
+
type: 'fail',
|
|
142
|
+
duration: 3000,
|
|
143
|
+
})
|
|
144
|
+
clearSignature()
|
|
145
|
+
}
|
|
146
|
+
emit('signatureComplete', {
|
|
147
|
+
status: result.status,
|
|
148
|
+
data: result.data,
|
|
149
|
+
})
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function clearSignature() {
|
|
159
|
+
signatureImage.value = ''
|
|
160
|
+
rawBase64Data.value = ''
|
|
161
|
+
hasSignature.value = false
|
|
162
|
+
imageList.value = []
|
|
163
|
+
emit('signatureComplete', {
|
|
164
|
+
base64: '',
|
|
165
|
+
status: 'cleared',
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 预览签字照片
|
|
170
|
+
function previewSignature() {
|
|
171
|
+
if (imageList.value) {
|
|
172
|
+
showImagePreview(imageList.value.map(item => item.url))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 暴露方法供父组件调用
|
|
177
|
+
defineExpose<SignatureComponentExpose>({
|
|
178
|
+
clearSignature,
|
|
179
|
+
hasSignature: () => hasSignature.value,
|
|
180
|
+
getSignatureData: () => rawBase64Data.value, // 返回原始base64数据
|
|
181
|
+
previewSignature,
|
|
182
|
+
getSignatureList: () => imageList.value.map((item) => { return { photo_name: item.photo_name, id: item.id, f_downloadpath: item.f_downloadpath } }),
|
|
183
|
+
})
|
|
184
|
+
</script>
|
|
185
|
+
|
|
186
|
+
<template>
|
|
187
|
+
<van-field
|
|
188
|
+
center
|
|
189
|
+
name="signature"
|
|
190
|
+
:label="label"
|
|
191
|
+
:required="required"
|
|
192
|
+
>
|
|
193
|
+
<template #input>
|
|
194
|
+
<div class="signature-container">
|
|
195
|
+
<!-- 签字照片预览 -->
|
|
196
|
+
<div v-if="imageList.length > 0 || hasSignature" class="signature-preview">
|
|
197
|
+
<van-image
|
|
198
|
+
v-for="(item, index) in imageList"
|
|
199
|
+
:key="`${item}_${index}`"
|
|
200
|
+
:src="item.url"
|
|
201
|
+
alt="签字照片"
|
|
202
|
+
class="signature-image"
|
|
203
|
+
fit="contain"
|
|
204
|
+
:show-loading="true"
|
|
205
|
+
:show-error="true"
|
|
206
|
+
loading-icon="photo-o"
|
|
207
|
+
error-icon="warning-o"
|
|
208
|
+
@click="previewSignature"
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<!-- 按钮行 -->
|
|
213
|
+
<div class="button-row">
|
|
214
|
+
<!-- 签字按钮 -->
|
|
215
|
+
<van-button
|
|
216
|
+
v-if="!props.formReadonly"
|
|
217
|
+
:disabled="disabled"
|
|
218
|
+
type="default"
|
|
219
|
+
size="small"
|
|
220
|
+
class="signature-btn dashed-btn"
|
|
221
|
+
@click="handleSignature"
|
|
222
|
+
>
|
|
223
|
+
<van-icon :name="hasSignature ? 'edit' : 'edit'" class="btn-icon" />
|
|
224
|
+
{{ imageList.length > 0 || hasSignature ? '重签' : '点击签字' }}
|
|
225
|
+
</van-button>
|
|
226
|
+
|
|
227
|
+
<!-- 清除按钮 -->
|
|
228
|
+
<van-button
|
|
229
|
+
v-if="(imageList.length > 0 || hasSignature) && !props.formReadonly"
|
|
230
|
+
type="default"
|
|
231
|
+
size="small"
|
|
232
|
+
class="dashed-btn clear-btn"
|
|
233
|
+
@click="clearSignature"
|
|
234
|
+
>
|
|
235
|
+
<van-icon name="delete" class="btn-icon" />
|
|
236
|
+
清除
|
|
237
|
+
</van-button>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</template>
|
|
241
|
+
</van-field>
|
|
242
|
+
</template>
|
|
243
|
+
|
|
244
|
+
<style scoped lang="less">
|
|
245
|
+
.signature-container {
|
|
246
|
+
display: flex;
|
|
247
|
+
flex-direction: column;
|
|
248
|
+
gap: 12px;
|
|
249
|
+
width: 100%;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.signature-preview {
|
|
253
|
+
position: relative;
|
|
254
|
+
display: flex;
|
|
255
|
+
flex-direction: column;
|
|
256
|
+
align-items: center;
|
|
257
|
+
gap: 8px;
|
|
258
|
+
|
|
259
|
+
.signature-image {
|
|
260
|
+
max-width: 200px;
|
|
261
|
+
max-height: 120px;
|
|
262
|
+
border: 1px solid #e8e8e8;
|
|
263
|
+
border-radius: 4px;
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
transition: all 0.2s ease;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// 按钮行样式
|
|
270
|
+
.button-row {
|
|
271
|
+
display: flex;
|
|
272
|
+
gap: 12px;
|
|
273
|
+
align-items: center;
|
|
274
|
+
justify-content: space-between;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.signature-btn {
|
|
278
|
+
flex-shrink: 0;
|
|
279
|
+
margin-right: auto;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 虚线按钮样式
|
|
283
|
+
.dashed-btn {
|
|
284
|
+
border: 1px dashed #1989fa !important;
|
|
285
|
+
background-color: transparent !important;
|
|
286
|
+
color: #1989fa !important;
|
|
287
|
+
border-radius: 4px !important;
|
|
288
|
+
|
|
289
|
+
&:hover {
|
|
290
|
+
border-color: #0570d9 !important;
|
|
291
|
+
color: #0570d9 !important;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
&:active {
|
|
295
|
+
border-color: #0366d6 !important;
|
|
296
|
+
color: #0366d6 !important;
|
|
297
|
+
background-color: rgba(25, 137, 250, 0.1) !important;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
&:disabled {
|
|
301
|
+
border-color: #c8c9cc !important;
|
|
302
|
+
color: #c8c9cc !important;
|
|
303
|
+
background-color: transparent !important;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 按钮图标样式
|
|
308
|
+
.btn-icon {
|
|
309
|
+
margin-right: 4px;
|
|
310
|
+
font-size: 14px;
|
|
311
|
+
}
|
|
312
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// 签字结果接口
|
|
2
|
+
export interface SignatureResult {
|
|
3
|
+
status: 'success' | 'failed' | 'cancelled' | 'cleared'
|
|
4
|
+
base64?: string
|
|
5
|
+
message?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// 签字组件属性接口
|
|
9
|
+
export interface SignatureComponentProps {
|
|
10
|
+
label?: string
|
|
11
|
+
required?: boolean
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
uploadMode?: string
|
|
14
|
+
imageList?: Array<any>
|
|
15
|
+
formReadonly?: boolean
|
|
16
|
+
isAsyncUpload?: boolean // 是否使用异步上传-异步上传后将不会返回file表的数据,需要通过上传时的文件名去t_files中查询
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 签字完成事件数据接口
|
|
20
|
+
export interface SignatureCompleteData {
|
|
21
|
+
base64: string
|
|
22
|
+
status: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 签字组件暴露的方法接口
|
|
26
|
+
export interface SignatureComponentExpose {
|
|
27
|
+
clearSignature: () => void
|
|
28
|
+
hasSignature: () => boolean
|
|
29
|
+
getSignatureData: () => string
|
|
30
|
+
previewSignature: () => void
|
|
31
|
+
getSignatureList: () => Array<any>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// base64图片工具类型
|
|
35
|
+
export interface Base64ImageData {
|
|
36
|
+
raw: string // 原始base64数据(不含前缀)
|
|
37
|
+
full: string // 完整的base64图片URL(含前缀)
|
|
38
|
+
}
|
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
Field as VanField,
|
|
4
|
-
Picker as VanPicker,
|
|
5
|
-
Popup as VanPopup,
|
|
6
|
-
Search as VanSearch,
|
|
7
|
-
} from 'vant'
|
|
8
|
-
import { computed, defineEmits, defineModel, defineProps, onBeforeMount, ref, watch } from 'vue'
|
|
9
|
-
|
|
10
|
-
const props = defineProps({
|
|
11
|
-
columns: {
|
|
12
|
-
type: Array,
|
|
13
|
-
default() {
|
|
14
|
-
return []
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
option: {
|
|
18
|
-
type: Object,
|
|
19
|
-
default() {
|
|
20
|
-
return { text: 'label', value: 'value', children: 'children' }
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
offOption: { // 关闭option配置key-value;当数据是非集合的数组的时候,开启
|
|
24
|
-
type: Boolean,
|
|
25
|
-
default: false,
|
|
26
|
-
},
|
|
27
|
-
border: { // 是否展示边框
|
|
28
|
-
type: Boolean,
|
|
29
|
-
default: false,
|
|
30
|
-
},
|
|
31
|
-
lazyLoad: { // 是否启用懒加载
|
|
32
|
-
type: String,
|
|
33
|
-
default: 'false',
|
|
34
|
-
},
|
|
35
|
-
onSearch: { // 懒加载时的搜索函数
|
|
36
|
-
type: Function,
|
|
37
|
-
default: null,
|
|
38
|
-
},
|
|
39
|
-
})
|
|
40
|
-
const emits = defineEmits(['confirm', 'change', 'cancel', 'input'])
|
|
41
|
-
const show = ref(false)
|
|
42
|
-
const resultValue = defineModel()
|
|
43
|
-
const columnsData = ref([])
|
|
44
|
-
const selectedOption = ref([])
|
|
45
|
-
const searchValue = ref('')
|
|
46
|
-
const isLoading = ref(false)
|
|
47
|
-
|
|
48
|
-
// 转换空children为空字符串
|
|
49
|
-
function transformColumns(data) {
|
|
50
|
-
return data.map((item) => {
|
|
51
|
-
if (item.children && item.children.length === 0) {
|
|
52
|
-
return {
|
|
53
|
-
...item,
|
|
54
|
-
children: '',
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
else if (item.children && item.children.length !== 0) {
|
|
58
|
-
return { ...item, children: transformColumns(item.children) }
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
return { ...item }
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
const filteredColumns = ref([])
|
|
66
|
-
|
|
67
|
-
watch(
|
|
68
|
-
() => searchValue.value,
|
|
69
|
-
async (newValue) => {
|
|
70
|
-
if (props.lazyLoad && props.lazyLoad === 'true' && props.onSearch) {
|
|
71
|
-
if (!newValue) {
|
|
72
|
-
filteredColumns.value = []
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
isLoading.value = true
|
|
77
|
-
try {
|
|
78
|
-
const results = await props.onSearch(newValue)
|
|
79
|
-
filteredColumns.value = transformColumns(results || [])
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
console.error('懒加载搜索失败:', error)
|
|
83
|
-
filteredColumns.value = []
|
|
84
|
-
}
|
|
85
|
-
finally {
|
|
86
|
-
isLoading.value = false
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
// 普通搜索模式
|
|
91
|
-
if (!newValue) {
|
|
92
|
-
filteredColumns.value = columnsData.value
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const searchTextLower = newValue.toLowerCase()
|
|
97
|
-
filteredColumns.value = columnsData.value.filter((item) => {
|
|
98
|
-
const text = props.offOption ? item : item[props.option.text]
|
|
99
|
-
return text?.toString().toLowerCase().includes(searchTextLower)
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
{ immediate: true },
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
onBeforeMount(() => {
|
|
107
|
-
columnsData.value = transformColumns(props.columns)
|
|
108
|
-
filteredColumns.value = columnsData.value
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
const resultLabel = computed({
|
|
112
|
-
get() {
|
|
113
|
-
if (!resultValue.value)
|
|
114
|
-
return ''
|
|
115
|
-
const res = props.columns.filter((item) => {
|
|
116
|
-
const data = props.offOption ? item : item[props.option.value]
|
|
117
|
-
return data === resultValue.value
|
|
118
|
-
})
|
|
119
|
-
return res.length ? (props.offOption ? res[0] : res[0][props.option.text]) : ''
|
|
120
|
-
},
|
|
121
|
-
set() {
|
|
122
|
-
|
|
123
|
-
},
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
function onConfirm(value, _index) {
|
|
127
|
-
resultValue.value = props.offOption ? value.selectedValues : value.selectedValues[0]
|
|
128
|
-
// resultValue.value = value.selectedValues
|
|
129
|
-
selectedOption.value = value.selectedOptions
|
|
130
|
-
show.value = !show.value
|
|
131
|
-
emits('confirm', value.selectedValues[0], value.selectedOptions)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function change(val, index) {
|
|
135
|
-
emits('change', val, index, resultValue.value)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function cancel(val, index) {
|
|
139
|
-
show.value = !show.value
|
|
140
|
-
emits('cancel', val, index, resultValue.value)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function showPopu(disabled) {
|
|
144
|
-
if (disabled !== undefined && disabled !== false)
|
|
145
|
-
return false
|
|
146
|
-
columnsData.value = transformColumns(props.columns)
|
|
147
|
-
filteredColumns.value = columnsData.value
|
|
148
|
-
searchValue.value = '' // 重置搜索
|
|
149
|
-
show.value = !show.value
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
watch(() => resultValue, (newVal, _oldVal) => {
|
|
153
|
-
columnsData.value = transformColumns(props.columns)
|
|
154
|
-
filteredColumns.value = columnsData.value
|
|
155
|
-
emits('input', newVal)
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
// 监听 columns 变化
|
|
159
|
-
watch(() => props.columns, () => {
|
|
160
|
-
columnsData.value = transformColumns(props.columns)
|
|
161
|
-
filteredColumns.value = columnsData.value
|
|
162
|
-
searchValue.value = ''
|
|
163
|
-
}, { deep: true })
|
|
164
|
-
</script>
|
|
165
|
-
|
|
166
|
-
<template>
|
|
167
|
-
<VanField
|
|
168
|
-
v-model="resultLabel"
|
|
169
|
-
v-bind="$attrs"
|
|
170
|
-
readonly
|
|
171
|
-
:is-link="true"
|
|
172
|
-
:border="props.border"
|
|
173
|
-
@click="showPopu($attrs.readonly)"
|
|
174
|
-
/>
|
|
175
|
-
<VanPopup v-model:show="show" position="bottom">
|
|
176
|
-
<div class="x-select-popup">
|
|
177
|
-
<!-- 搜索框 -->
|
|
178
|
-
<VanSearch
|
|
179
|
-
v-model="searchValue"
|
|
180
|
-
placeholder="搜索"
|
|
181
|
-
/>
|
|
182
|
-
|
|
183
|
-
<!-- 选择器 -->
|
|
184
|
-
<VanPicker
|
|
185
|
-
:loading="isLoading"
|
|
186
|
-
v-bind="$attrs"
|
|
187
|
-
:columns="filteredColumns"
|
|
188
|
-
:columns-field-names="props.option"
|
|
189
|
-
show-toolbar
|
|
190
|
-
:value-key="props.option.text"
|
|
191
|
-
@cancel="cancel"
|
|
192
|
-
@confirm="onConfirm"
|
|
193
|
-
@change="change"
|
|
194
|
-
/>
|
|
195
|
-
</div>
|
|
196
|
-
</VanPopup>
|
|
197
|
-
</template>
|
|
198
|
-
|
|
199
|
-
<style scoped>
|
|
200
|
-
.loading-container {
|
|
201
|
-
display: flex;
|
|
202
|
-
justify-content: center;
|
|
203
|
-
align-items: center;
|
|
204
|
-
height: 200px;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
.loading-text {
|
|
208
|
-
color: #969799;
|
|
209
|
-
font-size: 14px;
|
|
210
|
-
}
|
|
211
|
-
</style>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Field as VanField,
|
|
4
|
+
Picker as VanPicker,
|
|
5
|
+
Popup as VanPopup,
|
|
6
|
+
Search as VanSearch,
|
|
7
|
+
} from 'vant'
|
|
8
|
+
import { computed, defineEmits, defineModel, defineProps, onBeforeMount, ref, watch } from 'vue'
|
|
9
|
+
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
columns: {
|
|
12
|
+
type: Array,
|
|
13
|
+
default() {
|
|
14
|
+
return []
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
option: {
|
|
18
|
+
type: Object,
|
|
19
|
+
default() {
|
|
20
|
+
return { text: 'label', value: 'value', children: 'children' }
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
offOption: { // 关闭option配置key-value;当数据是非集合的数组的时候,开启
|
|
24
|
+
type: Boolean,
|
|
25
|
+
default: false,
|
|
26
|
+
},
|
|
27
|
+
border: { // 是否展示边框
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: false,
|
|
30
|
+
},
|
|
31
|
+
lazyLoad: { // 是否启用懒加载
|
|
32
|
+
type: String,
|
|
33
|
+
default: 'false',
|
|
34
|
+
},
|
|
35
|
+
onSearch: { // 懒加载时的搜索函数
|
|
36
|
+
type: Function,
|
|
37
|
+
default: null,
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
const emits = defineEmits(['confirm', 'change', 'cancel', 'input'])
|
|
41
|
+
const show = ref(false)
|
|
42
|
+
const resultValue = defineModel()
|
|
43
|
+
const columnsData = ref([])
|
|
44
|
+
const selectedOption = ref([])
|
|
45
|
+
const searchValue = ref('')
|
|
46
|
+
const isLoading = ref(false)
|
|
47
|
+
|
|
48
|
+
// 转换空children为空字符串
|
|
49
|
+
function transformColumns(data) {
|
|
50
|
+
return data.map((item) => {
|
|
51
|
+
if (item.children && item.children.length === 0) {
|
|
52
|
+
return {
|
|
53
|
+
...item,
|
|
54
|
+
children: '',
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (item.children && item.children.length !== 0) {
|
|
58
|
+
return { ...item, children: transformColumns(item.children) }
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
return { ...item }
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
const filteredColumns = ref([])
|
|
66
|
+
|
|
67
|
+
watch(
|
|
68
|
+
() => searchValue.value,
|
|
69
|
+
async (newValue) => {
|
|
70
|
+
if (props.lazyLoad && props.lazyLoad === 'true' && props.onSearch) {
|
|
71
|
+
if (!newValue) {
|
|
72
|
+
filteredColumns.value = []
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
isLoading.value = true
|
|
77
|
+
try {
|
|
78
|
+
const results = await props.onSearch(newValue)
|
|
79
|
+
filteredColumns.value = transformColumns(results || [])
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error('懒加载搜索失败:', error)
|
|
83
|
+
filteredColumns.value = []
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
isLoading.value = false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// 普通搜索模式
|
|
91
|
+
if (!newValue) {
|
|
92
|
+
filteredColumns.value = columnsData.value
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const searchTextLower = newValue.toLowerCase()
|
|
97
|
+
filteredColumns.value = columnsData.value.filter((item) => {
|
|
98
|
+
const text = props.offOption ? item : item[props.option.text]
|
|
99
|
+
return text?.toString().toLowerCase().includes(searchTextLower)
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{ immediate: true },
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
onBeforeMount(() => {
|
|
107
|
+
columnsData.value = transformColumns(props.columns)
|
|
108
|
+
filteredColumns.value = columnsData.value
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const resultLabel = computed({
|
|
112
|
+
get() {
|
|
113
|
+
if (!resultValue.value)
|
|
114
|
+
return ''
|
|
115
|
+
const res = props.columns.filter((item) => {
|
|
116
|
+
const data = props.offOption ? item : item[props.option.value]
|
|
117
|
+
return data === resultValue.value
|
|
118
|
+
})
|
|
119
|
+
return res.length ? (props.offOption ? res[0] : res[0][props.option.text]) : ''
|
|
120
|
+
},
|
|
121
|
+
set() {
|
|
122
|
+
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
function onConfirm(value, _index) {
|
|
127
|
+
resultValue.value = props.offOption ? value.selectedValues : value.selectedValues[0]
|
|
128
|
+
// resultValue.value = value.selectedValues
|
|
129
|
+
selectedOption.value = value.selectedOptions
|
|
130
|
+
show.value = !show.value
|
|
131
|
+
emits('confirm', value.selectedValues[0], value.selectedOptions)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function change(val, index) {
|
|
135
|
+
emits('change', val, index, resultValue.value)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function cancel(val, index) {
|
|
139
|
+
show.value = !show.value
|
|
140
|
+
emits('cancel', val, index, resultValue.value)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function showPopu(disabled) {
|
|
144
|
+
if (disabled !== undefined && disabled !== false)
|
|
145
|
+
return false
|
|
146
|
+
columnsData.value = transformColumns(props.columns)
|
|
147
|
+
filteredColumns.value = columnsData.value
|
|
148
|
+
searchValue.value = '' // 重置搜索
|
|
149
|
+
show.value = !show.value
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
watch(() => resultValue, (newVal, _oldVal) => {
|
|
153
|
+
columnsData.value = transformColumns(props.columns)
|
|
154
|
+
filteredColumns.value = columnsData.value
|
|
155
|
+
emits('input', newVal)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// 监听 columns 变化
|
|
159
|
+
watch(() => props.columns, () => {
|
|
160
|
+
columnsData.value = transformColumns(props.columns)
|
|
161
|
+
filteredColumns.value = columnsData.value
|
|
162
|
+
searchValue.value = ''
|
|
163
|
+
}, { deep: true })
|
|
164
|
+
</script>
|
|
165
|
+
|
|
166
|
+
<template>
|
|
167
|
+
<VanField
|
|
168
|
+
v-model="resultLabel"
|
|
169
|
+
v-bind="$attrs"
|
|
170
|
+
readonly
|
|
171
|
+
:is-link="true"
|
|
172
|
+
:border="props.border"
|
|
173
|
+
@click="showPopu($attrs.readonly)"
|
|
174
|
+
/>
|
|
175
|
+
<VanPopup v-model:show="show" position="bottom">
|
|
176
|
+
<div class="x-select-popup">
|
|
177
|
+
<!-- 搜索框 -->
|
|
178
|
+
<VanSearch
|
|
179
|
+
v-model="searchValue"
|
|
180
|
+
placeholder="搜索"
|
|
181
|
+
/>
|
|
182
|
+
|
|
183
|
+
<!-- 选择器 -->
|
|
184
|
+
<VanPicker
|
|
185
|
+
:loading="isLoading"
|
|
186
|
+
v-bind="$attrs"
|
|
187
|
+
:columns="filteredColumns"
|
|
188
|
+
:columns-field-names="props.option"
|
|
189
|
+
show-toolbar
|
|
190
|
+
:value-key="props.option.text"
|
|
191
|
+
@cancel="cancel"
|
|
192
|
+
@confirm="onConfirm"
|
|
193
|
+
@change="change"
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
</VanPopup>
|
|
197
|
+
</template>
|
|
198
|
+
|
|
199
|
+
<style scoped>
|
|
200
|
+
.loading-container {
|
|
201
|
+
display: flex;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
align-items: center;
|
|
204
|
+
height: 200px;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.loading-text {
|
|
208
|
+
color: #969799;
|
|
209
|
+
font-size: 14px;
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
@@ -4,7 +4,7 @@ import dayjs from 'dayjs/esm/index'
|
|
|
4
4
|
import { ref } from 'vue'
|
|
5
5
|
|
|
6
6
|
const { serviceName, dictName, dictValue, dictType } = defineProps<{
|
|
7
|
-
serviceName
|
|
7
|
+
serviceName?: string
|
|
8
8
|
dictName: string | null | undefined
|
|
9
9
|
dictValue: string | number | null | undefined
|
|
10
10
|
// 用于非字典型的格式化展示:如日期/小数/整数等
|
|
@@ -225,8 +225,6 @@ function checkModel(val = props.modelValue) {
|
|
|
225
225
|
*/
|
|
226
226
|
function getDefaultValue() {
|
|
227
227
|
const val = props.modelValue
|
|
228
|
-
console.warn('>>>>>props', props)
|
|
229
|
-
console.warn('>>>> attr', attr)
|
|
230
228
|
// 如果有有效值,直接返回(datePicker 初始值需按 formValueFormat 归一)
|
|
231
229
|
// 目的:外部通过 formData 传入的初始值在组件挂载即与配置格式一致
|
|
232
230
|
if (checkModel(val)) {
|
|
@@ -214,7 +214,7 @@ export const useUserStore = defineStore('app-user', () => {
|
|
|
214
214
|
setToken(data.access_token)
|
|
215
215
|
// 第三方教培系统鉴权兼容
|
|
216
216
|
const LoginTicket = crypto.AESEncrypt(JSON.stringify(params), '3KMKqvgwR8ULbR8Z')
|
|
217
|
-
if (data.session && useSettingStore().getSetting()
|
|
217
|
+
if (data.session && useSettingStore().getSetting().requestEncrypt) {
|
|
218
218
|
const k = encryptUtil.RSADecrypt(data.session as string)
|
|
219
219
|
localStorage.setItem('v4-session-key', k)
|
|
220
220
|
secureStorageWrite('v4-session-key', k)
|
|
@@ -9,92 +9,18 @@ const emit = defineEmits(['deleteRow'])
|
|
|
9
9
|
// 访问路由
|
|
10
10
|
const router = useRouter()
|
|
11
11
|
// 获取默认值
|
|
12
|
-
const idKey = ref('o_id')
|
|
12
|
+
// const idKey = ref('o_id')
|
|
13
13
|
|
|
14
14
|
// 简易crud表单测试
|
|
15
|
-
const configName = ref('
|
|
16
|
-
const serviceName = ref('af-
|
|
17
|
-
|
|
18
|
-
// 资源权限测试
|
|
19
|
-
// const configName = ref('crud_sources_test')
|
|
20
|
-
// const serviceName = ref('af-system')
|
|
21
|
-
|
|
22
|
-
// 实际业务测试
|
|
23
|
-
// const configName = ref('lngChargeAuditMobileCRUD')
|
|
24
|
-
// const serviceName = ref('af-gaslink')
|
|
25
|
-
|
|
26
|
-
// 跳转到详情页面
|
|
27
|
-
// function toDetail(item) {
|
|
28
|
-
// router.push({
|
|
29
|
-
// name: 'XCellDetailView',
|
|
30
|
-
// params: { id: item[idKey.value] }, // 如果使用命名路由,推荐使用路由参数而不是直接构建 URL
|
|
31
|
-
// query: {
|
|
32
|
-
// operName: item[operNameKey.value],
|
|
33
|
-
// method:item[methodKey.value],
|
|
34
|
-
// requestMethod:item[requestMethodKey.value],
|
|
35
|
-
// operatorType:item[operatorTypeKey.value],
|
|
36
|
-
// operUrl:item[operUrlKey.value],
|
|
37
|
-
// operIp:item[operIpKey.value],
|
|
38
|
-
// costTime:item[costTimeKey.value],
|
|
39
|
-
// operTime:item[operTimeKey.value],
|
|
40
|
-
//
|
|
41
|
-
// title: item[titleKey.value],
|
|
42
|
-
// businessType: item[businessTypeKey.value],
|
|
43
|
-
// status:item[statusKey.value]
|
|
44
|
-
// }
|
|
45
|
-
// })
|
|
46
|
-
// }
|
|
47
|
-
|
|
48
|
-
// 跳转到表单——以表单组来渲染纯表单
|
|
49
|
-
// function toDetail(item) {
|
|
50
|
-
// router.push({
|
|
51
|
-
// name: 'XFormGroupView',
|
|
52
|
-
// query: {
|
|
53
|
-
// id: item[idKey.value],
|
|
54
|
-
// // id: item.rr_id,
|
|
55
|
-
// // o_id: item.o_id,
|
|
56
|
-
// },
|
|
57
|
-
// })
|
|
58
|
-
// }
|
|
59
|
-
|
|
60
|
-
// 新增功能
|
|
61
|
-
// function addOption(totalCount) {
|
|
62
|
-
// router.push({
|
|
63
|
-
// name: 'XFormView',
|
|
64
|
-
// params: { id: totalCount, openid: totalCount },
|
|
65
|
-
// query: {
|
|
66
|
-
// configName: configName.value,
|
|
67
|
-
// serviceName: serviceName.value,
|
|
68
|
-
// mode: '新增',
|
|
69
|
-
// },
|
|
70
|
-
// })
|
|
71
|
-
// }
|
|
72
|
-
|
|
73
|
-
// 修改功能
|
|
74
|
-
// function updateRow(result) {
|
|
75
|
-
// router.push({
|
|
76
|
-
// name: 'XFormView',
|
|
77
|
-
// params: { id: result.o_id, openid: result.o_id },
|
|
78
|
-
// query: {
|
|
79
|
-
// configName: configName.value,
|
|
80
|
-
// serviceName: serviceName.value,
|
|
81
|
-
// mode: '修改',
|
|
82
|
-
// },
|
|
83
|
-
// })
|
|
84
|
-
// }
|
|
85
|
-
|
|
86
|
-
// // 删除功能
|
|
87
|
-
// function deleteRow(result) {
|
|
88
|
-
// emit('deleteRow', result.o_id)
|
|
89
|
-
// }
|
|
15
|
+
const configName = ref('ceshiCRUD')
|
|
16
|
+
const serviceName = ref('af-linepatrol')
|
|
90
17
|
</script>
|
|
91
18
|
|
|
92
19
|
<template>
|
|
93
|
-
<NormalDataLayout id="XCellListView" title="
|
|
20
|
+
<NormalDataLayout id="XCellListView" title="工作计划">
|
|
94
21
|
<template #layout_content>
|
|
95
22
|
<XCellList
|
|
96
23
|
:config-name="configName"
|
|
97
|
-
:service-name="serviceName"
|
|
98
24
|
/>
|
|
99
25
|
</template>
|
|
100
26
|
</NormalDataLayout>
|
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { LocationResult } from '@af-mobile-client-vue3/components/data/XOlMap/types'
|
|
3
|
-
import LocationPicker from '@af-mobile-client-vue3/components/data/XOlMap/XLocationPicker/index.vue'
|
|
4
|
-
import NormalDataLayout from '@af-mobile-client-vue3/components/layout/NormalDataLayout/index.vue'
|
|
5
|
-
import { showNotify } from 'vant'
|
|
6
|
-
import { ref } from 'vue'
|
|
7
|
-
|
|
8
|
-
const selectedLocation = ref<LocationResult>()
|
|
9
|
-
|
|
10
|
-
// 处理位置选择
|
|
11
|
-
function handleLocationConfirm(location: LocationResult) {
|
|
12
|
-
// console.log('选择的位置:', location)
|
|
13
|
-
// selectedLocation.value = location
|
|
14
|
-
showNotify({ type: 'success', message: '位置已选择' })
|
|
15
|
-
}
|
|
16
|
-
</script>
|
|
17
|
-
|
|
18
|
-
<template>
|
|
19
|
-
<NormalDataLayout id="XLocationPicker" title="XOlMap地址选择器">
|
|
20
|
-
<template #layout_content>
|
|
21
|
-
<div class="location-picker-demo">
|
|
22
|
-
<!-- 页面标题 -->
|
|
23
|
-
<div class="page-header">
|
|
24
|
-
<div class="title">
|
|
25
|
-
位置选择
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<!-- 选择结果展示 -->
|
|
30
|
-
<div v-if="selectedLocation" class="location-result">
|
|
31
|
-
<div class="label">
|
|
32
|
-
已选位置:
|
|
33
|
-
</div>
|
|
34
|
-
<div class="value">
|
|
35
|
-
{{ selectedLocation.address }}
|
|
36
|
-
</div>
|
|
37
|
-
<div class="coordinates">
|
|
38
|
-
经度: {{ selectedLocation.longitude.toFixed(6) }},
|
|
39
|
-
纬度: {{ selectedLocation.latitude.toFixed(6) }}
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
|
|
43
|
-
<!-- 地图组件 -->
|
|
44
|
-
<div class="map-container">
|
|
45
|
-
<LocationPicker
|
|
46
|
-
v-model="selectedLocation"
|
|
47
|
-
:default-center="[108.948024, 34.263161]"
|
|
48
|
-
:default-zoom="12"
|
|
49
|
-
@confirm="handleLocationConfirm"
|
|
50
|
-
/>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
</template>
|
|
54
|
-
</NormalDataLayout>
|
|
55
|
-
</template>
|
|
56
|
-
|
|
57
|
-
<style scoped lang="less">
|
|
58
|
-
.location-picker-demo {
|
|
59
|
-
width: 100%;
|
|
60
|
-
height: 100%;
|
|
61
|
-
position: relative;
|
|
62
|
-
display: flex;
|
|
63
|
-
flex-direction: column;
|
|
64
|
-
background-color: #f7f8fa;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.page-header {
|
|
68
|
-
height: 44px;
|
|
69
|
-
display: flex;
|
|
70
|
-
align-items: center;
|
|
71
|
-
justify-content: center;
|
|
72
|
-
background: white;
|
|
73
|
-
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
|
74
|
-
position: relative;
|
|
75
|
-
z-index: 1;
|
|
76
|
-
|
|
77
|
-
.title {
|
|
78
|
-
font-size: 16px;
|
|
79
|
-
color: #333;
|
|
80
|
-
font-weight: 500;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.location-result {
|
|
85
|
-
background: white;
|
|
86
|
-
padding: 12px 16px;
|
|
87
|
-
margin: 10px;
|
|
88
|
-
border-radius: 8px;
|
|
89
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
90
|
-
|
|
91
|
-
.label {
|
|
92
|
-
font-size: 14px;
|
|
93
|
-
color: #666;
|
|
94
|
-
margin-bottom: 4px;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.value {
|
|
98
|
-
font-size: 16px;
|
|
99
|
-
color: #333;
|
|
100
|
-
margin-bottom: 8px;
|
|
101
|
-
word-break: break-all;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.coordinates {
|
|
105
|
-
font-size: 12px;
|
|
106
|
-
color: #999;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.map-container {
|
|
111
|
-
flex: 1;
|
|
112
|
-
position: relative;
|
|
113
|
-
margin: 0 10px 10px 10px;
|
|
114
|
-
border-radius: 8px;
|
|
115
|
-
overflow: hidden;
|
|
116
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
117
|
-
}
|
|
118
|
-
</style>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { LocationResult } from '@af-mobile-client-vue3/components/data/XOlMap/types'
|
|
3
|
+
import LocationPicker from '@af-mobile-client-vue3/components/data/XOlMap/XLocationPicker/index.vue'
|
|
4
|
+
import NormalDataLayout from '@af-mobile-client-vue3/components/layout/NormalDataLayout/index.vue'
|
|
5
|
+
import { showNotify } from 'vant'
|
|
6
|
+
import { ref } from 'vue'
|
|
7
|
+
|
|
8
|
+
const selectedLocation = ref<LocationResult>()
|
|
9
|
+
|
|
10
|
+
// 处理位置选择
|
|
11
|
+
function handleLocationConfirm(location: LocationResult) {
|
|
12
|
+
// console.log('选择的位置:', location)
|
|
13
|
+
// selectedLocation.value = location
|
|
14
|
+
showNotify({ type: 'success', message: '位置已选择' })
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<NormalDataLayout id="XLocationPicker" title="XOlMap地址选择器">
|
|
20
|
+
<template #layout_content>
|
|
21
|
+
<div class="location-picker-demo">
|
|
22
|
+
<!-- 页面标题 -->
|
|
23
|
+
<div class="page-header">
|
|
24
|
+
<div class="title">
|
|
25
|
+
位置选择
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<!-- 选择结果展示 -->
|
|
30
|
+
<div v-if="selectedLocation" class="location-result">
|
|
31
|
+
<div class="label">
|
|
32
|
+
已选位置:
|
|
33
|
+
</div>
|
|
34
|
+
<div class="value">
|
|
35
|
+
{{ selectedLocation.address }}
|
|
36
|
+
</div>
|
|
37
|
+
<div class="coordinates">
|
|
38
|
+
经度: {{ selectedLocation.longitude.toFixed(6) }},
|
|
39
|
+
纬度: {{ selectedLocation.latitude.toFixed(6) }}
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<!-- 地图组件 -->
|
|
44
|
+
<div class="map-container">
|
|
45
|
+
<LocationPicker
|
|
46
|
+
v-model="selectedLocation"
|
|
47
|
+
:default-center="[108.948024, 34.263161]"
|
|
48
|
+
:default-zoom="12"
|
|
49
|
+
@confirm="handleLocationConfirm"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
</NormalDataLayout>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<style scoped lang="less">
|
|
58
|
+
.location-picker-demo {
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: 100%;
|
|
61
|
+
position: relative;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
background-color: #f7f8fa;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.page-header {
|
|
68
|
+
height: 44px;
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: center;
|
|
72
|
+
background: white;
|
|
73
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
|
74
|
+
position: relative;
|
|
75
|
+
z-index: 1;
|
|
76
|
+
|
|
77
|
+
.title {
|
|
78
|
+
font-size: 16px;
|
|
79
|
+
color: #333;
|
|
80
|
+
font-weight: 500;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.location-result {
|
|
85
|
+
background: white;
|
|
86
|
+
padding: 12px 16px;
|
|
87
|
+
margin: 10px;
|
|
88
|
+
border-radius: 8px;
|
|
89
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
90
|
+
|
|
91
|
+
.label {
|
|
92
|
+
font-size: 14px;
|
|
93
|
+
color: #666;
|
|
94
|
+
margin-bottom: 4px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.value {
|
|
98
|
+
font-size: 16px;
|
|
99
|
+
color: #333;
|
|
100
|
+
margin-bottom: 8px;
|
|
101
|
+
word-break: break-all;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.coordinates {
|
|
105
|
+
font-size: 12px;
|
|
106
|
+
color: #999;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.map-container {
|
|
111
|
+
flex: 1;
|
|
112
|
+
position: relative;
|
|
113
|
+
margin: 0 10px 10px 10px;
|
|
114
|
+
border-radius: 8px;
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
117
|
+
}
|
|
118
|
+
</style>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { FormInstance } from 'vant'
|
|
3
3
|
import { LoginStateEnum, useFormRules, useLoginState } from '@af-mobile-client-vue3/hooks/useLogin'
|
|
4
4
|
import GetAppDataService from '@af-mobile-client-vue3/plugins/AppData'
|
|
5
|
-
import {
|
|
5
|
+
import { runLogic } from '@af-mobile-client-vue3/services/api/common'
|
|
6
6
|
import { getUserPermissions } from '@af-mobile-client-vue3/services/api/search'
|
|
7
7
|
import { useSettingStore } from '@af-mobile-client-vue3/stores/modules/setting'
|
|
8
8
|
import { useUserStore } from '@af-mobile-client-vue3/stores/modules/user'
|
|
@@ -107,15 +107,11 @@ function handleSubmit() {
|
|
|
107
107
|
try {
|
|
108
108
|
loading.value = true
|
|
109
109
|
showLoadingToast('登录中...')
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const verification = webConfig.setting?.phoneLoginVerification || false
|
|
114
|
-
if (verification) {
|
|
110
|
+
const username = formData.username
|
|
111
|
+
const password = formData.password
|
|
112
|
+
if (setting.getSetting()?.phoneLoginVerification) {
|
|
115
113
|
try {
|
|
116
|
-
|
|
117
|
-
username = res.username
|
|
118
|
-
password = res.password
|
|
114
|
+
await runLogic('openapi/loginVerification', { username, password: bcrypt.hashSync(password, 10) }) as any
|
|
119
115
|
}
|
|
120
116
|
catch (e) {
|
|
121
117
|
console.error('验证登录失败: ', e)
|
package/vite.config.ts
CHANGED
|
@@ -11,8 +11,8 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
|
|
11
11
|
|
|
12
12
|
const appProxys = {}
|
|
13
13
|
|
|
14
|
-
const v4Server = 'http://
|
|
15
|
-
const v3Server = 'http://
|
|
14
|
+
const v4Server = 'http://192.168.50.67:31567'
|
|
15
|
+
const v3Server = 'http://192.168.50.67:31567'
|
|
16
16
|
const OSSServerDev = 'http://192.168.50.67:30351'
|
|
17
17
|
const geoserver = 'http://39.104.49.8:30372'
|
|
18
18
|
const mockServer = 'http://127.0.0.1:8086'
|
|
@@ -64,13 +64,6 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
|
|
64
64
|
changeOrigin: true,
|
|
65
65
|
rewrite: path => path.replace(/^\/linepatrol\/geoserver/, '/geoserver'),
|
|
66
66
|
},
|
|
67
|
-
// '/api/af-revenue': {
|
|
68
|
-
// // target: v4Server,
|
|
69
|
-
// rewrite: (path: string) => path.replace(/^\/api\/af-revenue\//, '/'),
|
|
70
|
-
// target: 'http://127.0.0.1:9026',
|
|
71
|
-
// ws: false,
|
|
72
|
-
// changeOrigin: true,
|
|
73
|
-
// },
|
|
74
67
|
'/api': {
|
|
75
68
|
// v3用
|
|
76
69
|
// rewrite: (path: string) => path.replace(/^\/api\/af-system\//, '/rs/'),
|