wui-components-v2 1.1.54 → 1.1.55

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.
@@ -31,23 +31,38 @@ const show = computed({
31
31
  },
32
32
  })
33
33
 
34
+ // 格式化文件数据为后端期望的 JSON 字符串
35
+ function formatFileData(data: Record<string, any>) {
36
+ const result = { ...data }
37
+ for (const key in result) {
38
+ if (result[key]?.response) {
39
+ const fileObj = result[key]
40
+ const res = typeof fileObj.response === 'string' ? JSON.parse(fileObj.response) : fileObj.response
41
+ result[key] = JSON.stringify({
42
+ valid: fileObj.disabled ? 'old' : 'new',
43
+ fileKey: res.fileKey,
44
+ fileName: fileObj.name,
45
+ })
46
+ }
47
+ }
48
+ return result
49
+ }
50
+
34
51
  // 提交数据
35
52
  async function action() {
36
- console.log('提交action')
37
53
  btnLoading.value = true
38
54
  if (!props.fieldGroup?.id) {
39
55
  return toast.warning({ msg: '无sourceId' })
40
56
  }
41
57
  try {
42
58
  const data = await formControlRef.value.submit()
43
- await actionDataSave(props.fieldGroup?.id || '', props.code, data)
59
+ await actionDataSave(props.fieldGroup?.id || '', props.code, formatFileData(data))
44
60
  btnLoading.value = false
45
61
  show.value = false
46
62
  props.zpaging.reload()
47
63
  toast.success({ msg: '操作成功!' })
48
64
  }
49
65
  catch (error: any) {
50
- console.log(error, 'error')
51
66
  btnLoading.value = false
52
67
  toast.error(error)
53
68
  }
@@ -124,6 +124,7 @@ const schema = computed((): FormSchema => {
124
124
 
125
125
  // 监听文件上传成功
126
126
  function handleFileChange(e: any, item: Fields) {
127
+ console.log(e, item,'2222222222222')
127
128
  model.value[item.sourceId] = e.fileList
128
129
  }
129
130
 
@@ -177,7 +178,7 @@ defineExpose({
177
178
  <template>
178
179
  <view>
179
180
  <wd-form ref="form" :model="model" :schema="schema" error-type="toast" :label-width="120" class="custom-from-style">
180
- <addAddressPage v-if="props.smartPaste" v-model="model" :group="props.fieldGroup" />
181
+ <addAddressPage v-if="props?.smartPaste" v-model="model" :group="props.fieldGroup" />
181
182
  <view v-for="item in fields" :key="item.sourceId">
182
183
  <view v-show="!item.title?.includes('y') && !item.hidden">
183
184
  <wd-form-item
@@ -318,6 +319,22 @@ defineExpose({
318
319
  :clearable="!item.disabled"
319
320
  :placeholder="`请选择${item.title}`"
320
321
  />
322
+
323
+ </wd-form-item>
324
+ <wd-form-item
325
+ v-else-if="ControlTypeSupportor.getControlType(item, props.entity && props.entity[item.sourceId]) === 'date-YY'"
326
+ :class="{ 'no-border-top': fields.indexOf(item) === 0 }"
327
+ :prop="item.sourceId"
328
+ :title="item.title"
329
+ >
330
+ <customDatePicker
331
+ v-model="model[item.sourceId]"
332
+ type="year"
333
+ use-second
334
+ :clearable="!item.disabled"
335
+ :placeholder="`请选择${item.title}`"
336
+ />
337
+
321
338
  </wd-form-item>
322
339
  <wd-form-item
323
340
  v-else-if="ControlTypeSupportor.getControlType(item, props.entity && props.entity[item.sourceId]) === 'file' || ControlTypeSupportor.getControlType(item, props.entity && props.entity[item.sourceId]) === 'relfile'"
@@ -1,11 +1,10 @@
1
1
  <script setup lang="ts">
2
- import { defineOptions, defineProps } from 'vue'
3
2
  import { useRouter } from 'uni-mini-router'
4
3
 
5
4
  defineOptions({
6
5
  name: 'ListTopButtons',
7
6
  })
8
- const props = defineProps<{ mainCode: string, buttons: string[], sourceId: string, id?: string, pageTitle: string, pageType?: string, addEvent?: string }>()
7
+ const props = defineProps<{ mainCode: string, buttons: string[], sourceId: string, id?: string, pageTitle: string, pageType?: string, addEvent?: string, config?: any }>()
9
8
  const router = useRouter()
10
9
  // 跳转添加页面
11
10
  function gotoAddPage() {
@@ -14,7 +13,7 @@ function gotoAddPage() {
14
13
  </script>
15
14
 
16
15
  <template>
17
- <view class="flex justify-end gap-1 py-2 pr-2 .light:bg-white">
18
- <wd-icon v-if="buttons.includes('dtmplAdd')" name="plus" size="24px" class="text-[#6b7280]" @click="gotoAddPage" />
16
+ <view class="flex justify-end gap-1 py-2 px-2 .light:bg-white">
17
+ <wd-button class="flex-1" v-if="buttons.includes('dtmplAdd')" type="primary" @click="gotoAddPage" icon="plus">{{ props.config?.editBtnTitle || '新增' }}</wd-button>
19
18
  </view>
20
19
  </template>
@@ -0,0 +1,312 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted, onUnmounted, nextTick } from 'vue'
3
+
4
+ defineOptions({
5
+ name: 'ScanInput',
6
+ })
7
+
8
+ const props = withDefaults(defineProps<{
9
+ autoStart?: boolean
10
+ placeholder?: string
11
+ }>(), {
12
+ autoStart: false,
13
+ placeholder: '请输入或扫描二维码/条形码',
14
+ })
15
+
16
+ const emit = defineEmits<{
17
+ (e: 'scan', data: string): void
18
+ (e: 'input', data: string): void
19
+ (e: 'error', error: string): void
20
+ }>()
21
+
22
+ const inputValue = ref('')
23
+ const isScanning = ref(false)
24
+ const showScanModal = ref(false)
25
+ const isLoading = ref(false)
26
+ let html5QrcodeScanner: any = null
27
+
28
+ // #ifdef H5
29
+ import { Html5Qrcode } from 'html5-qrcode'
30
+
31
+ function checkCameraEnvironment(): { ok: boolean, reason?: string } {
32
+ const hostname = window.location.hostname
33
+ if (hostname === 'localhost' || hostname === '127.0.0.1') return { ok: true }
34
+ if (window.location.protocol !== 'https:') {
35
+ return { ok: false, reason: '当前非HTTPS环境,请在 https 下使用扫码功能' }
36
+ }
37
+ return { ok: true }
38
+ }
39
+
40
+ async function startScan() {
41
+ if (isScanning.value) return
42
+ const envCheck = checkCameraEnvironment()
43
+ if (!envCheck.ok) {
44
+ uni.showToast({ title: envCheck.reason || '环境不支持', icon: 'none' })
45
+ return
46
+ }
47
+
48
+ isLoading.value = true
49
+ isScanning.value = true
50
+ showScanModal.value = true
51
+
52
+ try {
53
+ await nextTick()
54
+ // 减少延时,库已打包进 bundle 无需等待加载
55
+ await new Promise(r => setTimeout(r, 200))
56
+
57
+ const container = document.getElementById('h5-scan-reader')
58
+ if (!container) throw new Error('容器未找到')
59
+
60
+ html5QrcodeScanner = new Html5Qrcode('h5-scan-reader')
61
+
62
+ await html5QrcodeScanner.start(
63
+ { facingMode: 'environment' },
64
+ {
65
+ fps: 5,
66
+ // 不设置 qrbox,全屏显示摄像头画面
67
+ },
68
+ (decodedText: string) => {
69
+ console.log('扫码成功:', decodedText)
70
+ // 防抖:短时间内多次触发只处理第一次
71
+ if (!isScanning.value) return
72
+ inputValue.value = decodedText
73
+ emit('scan', decodedText)
74
+ emit('input', decodedText)
75
+ stopScan()
76
+ uni.showToast({ title: '扫描成功', icon: 'success' })
77
+ },
78
+ () => {}, // 忽略中间帧错误
79
+ )
80
+ }
81
+ catch (error: any) {
82
+ console.error('扫码启动失败:', error)
83
+ let msg = '摄像头访问失败'
84
+ const errMsg = error.message || ''
85
+ if (/Permission|NotAllowed/i.test(errMsg)) msg = '请允许浏览器使用摄像头'
86
+ else if (/NotFound|not found/i.test(errMsg)) msg = '未检测到摄像头设备'
87
+ else if (/secure origin|insecure/i.test(errMsg)) msg = '需要HTTPS环境才能使用摄像头'
88
+
89
+ emit('error', msg)
90
+ isScanning.value = false
91
+ showScanModal.value = false
92
+ uni.showModal({ title: '提示', content: msg, showCancel: false })
93
+ }
94
+ finally {
95
+ isLoading.value = false
96
+ }
97
+ }
98
+
99
+ async function stopScan() {
100
+ isScanning.value = false
101
+ showScanModal.value = false
102
+ if (html5QrcodeScanner) {
103
+ try {
104
+ if (html5QrcodeScanner.isScanning) await html5QrcodeScanner.stop()
105
+ html5QrcodeScanner.clear()
106
+ }
107
+ catch { /* ignore */ }
108
+ html5QrcodeScanner = null
109
+ }
110
+ }
111
+ // #endif
112
+
113
+ function startUniScanCode() {
114
+ // #ifndef H5
115
+ uni.scanCode({
116
+ success(res: any) {
117
+ inputValue.value = res.result
118
+ emit('scan', res.result)
119
+ emit('input', res.result)
120
+ uni.showToast({ title: '扫描成功', icon: 'success' })
121
+ },
122
+ fail() { emit('error', '扫描已取消') },
123
+ })
124
+ // #endif
125
+ }
126
+
127
+ function handleStartScan() {
128
+ // #ifdef H5
129
+ isScanning.value ? stopScan() : startScan()
130
+ // #endif
131
+ // #ifndef H5
132
+ startUniScanCode()
133
+ // #endif
134
+ }
135
+
136
+ function handleConfirm() {
137
+ if (inputValue.value.trim()) emit('scan', inputValue.value.trim())
138
+ }
139
+
140
+ onMounted(() => { if (props.autoStart) handleStartScan() })
141
+ onUnmounted(() => { /* #ifdef H5 */ stopScan() /* #endif */ })
142
+ </script>
143
+
144
+ <template>
145
+ <view class="si-wrap">
146
+ <!-- 使用 wot-ui wd-input -->
147
+ <wd-input
148
+ v-model="inputValue"
149
+ class="!pr-3"
150
+ :placeholder="placeholder"
151
+ type="text"
152
+ confirm-type="search"
153
+ clearable
154
+ no-border
155
+ custom-class="si-wd-input"
156
+ @confirm="handleConfirm"
157
+ @change="(val: string) => emit('input', val)"
158
+ suffix-icon="scan"
159
+ @clicksuffixicon="handleStartScan"
160
+ >
161
+ </wd-input>
162
+
163
+ <!-- #ifdef H5 -->
164
+ <!-- 全屏扫码遮罩 -->
165
+ <view v-if="showScanModal" class="si-modal" catchtouchmove>
166
+ <!-- 摄像头区域 -->
167
+ <view class="si-camera-area">
168
+ <!-- 加载中 -->
169
+ <view v-if="isLoading" class="si-loading-mask">
170
+ <view class="si-loading-box">
171
+ <text class="si-loading-text">正在启动摄像头...</text>
172
+ </view>
173
+ </view>
174
+ <!-- 渲染容器 -->
175
+ <div id="h5-scan-reader" class="si-reader" />
176
+ <!-- 扫描框装饰 -->
177
+ <view v-show="isScanning && !isLoading" class="si-scan-frame">
178
+ <view class="si-scan-line" />
179
+ </view>
180
+ </view>
181
+
182
+ <!-- 底部提示 -->
183
+ <view class="si-modal-footer">
184
+ <text class="si-tip">识别二维码 / 条形码 / 商品码等</text>
185
+ </view>
186
+ </view>
187
+ <!-- #endif -->
188
+ </view>
189
+ </template>
190
+
191
+ <!-- 全局样式:html5-qrcode 内部元素 -->
192
+ <style lang="scss">
193
+ #h5-scan-reader {
194
+ width: 100%;
195
+ height: 100%;
196
+ border-radius: inherit;
197
+ display: flex;
198
+
199
+ video {
200
+ border-radius: inherit;
201
+ object-fit: cover;
202
+ }
203
+
204
+ #qr-shaded-region {
205
+ border-radius: inherit;
206
+ }
207
+ }
208
+ </style>
209
+
210
+ <style scoped lang="scss">
211
+ .si-wrap {
212
+ width: 100%;
213
+ padding: 16rpx;
214
+ box-sizing: border-box;
215
+
216
+ // ---- wot-ui 输入框容器 ----
217
+ :deep(.si-wd-input) {
218
+ background: #f6f7f9;
219
+ border-radius: 10rpx;
220
+ border: 2rpx solid #eaecef;
221
+ padding-right: 4rpx;
222
+ }
223
+
224
+ // ---- 全屏扫码遮罩 ----
225
+ .si-modal {
226
+ position: fixed;
227
+ top: 0; left: 0; right: 0; bottom: 0;
228
+ z-index: 999;
229
+ background: #000;
230
+ display: flex;
231
+ flex-direction: column;
232
+ }
233
+
234
+ .si-camera-area {
235
+ flex: 1;
236
+ width: 100%;
237
+ position: relative;
238
+ overflow: hidden;
239
+
240
+ .si-reader {
241
+ position: absolute;
242
+ top: 50%;
243
+ left: 0;
244
+ transform: translateY(-50%);
245
+ width: 100%;
246
+ height: 50vh;
247
+ }
248
+
249
+ // ---- 扫描框(覆盖在 reader 上方) ----
250
+ .si-scan-frame {
251
+ position: absolute;
252
+ top: 50%;
253
+ left: 0;
254
+ transform: translateY(-50%);
255
+ width: 100%;
256
+ height: 50vh;
257
+ pointer-events: none;
258
+ z-index: 5;
259
+ overflow: hidden;
260
+
261
+ .si-scan-line {
262
+ position: absolute;
263
+ top: 0;
264
+ left: 10rpx;
265
+ right: 10rpx;
266
+ height: 6rpx;
267
+ background: #07C160;
268
+ border-radius: 3rpx;
269
+ box-shadow: 0 0 20rpx 4rpx rgba(7,193,96,0.7);
270
+ animation: si-sweep 2.5s ease-in-out infinite;
271
+ }
272
+ }
273
+
274
+ .si-loading-mask {
275
+ position: absolute;
276
+ top: 50%;
277
+ left: 50%;
278
+ transform: translate(-50%, -50%);
279
+ z-index: 10;
280
+
281
+ .si-loading-box {
282
+ background: rgba(0,0,0,0.75);
283
+ padding: 30rpx 48rpx;
284
+ border-radius: 16rpx;
285
+
286
+ .si-loading-text {
287
+ color: #fff;
288
+ font-size: 28rpx;
289
+ }
290
+ }
291
+ }
292
+ }
293
+
294
+ .si-modal-footer {
295
+ padding: 40rpx 32rpx 80rpx;
296
+ text-align: center;
297
+ flex-shrink: 0;
298
+
299
+ .si-tip {
300
+ color: rgba(255,255,255,0.65);
301
+ font-size: 26rpx;
302
+ letter-spacing: 2rpx;
303
+ }
304
+ }
305
+ }
306
+
307
+ @keyframes si-sweep {
308
+ 0% { top: 0; opacity: 1; }
309
+ 50% { opacity: 1; }
310
+ 100% { top: calc(100% - 4rpx); opacity: 0.3; }
311
+ }
312
+ </style>
@@ -95,6 +95,7 @@ async function getPageConfig() {
95
95
  const res = await pageConfig(sourceId.value)
96
96
  if (res.ltmplConfig) {
97
97
  config.value = res.ltmplConfig
98
+ console.log('config', config.value)
98
99
  getEnums()
99
100
  }
100
101
  }
@@ -175,19 +176,18 @@ function tabSearchClick(data: any) {
175
176
  >
176
177
  <template #top>
177
178
  <slot name="top" />
178
- <view class="flex items-center pl-3 bg-white">
179
- <ListTopButtons
180
- :add-event="addEvent" :main-code="mainCode" :buttons="config.buttons" :source-id="sourceId"
181
- :page-title="pageTitle" :page-type="pageType"
182
- />
183
179
  <Search
184
180
  :main-code="mainCode" :enum-column="enumColumn" :criterias="config.criterias"
185
181
  :primary-criteria="config.primaryCriteria" :split2-tab-criterias="config.split2TabCriterias"
186
182
  @submit="submitSearch"
187
183
  />
184
+ <view>
185
+ <ListTopButtons
186
+ :add-event="addEvent" :main-code="mainCode" :buttons="config.buttons" :source-id="sourceId"
187
+ :page-title="pageTitle" :page-type="pageType"
188
+ :config="config"
189
+ />
188
190
  </view>
189
-
190
-
191
191
  <!-- 自定义tab搜索 -->
192
192
  <view v-if="config.split2TabCriterias">
193
193
  <tabSearch :split2-tab-criterias="config.split2TabCriterias" :enums="enumColumn" @click="tabSearchClick" />
@@ -6,6 +6,9 @@ import {
6
6
  import { useUser } from '../../composables/useUser'
7
7
  import { generateGradientColor } from '../../utils'
8
8
  import { useManualTheme } from '../../composables/useManualTheme'
9
+ import scan from '../scan-input/scan-input.vue'
10
+ import { getUserConfig } from '../../api/login'
11
+ import { useGlobalToast } from '../../composables/useGlobalToast'
9
12
 
10
13
  defineOptions({
11
14
  name: 'WuiUser',
@@ -21,6 +24,7 @@ const {
21
24
  } = useManualTheme()
22
25
  const user = useUser().getUserInfo()
23
26
  const dialog = useDialog()
27
+ const toast = useGlobalToast()
24
28
  const userInfo = ref({
25
29
  name: user.name,
26
30
  nickName: user.nickName,
@@ -50,6 +54,14 @@ function setting() {
50
54
  url: '/pages/system-settings/index',
51
55
  })
52
56
  }
57
+ async function clearCache() {
58
+ // uni.removeStorageSync('userInfo')
59
+ const res= await getUserConfig();
60
+ if(res.status=='success') {
61
+ toast.success({ msg: '清除成功!' })
62
+ uni.setStorageSync('userInfo', res.user)
63
+ }
64
+ }
53
65
 
54
66
  // 查看用户信息
55
67
  // function goUserInfo() {
@@ -60,6 +72,7 @@ function setting() {
60
72
  </script>
61
73
 
62
74
  <template>
75
+ <!-- <scan/> -->
63
76
  <view class="user">
64
77
  <view class="top-box">
65
78
  <!-- 用户头部信息 -->
@@ -87,6 +100,7 @@ function setting() {
87
100
  <!-- <wd-cell title="修改密码" icon="keywords" :is-link="true" :clickable="true" /> -->
88
101
  <wd-cell title="系统设置" icon="setting" :is-link="true" :clickable="true" @click="setting" />
89
102
  <wd-cell title="退出登录" icon="logout" :is-link="true" :clickable="true" @click="quit" />
103
+ <wd-cell title="清空缓存" :is-link="true" :clickable="true" @click="clearCache" />
90
104
  </wd-cell-group>
91
105
  <wd-dialog />
92
106
  </view>
@@ -41,7 +41,7 @@ export function useCompanyFieldFilter(
41
41
  }
42
42
 
43
43
  const formatFieldsItem=(company:string,item:any)=>{
44
- const transDefaultValue = item.usableRuleCriterias[0]?.transDefaultValue||''
44
+ const transDefaultValue = item.usableRuleCriterias?.[0]?.transDefaultValue||''
45
45
  const isHidden=item?.unusableMode==='hidden'
46
46
  const values = transDefaultValue.split(',').map(v => v.trim())
47
47
  if(!company){
@@ -73,7 +73,7 @@ export function useCompanyFieldFilter(
73
73
  return true
74
74
  }
75
75
  // 获取 transDefaultValue 并检查是否包含选中的公司
76
- const transDefaultValue = item.usableRuleCriterias[0]?.transDefaultValue
76
+ const transDefaultValue = item.usableRuleCriterias?.[0]?.transDefaultValue
77
77
  if (typeof transDefaultValue === 'string') {
78
78
  const values = transDefaultValue.split(',').map(v => v.trim())
79
79
  // console.log('transDefaultValue', company, 'values', values,values.includes(company))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wui-components-v2",
3
- "version": "1.1.54",
3
+ "version": "1.1.55",
4
4
  "description": "wui 组件库",
5
5
  "author": "wgxshh",
6
6
  "license": "MIT",
@@ -16,6 +16,7 @@
16
16
  "@wot-ui/ui": "^2.0.8",
17
17
  "china-address-parse": "^1.2.1",
18
18
  "dayjs": "^1.11.20",
19
+ "html5-qrcode": "^2.3.8",
19
20
  "jsencrypt": "^3.3.2",
20
21
  "sass": "^1.100.0",
21
22
  "z-paging": "^2.8.5"
package/store/language.ts CHANGED
@@ -48,7 +48,7 @@ export const useLanguageStore = defineStore('language', () => {
48
48
  * 加载并设置语言
49
49
  */
50
50
  const loadLanguage = async (lang: string): Promise<void> => {
51
- console.log(`网络加载语言 ${lang}...`)
51
+ // console.log(`网络加载语言 ${lang}...`)
52
52
  if (loadedLanguages.value.includes(lang)) {
53
53
  currentLanguage.value = lang
54
54
  return
@@ -129,7 +129,7 @@ export const useManualThemeStore = defineStore('manualTheme', {
129
129
  this.theme = systemTheme
130
130
  if (!this.hasUserSet) {
131
131
  this.followSystem = true
132
- console.log('首次启动,使用系统主题:', this.theme)
132
+ // console.log('首次启动,使用系统主题:', this.theme)
133
133
  }
134
134
  else {
135
135
  console.log('跟随系统主题:', this.theme)