vue2-client 1.16.52 → 1.16.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2-client",
3
- "version": "1.16.52",
3
+ "version": "1.16.55",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint",
@@ -177,8 +177,8 @@ defineExpose({
177
177
  border: 1px solid #CDCDCD;
178
178
  color: #5D5C5C;
179
179
  width: auto;
180
- min-width: 124px;
181
- max-width: 170px;
180
+ min-width: 110px;
181
+ max-width: 140px;
182
182
  white-space: nowrap;
183
183
  overflow: hidden;
184
184
  margin-right: 0px;
@@ -1,10 +1,17 @@
1
1
  <script setup lang="ts">
2
2
  import XAddNativeForm from '@vue2-client/base-client/components/common/XAddNativeForm/XAddNativeForm.vue'
3
- import { ref, computed, useAttrs } from 'vue'
3
+ import { ref, computed, useAttrs, defineProps } from 'vue'
4
4
 
5
5
  const xAddNativeFormRef = ref()
6
6
 
7
7
  const attrs = useAttrs()
8
+ const props = defineProps({
9
+ // 是否启用 horizontal 模式的自定义配置
10
+ enableHorizontalCustom: {
11
+ type: Boolean,
12
+ default: false
13
+ }
14
+ })
8
15
  const wrapperClassObject = computed(() => {
9
16
  const a = attrs
10
17
  const classes = {}
@@ -14,8 +21,9 @@ const wrapperClassObject = computed(() => {
14
21
  'query-conditions',
15
22
  'padding-50',
16
23
  'label-text-horizontal',
17
- 'item-control-width90%',
18
- 'label-text-justify'
24
+ 'item-control-width90',
25
+ 'label-text-justify',
26
+ 'item-control-width700',
19
27
  ]
20
28
  for (const key of booleanStyleKeys) {
21
29
  const val = a[key]
@@ -45,6 +53,7 @@ defineExpose({
45
53
  <x-add-native-form
46
54
  ref="xAddNativeFormRef"
47
55
  v-bind="$attrs"
56
+ :enable-horizontal-custom="props.enableHorizontalCustom"
48
57
  v-on="$listeners"
49
58
  >
50
59
  <template v-for="(_, name) in $slots" #[name]="slotData">
@@ -158,6 +167,11 @@ defineExpose({
158
167
  width: 90%;
159
168
  }
160
169
  }
161
-
170
+ /**表单项700宽度 */
171
+ &.h-form-item-control-width700{
172
+ :deep(.ant-form-item-control-wrapper){
173
+ width: 700px;
174
+ }
175
+ }
162
176
  }
163
177
  </style>
@@ -8,6 +8,11 @@ const props = defineProps({
8
8
  tabBarExtraConfig: {
9
9
  type: String,
10
10
  default: undefined
11
+ },
12
+ // 控制标签间距,覆盖内部 XTab 的默认 10px;默认置 0 清除外边距
13
+ tabBarGutter: {
14
+ type: Number,
15
+ default: 0
11
16
  }
12
17
  })
13
18
 
@@ -88,6 +93,7 @@ defineExpose({
88
93
  :class="[wrapperClassObject, extraItems.length > 1 ? 'h-tab-extra-wrapper-multiple' : '']">
89
94
  <x-tab
90
95
  ref="xTabRef"
96
+ :tabBarGutter="props.tabBarGutter"
91
97
  v-bind="$attrs"
92
98
  v-on="$listeners"
93
99
  >
@@ -125,6 +131,20 @@ defineExpose({
125
131
  }
126
132
 
127
133
  :deep(.ant-tabs-nav) {
134
+ // 去除导航容器默认左右内边距,避免收缩时左侧多出一截
135
+ .ant-tabs-nav-container,
136
+ .ant-tabs-nav-scroll {
137
+ margin-left: 0 !important;
138
+ margin-right: 0 !important;
139
+ }
140
+
141
+ // 移除左右切换箭头带来的占位
142
+ .ant-tabs-tab-prev,
143
+ .ant-tabs-tab-next {
144
+ display: none !important;
145
+ width: 0 !important;
146
+ }
147
+
128
148
  .ant-tabs-tab-next-icon {
129
149
  display: none;
130
150
  }
@@ -132,7 +152,7 @@ defineExpose({
132
152
  .ant-tabs-tab {
133
153
  background-color: transparent;
134
154
  border-radius: 6px 6px 0 0;
135
- color: #5D5C5C;
155
+ color: #313131;
136
156
  font-weight: bold;
137
157
  width: 134px;
138
158
  font-size: 18px;
@@ -155,6 +175,13 @@ defineExpose({
155
175
 
156
176
  :deep(.ant-tabs-bar) {
157
177
  border-bottom: 2px solid #0057FE;
178
+ margin: 20px 10px 0 10px;
179
+ }
180
+
181
+ // 滚动状态下的容器左右内边距清零,避免左侧多出一截
182
+ :deep(.ant-tabs-nav-container-scrolling) {
183
+ padding-left: 0 !important;
184
+ padding-right: 0 !important;
158
185
  }
159
186
 
160
187
  :deep(.ant-tabs-tab-arrow-show) {
@@ -375,7 +402,7 @@ defineExpose({
375
402
  padding-top: 2px;
376
403
  }
377
404
  :deep(.ant-tabs-extra-content){
378
- line-height: 0px !important;
405
+ line-height: 0px !important;
379
406
  }
380
407
  }
381
408
  }
@@ -186,7 +186,13 @@ export default {
186
186
  default: null
187
187
  }
188
188
  },
189
- props: {},
189
+ props: {
190
+ // 是否启用 horizontal 模式的自定义配置
191
+ enableHorizontalCustom: {
192
+ type: Boolean,
193
+ default: false
194
+ }
195
+ },
190
196
  data () {
191
197
  return {
192
198
  DEFAULT_GROUP_NAME,
@@ -324,6 +330,17 @@ export default {
324
330
  },
325
331
  formItemLayoutGen () {
326
332
  if (this.layout === 'horizontal') {
333
+ // 如果启用了自定义配置,从 formItemLayout 读取(支持 0 值)
334
+ if (this.enableHorizontalCustom && this.formItemLayout) {
335
+ return {
336
+ labelCol: {
337
+ span: (this.formItemLayout.labelCol ?? 4),
338
+ offset: (this.formItemLayout.offset ?? 2)
339
+ },
340
+ wrapperCol: { span: (this.formItemLayout.wrapperCol ?? 14) },
341
+ }
342
+ }
343
+ // 默认配置
327
344
  return {
328
345
  labelCol: { span: 4, offset: 2 },
329
346
  wrapperCol: { span: 14 },
@@ -84,7 +84,7 @@
84
84
  </template>
85
85
  </template>
86
86
  </div>
87
- <a-card v-else class="flexItem" :bordered="false">
87
+ <a-card v-else class="flexItem" :bordered="false" :body-style="{ padding: 0 }">
88
88
  <!-- 插槽渲染 -->
89
89
  <template v-if="Array.isArray(cell)">
90
90
  <!-- 处理 cell 是数组的情况 -->
@@ -580,7 +580,7 @@ export default {
580
580
  })
581
581
 
582
582
  // 处理子节点样式
583
- this.parseNestedStyles(classConfig, childStyles)
583
+ // this.parseNestedStyles(classConfig, childStyles)
584
584
  })
585
585
  return {
586
586
  rootStyles,
@@ -13,6 +13,7 @@
13
13
  :scroll="{ x: tableContext.scrollXWidth, y: tableContext.scrollYHeight }"
14
14
  :showPagination="tableContext.showPagination"
15
15
  :hidePagination="tableContext.simpleMode"
16
+ :customPagination="tableContext.customPagination"
16
17
  :showSelected="!tableContext.simpleMode"
17
18
  :pageSize="tableContext.simpleMode ? 1000 : undefined"
18
19
  :pageMaxSize="tableContext.pageMaxSize"
@@ -1,5 +1,10 @@
1
1
  <template>
2
2
  <div class="xcharge-wrapper">
3
+ <!-- 标题 -->
4
+ <x-title :title="config.title || '结算-收费方式'" :dot="true" />
5
+ <!-- 分割线 -->
6
+ <div class="top-divider"></div>
7
+
3
8
  <!-- 顶部金额信息区(表单行风格,宽度固定,每行4个) -->
4
9
  <a-row :gutter="0" class="amount-info">
5
10
  <a-col v-for="item in config.amountFields" :key="item.field" :span="6" class="form-row">
@@ -13,50 +18,60 @@
13
18
  </a-col>
14
19
  </a-row>
15
20
 
16
- <a-divider/>
17
-
18
- <!-- 支付方式选择区 -->
19
- <div class="payment-methods">
20
- <a-button
21
- v-for="method in config.paymentMethods"
22
- :key="method.key"
23
- :class="['payment-btn', {
24
- active: isMixedPaymentEnabled ? selectedMethods.includes(method.key) : selectedMethod === method.key
25
- }]"
26
- @click="isMixedPaymentEnabled ? toggleMethod(method.key) : selectMethod(method.key)"
27
- >
28
- <div>
29
- <div class="pay-label">{{ method.label }}</div>
21
+ <!-- 支付方式选择区和支付详情容器 -->
22
+ <div class="payment-container">
23
+ <!-- 支付方式选择区 -->
24
+ <div class="payment-methods">
25
+ <div
26
+ v-for="method in config.paymentMethods"
27
+ :key="method.key"
28
+ :class="['payment-option', {
29
+ active: isMixedPaymentEnabled ? selectedMethods.includes(method.key) : selectedMethod === method.key
30
+ }]"
31
+ @click="isMixedPaymentEnabled ? toggleMethod(method.key) : selectMethod(method.key)"
32
+ >
33
+ <!-- 图标区域 -->
34
+ <div class="payment-icon" :class="{ multiple: getMethodIcons(method).length > 1 }">
35
+ <template v-if="getMethodIcons(method).length">
36
+ <img
37
+ v-for="(src, idx) in getMethodIcons(method)"
38
+ :key="idx"
39
+ :src="src"
40
+ :alt="method.label"
41
+ class="icon-image"
42
+ />
43
+ </template>
44
+ <div v-else class="icon-placeholder">{{ method.label.charAt(0) }}</div>
45
+ </div>
46
+ <!-- 标签区域 -->
47
+ <div class="payment-label">{{ method.label }}</div>
48
+ <!-- 选中指示器 -->
30
49
  <div v-if="isMixedPaymentEnabled && selectedMethods.includes(method.key)" class="selected-indicator">✓</div>
31
50
  </div>
32
- </a-button>
33
- </div>
51
+ </div>
34
52
 
35
- <a-divider v-if="isMixedPaymentEnabled && selectedMethods.length > 1"/>
36
-
37
- <!-- 支付详情(同一行展示,仅在混合支付且选择方式>1时出现) -->
38
- <div v-if="isMixedPaymentEnabled && selectedMethods.length > 1" class="mixed-payment-section simple">
39
- <div class="payment-operation-row">
40
- <div class="payment-inline">
41
- <div v-for="method in selectedMethods" :key="method" class="inline-field">
42
- <label class="form-label">{{ getAmountLabel(method) }}</label>
43
- <a-input
44
- v-model="paymentAmounts[method]"
45
- type="number"
46
- placeholder="请输入金额"
47
- class="form-input"
48
- @change="updatePaymentAmount(method, $event)"
49
- />
53
+ <!-- 支付详情(同一行展示,仅在混合支付手动模式且选择方式>1时出现) -->
54
+ <div v-if="isMixedPaymentManualMode && selectedMethods.length > 1" class="mixed-payment-section simple">
55
+ <div class="payment-operation-row">
56
+ <div class="payment-inline">
57
+ <div v-for="method in selectedMethods" :key="method" class="inline-field">
58
+ <label class="form-label">{{ getAmountLabel(method) }}</label>
59
+ <a-input
60
+ v-model="paymentAmounts[method]"
61
+ type="number"
62
+ placeholder="请输入金额"
63
+ class="form-input"
64
+ @change="updatePaymentAmount(method, $event)"
65
+ />
66
+ </div>
50
67
  </div>
51
68
  </div>
52
69
  </div>
53
70
  </div>
54
71
 
55
- <a-divider/>
56
-
57
72
  <!-- 底部金额和操作区(表单行风格,宽度固定) -->
58
73
  <a-row :gutter="0" class="bottom-info">
59
- <a-col v-for="item in config.bottomFields" :key="item.field" :span="8" class="form-row">
74
+ <a-col v-for="item in config.bottomFields" :key="item.field" :span="6" class="form-row">
60
75
  <label class="form-label">{{ item.label }}</label>
61
76
  <a-input
62
77
  v-model="data[item.field]"
@@ -82,9 +97,13 @@
82
97
 
83
98
  <script>
84
99
  import { getConfigByName, runLogic } from '@vue2-client/services/api/common'
100
+ import XTitle from '../XTitle/XTitle.vue'
85
101
 
86
102
  export default {
87
103
  name: 'XCharge',
104
+ components: {
105
+ XTitle
106
+ },
88
107
  props: {
89
108
  queryParamsName: {
90
109
  type: String,
@@ -98,11 +117,13 @@ export default {
98
117
  data () {
99
118
  return {
100
119
  config: {
120
+ title: '', // 标题配置
101
121
  amountFields: [],
102
122
  paymentMethods: [],
103
123
  bottomFields: [],
104
- actionButtons: [],
105
- enableMixedPayment: false // 默认关闭混合支付
124
+ actionButtons: [], // 操作按钮
125
+ enableMixedPayment: false, // 默认关闭混合支付
126
+ mixedPaymentMode: 'manual' // 混合支付模式:'manual'手动输入,'auto'自动模式
106
127
  },
107
128
  data: {},
108
129
  selectedMethod: '', // 保持向后兼容
@@ -110,14 +131,13 @@ export default {
110
131
  paymentAmounts: {}, // 各支付方式的金额
111
132
  // 事件名配置(可通过配置覆盖)
112
133
  eventNames: {
113
- method: 'method',
114
- methods: 'methods',
115
- totalPaymentAmount: 'totalPaymentAmount',
116
- action: 'action',
117
- // 新增:单个支付方式金额变更事件
118
- paymentAmountChange: 'paymentAmountChange'
134
+ method: 'method', // 单选支付方式变更事件
135
+ methods: 'methods', // 多选支付方式变更事件
136
+ totalPaymentAmount: 'totalPaymentAmount', // 总支付金额变更事件
137
+ action: 'action', // 操作按钮点击事件
138
+ paymentAmountChange: 'paymentAmountChange' // 新增:单个支付方式金额变更事件
119
139
  },
120
- hasInitialized: false
140
+ hasInitialized: false // 是否已初始化
121
141
  }
122
142
  },
123
143
  emits: ['init'],
@@ -126,6 +146,14 @@ export default {
126
146
  isMixedPaymentEnabled () {
127
147
  return this.config && this.config.enableMixedPayment === true
128
148
  },
149
+ // 是否为混合支付自动模式
150
+ isMixedPaymentAutoMode () {
151
+ return this.isMixedPaymentEnabled && this.config.mixedPaymentMode === 'auto'
152
+ },
153
+ // 是否为混合支付手动模式
154
+ isMixedPaymentManualMode () {
155
+ return this.isMixedPaymentEnabled && this.config.mixedPaymentMode === 'manual'
156
+ },
129
157
  // 预计算:金额标签映射(仅基于 paymentMethods 配置)
130
158
  amountLabelMap () {
131
159
  const map = {}
@@ -135,7 +163,7 @@ export default {
135
163
  const key = m && m.key
136
164
  const label = m && m.label
137
165
  if (!key) return
138
- map[key] = m && m.amountLabel ? m.amountLabel : `${label || key}金额`
166
+ map[key] = `${label || key}金额`
139
167
  })
140
168
  return map
141
169
  }
@@ -147,6 +175,44 @@ export default {
147
175
  if (hasParam) this.hasInitialized = true
148
176
  },
149
177
  methods: {
178
+ // -------- 金额精度工具:使用「分」进行运算,避免浮点精度问题 --------
179
+ // 解析金额为分(整数);非法输入按 0 处理
180
+ parseAmountToCents (value) {
181
+ if (value === null || value === undefined || value === '') return 0
182
+ // 允许字符串或数字,移除非数字与小数点字符
183
+ const str = String(value).replace(/[^0-9.-]/g, '')
184
+ if (str === '' || str === '-' || isNaN(Number(str))) return 0
185
+ // 四舍五入到分
186
+ return Math.round(Number(str) * 100)
187
+ },
188
+ // 将分格式化为金额(保留两位小数)
189
+ formatCentsToAmount (cents) {
190
+ const v = Number(cents) / 100
191
+ // 使用 toFixed 返回字符串,再转为数字,避免 0.30000000000004
192
+ return Number(v.toFixed(2))
193
+ },
194
+ // 规范化金额:返回保留两位小数的数字
195
+ normalizeAmount (value) {
196
+ return this.formatCentsToAmount(this.parseAmountToCents(value))
197
+ },
198
+ // 解析多图标:支持 method.icons 数组;否则回退到单个 icon
199
+ getMethodIcons (method) {
200
+ if (!method) return []
201
+ const icons = Array.isArray(method.icons) ? method.icons : (method.icon ? [method.icon] : [])
202
+ const resolved = icons.map(n => {
203
+ if (!n || typeof n !== 'string') return ''
204
+ if (n.startsWith('/') || n.startsWith('http')) return n
205
+ const baseName = n.replace(/\.(png|jpg|jpeg|webp|svg)$/i, '')
206
+ // 仅使用 @vue2-client 资源解析
207
+ try { return require('@vue2-client/assets/img/paymentMethod/' + baseName + '.png') } catch (e) {}
208
+ try { return require('@vue2-client/assets/img/paymentMethod/' + baseName + '.jpg') } catch (e) {}
209
+ try { return require('@vue2-client/assets/img/paymentMethod/' + baseName + '.jpeg') } catch (e) {}
210
+ try { return require('@vue2-client/assets/img/paymentMethod/' + baseName + '.webp') } catch (e) {}
211
+ try { return require('@vue2-client/assets/img/paymentMethod/' + baseName + '.svg') } catch (e) {}
212
+ return ''
213
+ }).filter(Boolean)
214
+ return resolved
215
+ },
150
216
  // 同步 data 中的 selectedMethods / paymentAmounts 到本地状态;并补回缺失的 data 字段
151
217
  hydrateFromData () {
152
218
  const incomingMethods = Array.isArray(this.data && this.data.selectedMethods) ? this.data.selectedMethods : undefined
@@ -231,17 +297,16 @@ export default {
231
297
  const method = this.config.paymentMethods.find(m => m.key === methodKey)
232
298
  return method ? method.label : methodKey
233
299
  },
234
- // 获取支付金额字段标签(优先配置映射,其次方法自带 amountLabel,最后回退)
300
+ // 获取支付金额字段标签(优先使用映射,否则回退为“{label}金额”)
235
301
  getAmountLabel (methodKey) {
236
302
  const mapped = (this.config.paymentAmountLabels && this.config.paymentAmountLabels[methodKey]) || ''
237
303
  if (mapped) return mapped
238
304
  const method = this.config.paymentMethods.find(m => m.key === methodKey)
239
- if (method && method.amountLabel) return method.amountLabel
240
305
  return `${method ? method.label : methodKey}金额`
241
306
  },
242
307
  // 新增:更新支付金额
243
308
  updatePaymentAmount (methodKey, event) {
244
- const amount = parseFloat(event.target.value) || 0
309
+ const amount = this.normalizeAmount(event && event.target ? event.target.value : event)
245
310
  this.$set(this.paymentAmounts, methodKey, amount)
246
311
  if (!this.data.paymentAmounts || typeof this.data.paymentAmounts !== 'object') this.$set(this.data, 'paymentAmounts', {})
247
312
  this.$set(this.data.paymentAmounts, methodKey, amount)
@@ -251,12 +316,31 @@ export default {
251
316
  },
252
317
  // 新增:计算总支付金额
253
318
  calculateTotalPayment () {
254
- const total = Object.values(this.paymentAmounts).reduce((sum, amount) => {
255
- return sum + (parseFloat(amount) || 0)
319
+ const totalCents = Object.values(this.paymentAmounts).reduce((sumCents, amount) => {
320
+ return sumCents + this.parseAmountToCents(amount)
256
321
  }, 0)
322
+ const total = this.formatCentsToAmount(totalCents)
257
323
  this.data.totalPaymentAmount = total
258
324
  this.$emit(this.eventNames.totalPaymentAmount, total)
259
325
  },
326
+ // 新增:设置支付金额(由外部调用)
327
+ setPaymentAmounts (amounts) {
328
+ if (!this.isMixedPaymentEnabled || !amounts || typeof amounts !== 'object') return
329
+ // 更新本地状态
330
+ Object.keys(amounts).forEach(method => {
331
+ const normalized = this.normalizeAmount(amounts[method])
332
+ this.$set(this.paymentAmounts, method, normalized)
333
+ })
334
+ // 更新数据模型
335
+ if (!this.data.paymentAmounts || typeof this.data.paymentAmounts !== 'object') {
336
+ this.$set(this.data, 'paymentAmounts', {})
337
+ }
338
+ Object.keys(amounts).forEach(method => {
339
+ const normalized = this.normalizeAmount(amounts[method])
340
+ this.$set(this.data.paymentAmounts, method, normalized)
341
+ })
342
+ this.calculateTotalPayment()
343
+ },
260
344
  getConfig (configName, param) {
261
345
  if (configName) {
262
346
  console.log('configName', configName)
@@ -268,11 +352,13 @@ export default {
268
352
  return
269
353
  }
270
354
  this.config = {
355
+ title: res.title || '', // 标题配置
271
356
  amountFields: res.amountFields || [],
272
357
  paymentMethods: res.paymentMethods || [],
273
358
  bottomFields: res.bottomFields || [],
274
359
  actionButtons: res.actionButtons || [],
275
- enableMixedPayment: res.enableMixedPayment === true
360
+ enableMixedPayment: res.enableMixedPayment === true,
361
+ mixedPaymentMode: res.mixedPaymentMode || 'manual' // 默认手动模式
276
362
  }
277
363
  // 事件名覆盖(fronImport风格)
278
364
  if (res.eventNames && typeof res.eventNames === 'object') {
@@ -340,14 +426,14 @@ export default {
340
426
  // 使用默认数据
341
427
  useDefaultData () {
342
428
  this.data = {
343
- f_amount: 100,
429
+ f_amount: 0,
344
430
  f_insurance_amount: 0,
345
- f_self_amount: 100,
346
- f_balance: 100,
431
+ f_self_amount: 0,
432
+ f_balance: 0,
347
433
  out_of_pocket_amount: 0,
348
434
  biscount_amount: 0,
349
435
  billing_amount: 0,
350
- received: 100,
436
+ received: 0,
351
437
  change: 0
352
438
  }
353
439
  },
@@ -399,10 +485,14 @@ export default {
399
485
  <style scoped>
400
486
  .xcharge-wrapper {
401
487
  background: #fff;
402
- padding: 24px;
488
+ padding: 12px;
403
489
  border-radius: 8px;
404
490
  }
405
491
 
492
+ .charge-title {
493
+ margin-bottom: 16px;
494
+ }
495
+
406
496
  .amount-info {
407
497
  margin-bottom: 20px;
408
498
  display: flex;
@@ -410,26 +500,34 @@ export default {
410
500
  row-gap: 16px;
411
501
  }
412
502
 
503
+ /* 顶部分割线 */
504
+ .top-divider {
505
+ height: 2px;
506
+ opacity: 1;
507
+ background: #0057FE;
508
+ margin-top: 8px;
509
+ margin-bottom: 12px;
510
+ }
511
+
413
512
  .form-row {
414
513
  display: flex;
415
514
  align-items: center;
416
- margin-bottom: 30px;
417
515
  }
418
516
 
419
517
  .form-label {
420
518
  width: 100px;
421
- font-size: 18px;
519
+ font-size: 16px;
422
520
  color: #333;
423
521
  font-weight: bold;
424
- text-align: left;
522
+ text-align: center;
425
523
  letter-spacing: 1px;
426
524
  flex-shrink: 0;
427
525
  }
428
526
 
429
527
  .form-input {
430
- width: 180px;
431
- height: 40px;
432
- font-size: 18px;
528
+ width: 200px;
529
+ height: 30px;
530
+ font-size: 16px;
433
531
  border-radius: 8px;
434
532
  border: 1.5px solid #dcdfe6;
435
533
  background: #fff;
@@ -442,44 +540,103 @@ export default {
442
540
  background: #f5f5f5;
443
541
  }
444
542
 
543
+ /* 支付容器 */
544
+ .payment-container {
545
+ border: 1px solid #e4e7ed;
546
+ border-radius: 8px;
547
+ margin-bottom: 10px;
548
+ height: 464px;
549
+ padding: 112px 31px 0px;
550
+ }
551
+
445
552
  .payment-methods {
446
553
  display: flex;
447
554
  justify-content: space-around;
448
- margin: 32px 0;
555
+ margin: 0 0 16px 0;
556
+ gap: 20px;
449
557
  }
450
558
 
451
- .payment-btn {
452
- width: 180px;
453
- height: 120px;
454
- margin: 0 16px;
559
+ .payment-option {
560
+ width: 220px;
561
+ height: 200px;
455
562
  display: flex;
563
+ flex-direction: column;
456
564
  align-items: center;
457
565
  justify-content: center;
458
- background: #f7f8fa;
459
566
  border: 1.5px solid #e4e7ed;
460
- color: #333;
461
- font-weight: bold;
462
- font-size: 24px;
463
- transition: all 0.2s;
464
- box-shadow: none;
465
567
  border-radius: 12px;
568
+ cursor: pointer;
569
+ transition: all 0.2s;
570
+ position: relative;
571
+ }
572
+
573
+ .payment-option:hover {
574
+ background: #0057FE;
575
+ border-color: #0057FE;
576
+ box-shadow: 0 2px 8px rgba(0, 87, 254, 0.2);
577
+ }
578
+
579
+ .payment-option.active {
580
+ background: #587EDF;
581
+ border-color: #587EDF;
582
+ color: #fff;
466
583
  }
467
584
 
468
- .payment-btn.active {
469
- background: #409eff;
470
- border-color: #409eff;
585
+ .payment-option:hover .payment-label {
471
586
  color: #fff;
472
587
  }
473
588
 
474
- .pay-label {
589
+ .payment-icon {
590
+ position: relative;
591
+ width: 65px;
592
+ height: 65px;
593
+ margin-bottom: 12px;
594
+ display: flex;
595
+ align-items: center;
596
+ justify-content: center;
597
+ }
598
+
599
+ .payment-icon.multiple {
600
+ gap: 12px;
601
+ }
602
+
603
+ .icon-image {
604
+ width: 65px;
605
+ height: 65px;
606
+ object-fit: contain;
607
+ border-radius: 8px;
608
+ }
609
+
610
+ .icon-placeholder {
611
+ width: 100%;
612
+ height: 100%;
613
+ background: #ddd;
614
+ border-radius: 8px;
615
+ display: flex;
616
+ align-items: center;
617
+ justify-content: center;
475
618
  font-size: 24px;
476
619
  font-weight: bold;
620
+ color: #666;
621
+ }
622
+
623
+ /* 移除徽章样式:模板已不再使用 */
624
+
625
+ .payment-label {
626
+ font-size: 36px;
627
+ font-weight: 500;
628
+ text-align: center;
629
+ color: #333;
630
+ }
631
+
632
+ .payment-option.active .payment-label {
633
+ color: #fff;
477
634
  }
478
635
 
479
636
  .selected-indicator {
480
637
  position: absolute;
481
- top: 8px;
482
- right: 8px;
638
+ top: 12px;
639
+ right: 12px;
483
640
  background: #52c41a;
484
641
  color: #fff;
485
642
  border-radius: 50%;
@@ -493,11 +650,10 @@ export default {
493
650
  }
494
651
 
495
652
  .mixed-payment-section {
496
- margin: 24px 0;
497
- padding: 20px;
498
- background: #f8f9fa;
499
- border-radius: 8px;
500
- border: 1px solid #e9ecef;
653
+ margin: 0;
654
+ padding: 0;
655
+ background: transparent;
656
+ border: none;
501
657
  }
502
658
 
503
659
  .mixed-payment-section.simple {
@@ -510,7 +666,7 @@ export default {
510
666
  border: none;
511
667
  background: transparent;
512
668
  padding: 0;
513
- margin-bottom: 12px;
669
+ margin: 0;
514
670
  }
515
671
 
516
672
  .section-title {
@@ -560,11 +716,11 @@ export default {
560
716
 
561
717
  .payment-inline {
562
718
  display: flex;
563
- flex-wrap: nowrap;
719
+ flex-wrap: wrap;
564
720
  align-items: center;
565
721
  justify-content: center;
566
722
  column-gap: 32px;
567
- overflow-x: auto;
723
+ row-gap: 16px;
568
724
  }
569
725
 
570
726
  .inline-field {
@@ -576,6 +732,7 @@ export default {
576
732
 
577
733
  .payment-inline .form-label {
578
734
  width: auto;
735
+ font-size: 16px;
579
736
  }
580
737
 
581
738
  .pay-shortcut {
@@ -607,4 +764,5 @@ export default {
607
764
  height: 38px;
608
765
  min-width: 90px;
609
766
  }
767
+
610
768
  </style>
@@ -10,22 +10,96 @@ export default {
10
10
  methods: [],
11
11
  total: 0,
12
12
  lastAmountChange: null,
13
- lastAction: null
13
+ lastAction: null,
14
+ currentMode: 'manual' // 当前模式:manual, auto, single
14
15
  }
15
16
  },
16
17
  methods: {
17
18
  onMethod (value) { console.log('【事件】单选支付方式(method):', value) },
18
- onMethods (values) { console.log('【事件】多选支付方式(methods):', values); this.methods = values },
19
+ onMethods (values) {
20
+ console.log('【事件】多选支付方式(methods):', values);
21
+ this.methods = values
22
+ // 在自动模式下,由外部决定金额分配
23
+ if (this.currentMode === 'auto') {
24
+ this.autoDistributeAmounts(values)
25
+ }
26
+ },
19
27
  onTotal (total) { console.log('【事件】合计金额变更(totalPaymentAmount):', total); this.total = total },
20
28
  onAmountChange (payload) { console.log('【事件】单个方式金额变更(paymentAmountChange):', payload, '→ 方式:', payload.method, ' 金额:', payload.amount); this.lastAmountChange = payload },
21
- onAction (payload) { console.log('【事件】操作(action):', payload); this.lastAction = payload }
29
+ onAction (payload) { console.log('【事件】操作(action):', payload); this.lastAction = payload },
30
+ switchMode (mode) {
31
+ this.currentMode = mode
32
+ // 根据模式切换配置
33
+ switch (mode) {
34
+ case 'manual':
35
+ this.queryParamsName = 'testChargeConfig'
36
+ break
37
+ case 'auto':
38
+ this.queryParamsName = 'testAutoMixedChargeConfig'
39
+ break
40
+ case 'single':
41
+ this.queryParamsName = 'testSingleChargeConfig'
42
+ break
43
+ }
44
+ },
45
+ getModeText (mode) {
46
+ const modeMap = {
47
+ 'manual': '混合支付-手动模式',
48
+ 'auto': '混合支付-自动模式',
49
+ 'single': '单选模式'
50
+ }
51
+ return modeMap[mode] || mode
52
+ },
53
+ // 外部金额分配逻辑(示例:平均分配)
54
+ autoDistributeAmounts (selectedMethods) {
55
+ if (!selectedMethods || selectedMethods.length === 0) return
56
+
57
+ // 示例:假设总金额为100,平均分配
58
+ const totalAmount = 100
59
+ const amountPerMethod = totalAmount / selectedMethods.length
60
+
61
+ const amounts = {}
62
+ selectedMethods.forEach(method => {
63
+ amounts[method] = amountPerMethod
64
+ })
65
+
66
+ // 调用组件的setPaymentAmounts方法
67
+ this.$refs.xCharge.setPaymentAmounts(amounts)
68
+ }
22
69
  }
23
70
  }
24
71
  </script>
25
72
 
26
73
  <template>
27
74
  <div class="demo">
75
+ <!-- 模式切换按钮 -->
76
+ <div class="mode-switcher">
77
+ <h3>XCharge 组件测试 - 混合支付模式切换</h3>
78
+ <div class="mode-buttons">
79
+ <a-button
80
+ :type="currentMode === 'manual' ? 'primary' : 'default'"
81
+ @click="switchMode('manual')"
82
+ >
83
+ 混合支付-手动模式
84
+ </a-button>
85
+ <a-button
86
+ :type="currentMode === 'auto' ? 'primary' : 'default'"
87
+ @click="switchMode('auto')"
88
+ >
89
+ 混合支付-自动模式
90
+ </a-button>
91
+ <a-button
92
+ :type="currentMode === 'single' ? 'primary' : 'default'"
93
+ @click="switchMode('single')"
94
+ >
95
+ 单选模式
96
+ </a-button>
97
+ </div>
98
+ <p class="current-mode">当前模式:{{ getModeText(currentMode) }}</p>
99
+ </div>
100
+
28
101
  <x-charge
102
+ ref="xCharge"
29
103
  :queryParamsName="queryParamsName"
30
104
  @method="onMethod"
31
105
  @methods="onMethods"
@@ -44,6 +118,28 @@ export default {
44
118
 
45
119
  <style scoped>
46
120
  .demo { padding: 16px; }
121
+ .mode-switcher {
122
+ margin-bottom: 24px;
123
+ padding: 16px;
124
+ background: #f8f9fa;
125
+ border-radius: 8px;
126
+ border: 1px solid #e9ecef;
127
+ }
128
+ .mode-switcher h3 {
129
+ margin: 0 0 16px 0;
130
+ color: #333;
131
+ font-size: 18px;
132
+ }
133
+ .mode-buttons {
134
+ display: flex;
135
+ gap: 12px;
136
+ margin-bottom: 12px;
137
+ }
138
+ .current-mode {
139
+ margin: 0;
140
+ color: #666;
141
+ font-weight: 500;
142
+ }
47
143
  .info { margin-top: 16px; color: #333; }
48
144
  .info p { margin: 6px 0; }
49
145
  </style>
@@ -1,5 +1,9 @@
1
- // 测试配置 - 用于演示混合支付功能
1
+ // XCharge 组件测试配置文件 - 最新最全版本
2
+ // 包含所有功能:图标、徽章、混合支付模式、XTitle集成等
3
+
4
+ // 1. 完整功能配置 - 混合支付手动模式(带图标和徽章)
2
5
  export const testChargeConfig = {
6
+ "title": "结算-收费方式", // 使用XTitle组件显示标题
3
7
  "amountFields": [
4
8
  {
5
9
  "field": 'f_amount',
@@ -35,19 +39,23 @@ export const testChargeConfig = {
35
39
  "paymentMethods": [
36
40
  {
37
41
  "key": '医保卡',
38
- "label": '医保卡'
42
+ "label": '医保卡',
43
+ "icon": '/img/paymentMethod/medical-card.png'
39
44
  },
40
45
  {
41
46
  "key": '微信/支付宝',
42
- "label": '微信/支付宝'
47
+ "label": '微信/支付宝',
48
+ "icon": '/img/paymentMethod/wechat-alipay.png'
43
49
  },
44
50
  {
45
51
  "key": '银行卡',
46
- "label": '银行卡'
52
+ "label": '银行卡',
53
+ "icon": '/img/paymentMethod/bank-card.png'
47
54
  },
48
55
  {
49
56
  "key": '现金',
50
- "label": '现金'
57
+ "label": '现金',
58
+ "icon": '/img/paymentMethod/cash.png'
51
59
  }
52
60
  ],
53
61
  "bottomFields": [
@@ -85,6 +93,7 @@ export const testChargeConfig = {
85
93
  }
86
94
  ],
87
95
  "enableMixedPayment": true, // 启用混合支付
96
+ "mixedPaymentMode": 'manual', // 混合支付模式:'manual'手动输入,'auto'自动模式
88
97
  // 可配置事件名,参考 fronImport 风格
89
98
  "eventNames": {
90
99
  "method": 'method',
@@ -95,8 +104,101 @@ export const testChargeConfig = {
95
104
  "dataSourceConfig": 'testChargeLogic'
96
105
  }
97
106
 
98
- // 单选模式测试配置
107
+ // 2. 混合支付自动模式配置(外部控制金额分配)
108
+ export const testAutoMixedChargeConfig = {
109
+ "title": "自动收费管理",
110
+ "amountFields": [
111
+ {
112
+ "field": 'f_amount',
113
+ "disabled": false,
114
+ "label": '费用总额'
115
+ },
116
+ {
117
+ "field": 'f_insurance_amount',
118
+ "disabled": true,
119
+ "label": '医保支付'
120
+ },
121
+ {
122
+ "field": 'f_self_amount',
123
+ "disabled": true,
124
+ "label": '自费金额'
125
+ },
126
+ {
127
+ "field": 'out_of_pocket_amount',
128
+ "disabled": true,
129
+ "label": '自付金额'
130
+ }
131
+ ],
132
+ "paymentMethods": [
133
+ {
134
+ "key": '医保卡',
135
+ "label": '医保卡',
136
+ "icon": 'icon1'
137
+ },
138
+ {
139
+ "key": '微信/支付宝',
140
+ "label": '微信/支付宝',
141
+ "icons": ['icon1','icon1']
142
+ },
143
+ {
144
+ "key": '银行卡',
145
+ "label": '银行卡',
146
+ "icon": 'icon1'
147
+ },
148
+ {
149
+ "key": '现金',
150
+ "label": '现金',
151
+ "icon": 'icon1'
152
+ }
153
+ ],
154
+ "bottomFields": [
155
+ {
156
+ "field": 'f_balance',
157
+ "label": '账户余额',
158
+ "disabled": false
159
+ },
160
+ {
161
+ "field": 'received',
162
+ "label": '实收',
163
+ "disabled": false
164
+ },
165
+ {
166
+ "field": 'change',
167
+ "label": '找零',
168
+ "disabled": false
169
+ }
170
+ ],
171
+ "actionButtons": [
172
+ {
173
+ "key": 'charge',
174
+ "label": '收费',
175
+ "icon": 'check'
176
+ },
177
+ {
178
+ "key": 'refund',
179
+ "label": '退费',
180
+ "icon": 'undo'
181
+ },
182
+ {
183
+ "key": 'print',
184
+ "label": '打印',
185
+ "icon": 'printer'
186
+ }
187
+ ],
188
+ "enableMixedPayment": true, // 启用混合支付
189
+ "mixedPaymentMode": 'auto', // 自动模式:不需要手动输入金额,由外部控制
190
+ "eventNames": {
191
+ "method": 'method',
192
+ "methods": 'methods',
193
+ "totalPaymentAmount": 'totalPaymentAmount',
194
+ "action": 'action'
195
+ },
196
+ "dataSourceConfig": 'testChargeLogic'
197
+ }
198
+
199
+ // 3. 单选模式配置(传统单选支付方式)
99
200
  export const testSingleChargeConfig = {
201
+ "title": "简单收费",
100
202
  "amountFields": [
101
203
  {
102
204
  "field": 'f_amount',
@@ -112,11 +214,13 @@ export const testSingleChargeConfig = {
112
214
  "paymentMethods": [
113
215
  {
114
216
  "key": '医保卡',
115
- "label": '医保卡'
217
+ "label": '医保卡',
218
+ "icon": 'icon2'
116
219
  },
117
220
  {
118
221
  "key": '现金',
119
- "label": '现金'
222
+ "label": '现金',
223
+ "icon": 'icon1'
120
224
  }
121
225
  ],
122
226
  "bottomFields": [
@@ -147,3 +251,189 @@ export const testSingleChargeConfig = {
147
251
  },
148
252
  "dataSourceConfig": 'testChargeLogic'
149
253
  }
254
+
255
+ // 4. 最小配置示例(仅必需字段)
256
+ export const testMinimalConfig = {
257
+ "title": "最小配置",
258
+ "enableMixedPayment": true,
259
+ "paymentMethods": [
260
+ {
261
+ key: '医保卡',
262
+ label: '医保卡',
263
+ icon: 'icon1'
264
+ },
265
+ {
266
+ key: '微信/支付宝',
267
+ label: '微信/支付宝',
268
+ icon: 'icon1'
269
+ }
270
+ ],
271
+ "amountFields": [
272
+ { field: 'f_amount', label: '费用总额' }
273
+ ],
274
+ "bottomFields": [
275
+ { field: 'received', label: '实收' },
276
+ { field: 'change', label: '找零' }
277
+ ],
278
+ "actionButtons": [
279
+ { key: 'charge', label: '收费' }
280
+ ]
281
+ }
282
+
283
+ // 5. 高级配置示例(包含所有可选字段)
284
+ export const testAdvancedConfig = {
285
+ "title": "高级收费管理",
286
+ "amountFields": [
287
+ {
288
+ "field": 'f_amount',
289
+ "disabled": false,
290
+ "label": '费用总额'
291
+ },
292
+ {
293
+ "field": 'f_insurance_amount',
294
+ "disabled": true,
295
+ "label": '医保支付'
296
+ },
297
+ {
298
+ "field": 'f_self_amount',
299
+ "disabled": true,
300
+ "label": '自费金额'
301
+ },
302
+ {
303
+ "field": 'out_of_pocket_amount',
304
+ "disabled": true,
305
+ "label": '自付金额'
306
+ },
307
+ {
308
+ "field": 'biscount_amount',
309
+ "disabled": true,
310
+ "label": '折扣金额'
311
+ },
312
+ {
313
+ "field": 'billing_amount',
314
+ "disabled": true,
315
+ "label": '记账金额'
316
+ }
317
+ ],
318
+ "paymentMethods": [
319
+ {
320
+ "key": '医保卡',
321
+ "label": '医保卡',
322
+ "icon": '/img/paymentMethod/medical-card.png'
323
+ },
324
+ {
325
+ "key": '微信',
326
+ "label": '微信支付',
327
+ "icon": '/img/paymentMethod/wechat.png'
328
+ },
329
+ {
330
+ "key": '支付宝',
331
+ "label": '支付宝',
332
+ "icon": '/img/paymentMethod/alipay.png'
333
+ },
334
+ {
335
+ "key": '银行卡',
336
+ "label": '银行卡',
337
+ "icon": '/img/paymentMethod/bank-card.png'
338
+ },
339
+ {
340
+ "key": '现金',
341
+ "label": '现金',
342
+ "icon": '/img/paymentMethod/cash.png'
343
+ },
344
+ {
345
+ "key": '其他',
346
+ "label": '其他方式',
347
+ "icon": '/img/paymentMethod/other.png'
348
+ }
349
+ ],
350
+ "bottomFields": [
351
+ {
352
+ "field": 'f_balance',
353
+ "label": '账户余额',
354
+ "disabled": false
355
+ },
356
+ {
357
+ "field": 'received',
358
+ "label": '实收',
359
+ "disabled": false
360
+ },
361
+ {
362
+ "field": 'change',
363
+ "label": '找零',
364
+ "disabled": false
365
+ },
366
+ {
367
+ "field": 'discount',
368
+ "label": '优惠金额',
369
+ "disabled": false
370
+ }
371
+ ],
372
+ "actionButtons": [
373
+ {
374
+ "key": 'charge',
375
+ "label": '收费',
376
+ "icon": 'check'
377
+ },
378
+ {
379
+ "key": 'refund',
380
+ "label": '退费',
381
+ "icon": 'undo'
382
+ },
383
+ {
384
+ "key": 'print',
385
+ "label": '打印',
386
+ "icon": 'printer'
387
+ },
388
+ {
389
+ "key": 'save',
390
+ "label": '保存',
391
+ "icon": 'save'
392
+ }
393
+ ],
394
+ "enableMixedPayment": true,
395
+ "mixedPaymentMode": 'manual',
396
+ // 自定义事件名
397
+ "eventNames": {
398
+ "method": 'onPaymentMethodChange',
399
+ "methods": 'onPaymentMethodsChange',
400
+ "totalPaymentAmount": 'onTotalAmountChange',
401
+ "action": 'onActionClick'
402
+ },
403
+ "dataSourceConfig": 'advancedChargeLogic'
404
+ }
405
+
406
+ // 6. 无图标配置示例(测试占位符功能)
407
+ export const testNoIconConfig = {
408
+ "title": "无图标测试",
409
+ "enableMixedPayment": true,
410
+ "paymentMethods": [
411
+ {
412
+ "key": '方式一',
413
+ "label": '方式一'
414
+ // 无icon配置,将显示首字母占位符
415
+ },
416
+ {
417
+ "key": '方式二',
418
+ "label": '方式二'
419
+ // 无icon配置,将显示首字母占位符
420
+ }
421
+ ],
422
+ "amountFields": [
423
+ { field: 'f_amount', label: '费用总额' }
424
+ ],
425
+ "bottomFields": [
426
+ { field: 'received', label: '实收' }
427
+ ],
428
+ "actionButtons": [
429
+ { key: 'charge', label: '收费' }
430
+ ]
431
+ }
432
+
433
+ // 配置说明:
434
+ // 1. title: 标题文本,支持XTitle组件的所有功能
435
+ // 2. paymentMethods.icon: 图标路径,支持相对路径和绝对路径
436
+ // 3. mixedPaymentMode: 'manual'手动输入模式,'auto'自动模式
437
+ // 4. enableMixedPayment: true启用混合支付,false为单选模式
438
+ // 5. eventNames: 可自定义事件名称
439
+ // 6. dataSourceConfig: 数据源逻辑配置名
@@ -27,6 +27,11 @@ const props = defineProps({
27
27
  queryParamsName: {
28
28
  type: String,
29
29
  default: ''
30
+ },
31
+ // 新增:直接设置标题文本,优先级高于queryParamsName
32
+ title: {
33
+ type: String,
34
+ default: ''
30
35
  }
31
36
  })
32
37
 
@@ -75,6 +80,17 @@ const handleClick = () => {
75
80
  }
76
81
 
77
82
  const parseConfig = (data) => {
83
+ // 如果设置了title属性,直接使用title作为标签
84
+ if (props.title) {
85
+ config.value.label = props.title
86
+ config.value.type = 'title'
87
+ config.value.line = ''
88
+ config.value.color = ''
89
+ config.value.lineLength = ''
90
+ config.value.clickName = ''
91
+ return
92
+ }
93
+
78
94
  if (!data) return
79
95
 
80
96
  const parts = data.includes('-') ? data.split('-') : data === 'line' ? ['', 'title', 'line'] : [data]