bl-common-vue3 3.8.134 → 3.8.136

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": "bl-common-vue3",
3
- "version": "3.8.134",
3
+ "version": "3.8.136",
4
4
  "main": "index.js",
5
5
  "module": "index.js",
6
6
  "description": "bailing vue3 common components lib",
@@ -55,7 +55,10 @@
55
55
  <a-divider />
56
56
  <div class="section-title">{{ t('components.extraManageEdit.enterSetting') }}</div>
57
57
  <template v-if="isTextField">
58
- <a-form-item :label="t('components.ExtraManageEdit.249040-3')"><a-input v-model:value="formState.default_value" /></a-form-item>
58
+ <a-form-item :label="t('components.ExtraManageEdit.249040-3')">
59
+ <a-input-number v-if="formState.fields_type === 'number'" v-model:value="formState.default_value" class="full-width" />
60
+ <a-input v-else v-model:value="formState.default_value" />
61
+ </a-form-item>
59
62
  <a-form-item :label="t(formState.fields_type === 'number' ? 'PublicExtensionField.numberRange' : 'components.ExtraManageEdit.249040-4')">
60
63
  <div class="range-inputs"><a-input-number v-model:value="formState.min_length" @blur="blurNumber" /><span>{{ t('components.ExtraManageEdit.249040-5') }}</span><a-input-number v-model:value="formState.max_length" @blur="blurNumber" /></div>
61
64
  </a-form-item>
@@ -358,18 +361,14 @@ export default defineComponent({
358
361
  }
359
362
 
360
363
  const addclick = () => {
361
- let num = 0
362
-
363
- for (let j = 0; j < state.formState.option_value.length; j++) {
364
- if(state.formState.option_value[j].value > num){
365
- num = state.formState.option_value[j].value
366
- }
367
- }
368
-
369
- state.formState.option_value.push(
370
- {label : '' , value : num + 1 }
371
- )
372
- }
364
+ // 历史选项编号可能是字符串,必须按数值取最大值,避免拼接或字典序比较。
365
+ const maxValue = state.formState.option_value.reduce((max, item) => {
366
+ const value = Number(item.value);
367
+ return Number.isFinite(value) ? Math.max(max, value) : max;
368
+ }, 0);
369
+ // 沿用旧编辑器新增选项的字符串协议,不改写已有选项编号,避免影响已保存的业务值。
370
+ state.formState.option_value.push({label: '', value: String(maxValue + 1)});
371
+ };
373
372
 
374
373
  const gettypelist = (text) => {
375
374
  emit("request", {
@@ -396,8 +395,11 @@ export default defineComponent({
396
395
  }
397
396
 
398
397
  const fieldsTypechage = () => {
399
- if(state.formState.fields_type == 'radio' || state.formState.fields_type == 'multiple_choice' || state.formState.fields_type == 'select_single' || state.formState.fields_type == 'select_multiple'){
398
+ if(state.formState.fields_type == 'radio' || state.formState.fields_type == 'multiple_choice'){
400
399
  state.formState.option_value = [{label : '' , value : 1}]
400
+ }else if(state.formState.fields_type == 'select_single' || state.formState.fields_type == 'select_multiple'){
401
+ // 下拉字段沿用历史字符串编号,保证旧业务表单的选中值能够匹配。
402
+ state.formState.option_value = [{label : '' , value : '1'}]
401
403
  }else {
402
404
  state.formState.option_value = []
403
405
  }
@@ -407,6 +409,8 @@ export default defineComponent({
407
409
  }else {
408
410
  state.formState.option_max = ''
409
411
  }
412
+ // 新建时切换类型不能把文本默认值带入数字等其他字段类型。
413
+ state.formState.default_value = '';
410
414
  };
411
415
 
412
416
  const getVillageList = (isDetail = false) => {
@@ -43,7 +43,9 @@
43
43
  </template>
44
44
  <template #column-action="{ record }">
45
45
  <div v-if="record.is_system == 1">
46
- <a-tooltip>
46
+ <!-- 沿用旧组件的后端可编辑标记,系统字段仍不可删除。 -->
47
+ <a-button v-if="record.can_edit" @click="handleEdit(record)" type="link">{{t('common.button.edit')}}</a-button>
48
+ <a-tooltip v-else>
47
49
  <template #title>{{t('villageIotInfo.index.885385-0')}}</template>
48
50
  <a-button disabled type="link">{{t('common.button.edit')}}</a-button>
49
51
  </a-tooltip>
@@ -370,7 +372,20 @@ export default defineComponent({
370
372
  </script>
371
373
  <style lang="less" scoped>
372
374
  .field-manager-description { margin-bottom: 20px; padding: 12px 16px; background: #edf5ff; border: 1px solid #dceaff; border-radius: 5px; color: #5c7da3; line-height: 1.7; }
373
- .field-manager-table { padding: 10px 14px; border: 1px solid #e8edf3; border-radius: 6px; background: #fff; }
375
+ .field-manager-table {
376
+ // 限定到当前字段管理器的直属区域,避免影响弹窗内的其他表格。
377
+ :deep(> .table-warp > .filter-warp),
378
+ :deep(> .table-warp > .ant-table-wrapper) {
379
+ padding: 10px 14px;
380
+ border: 1px solid #e8edf3;
381
+ border-radius: 6px;
382
+ background: #fff;
383
+ }
384
+
385
+ :deep(> .table-warp > .filter-warp) {
386
+ margin-bottom: 16px;
387
+ }
388
+ }
374
389
  .field-manager-title { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
375
390
  .field-manager-title strong { font-size: 15px; font-weight: 500; }
376
391
  .field-manager-title strong span { margin-left: 6px; color: #8c97a6; font-size: 13px; }
@@ -378,5 +393,12 @@ export default defineComponent({
378
393
  .field-required { padding: 3px 8px; border-radius: 4px; color: #487ecc; background: #eef5ff; font-size: 12px; }
379
394
  .field-optional { color: #8c97a6; font-size: 12px; }
380
395
  .field-manager-hint { padding: 12px 8px; color: #8c97a6; font-size: 12px; }
381
- @media (max-width: 767px) { .field-manager-title { gap: 8px; } .field-manager-search { width: 145px; } .field-manager-table { padding: 8px; } }
396
+ @media (max-width: 767px) {
397
+ .field-manager-title { gap: 8px; }
398
+ .field-manager-search { width: 145px; }
399
+ .field-manager-table {
400
+ :deep(> .table-warp > .filter-warp),
401
+ :deep(> .table-warp > .ant-table-wrapper) { padding: 8px; }
402
+ }
403
+ }
382
404
  </style>
@@ -193,3 +193,163 @@ test('field list has no footer and mask closing remains disabled while editing w
193
193
  assert.equal(drawerAttrs.maskClosable, true);
194
194
  } finally {app.unmount(); global.window = previousWindow;}
195
195
  });
196
+
197
+ // 挂载真实编辑逻辑和表单校验,仅替换接口与 UI 渲染边界。
198
+ function mountEditor({detail, id = detail ? 9 : 0, deferredDetail = false, props = {}, template = false} = {}) {
199
+ const previousWindow = global.window;
200
+ global.window = {addEventListener() {}, removeEventListener() {}};
201
+ const antd = require('ant-design-vue');
202
+ const compiled = loadScript(path.resolve(__dirname, '../src/components/PublicExtensionField/components/ExtraManageEdit.vue'), {
203
+ 'ant-design-vue': {...antd, message: {success() {}, warning() {}}},
204
+ }, template);
205
+ const controls = [];
206
+ const components = Object.fromEntries(Object.keys(compiled.default.components).map(name => [name, {
207
+ inheritAttrs: false,
208
+ setup(_, {attrs, slots}) {
209
+ return () => {
210
+ controls.push({name, ...attrs});
211
+ return Vue.h('div', slots.default?.());
212
+ };
213
+ },
214
+ }]));
215
+ const requests = [];
216
+ const instance = Vue.ref();
217
+ const settings = Vue.reactive({visible: true, id, alias: 'roomExpandFieldAlias_9', server: 'village',
218
+ displayCompany: false, ...props});
219
+ const component = {...compiled.default, components, render: template ? compiled.render : () => Vue.h('div')};
220
+ const app = renderer.createApp({render: () => Vue.h(component, {
221
+ ...settings, ref: instance, onRequest: event => {
222
+ requests.push(event);
223
+ if (event.params.url === '/system/dict/data') event.success({list: [{value: 'single_text', label: 'Text'}]});
224
+ else if (event.params.url === '/company/authBranchList') event.success({list: [{id: 12, name: 'Company'}]});
225
+ else if (event.params.method === 'get' && !deferredDetail) event.success({detail: structuredClone(detail)});
226
+ },
227
+ })});
228
+ app.config.warnHandler = () => {};
229
+ app.mount({children: []});
230
+ return {instance, requests, settings, controls,
231
+ writes: () => requests.filter(event => ['post', 'put'].includes(event.params.method)),
232
+ close: () => {app.unmount(); global.window = previousWindow;},
233
+ };
234
+ }
235
+
236
+ test('historical option IDs survive reordering and new options use the numeric maximum without string concatenation', async () => {
237
+ const fixture = mountEditor({detail: {fields_name: 'Choices', fields_type: 'select_multiple', sort: 1,
238
+ option_value: [{label: 'Ten', value: '10'}, {label: 'Two', value: '2'}, {label: 'One', value: '1'}]}});
239
+ try {
240
+ await flush();
241
+ fixture.instance.value.addclick();
242
+ assert.deepEqual(fixture.instance.value.formState.option_value.map(item => item.value), ['10', '2', '1', '11']);
243
+ fixture.instance.value.belerasure(1);
244
+ fixture.instance.value.addclick();
245
+ assert.deepEqual(fixture.instance.value.formState.option_value.map(item => item.value), ['10', '1', '11', '12']);
246
+ } finally {fixture.close();}
247
+ });
248
+
249
+ test('new dropdown choices retain legacy string IDs and changing type clears the previous default', async () => {
250
+ const fixture = mountEditor();
251
+ try {
252
+ await flush();
253
+ for (const [type, firstValue] of [['select_single', '1'], ['select_multiple', '1'], ['radio', 1], ['multiple_choice', 1]]) {
254
+ fixture.instance.value.formState.default_value = 'old text';
255
+ fixture.instance.value.formState.fields_type = type;
256
+ fixture.instance.value.fieldsTypechage();
257
+ assert.equal(fixture.instance.value.formState.option_value[0].value, firstValue);
258
+ assert.equal(fixture.instance.value.formState.default_value, '');
259
+ }
260
+ } finally {fixture.close();}
261
+ });
262
+
263
+ for (const type of ['single_text', 'multiple_text', 'number', 'radio', 'multiple_choice', 'select_single', 'select_multiple', 'date', 'image', 'attachment']) {
264
+ test(`${type}: editing retains historical configuration, zero values and custom switches in the submitted data`, async () => {
265
+ const detail = {fields_name: 'Existing', fields_type: type, sort: 0, fill: 0, org_branch_id: 12,
266
+ default_value: type === 'number' ? 0 : '', min_length: 0, max_length: 20, placeholder: 'Hint',
267
+ date_type: 2, option_max: 2, option_value: [{label: 'Existing', value: '10'}], file_type: '.pdf,.doc', user_show: 0};
268
+ const fixture = mountEditor({detail, props: {displayCompany: true,
269
+ customConfigItems: [{key: 'user_show', type: 'switch', form: {defaultValue: 1}}]}});
270
+ try {
271
+ await flush();
272
+ fixture.instance.value.onSubmit();
273
+ await flush();
274
+ assert.equal(fixture.writes().length, 1);
275
+ assert.deepEqual(fixture.writes()[0].params.extra, {...detail, id: 9, alias: 'roomExpandFieldAlias_9'});
276
+ assert.equal(fixture.requests.filter(event => event.params.url === '/company/authBranchList').length, 1);
277
+ } finally {fixture.close();}
278
+ });
279
+ }
280
+
281
+ test('detail failure blocks saving; reopening a new editor resets historical values and custom defaults', async () => {
282
+ const fixture = mountEditor({id: 9, deferredDetail: true,
283
+ props: {customConfigItems: [{key: 'user_show', type: 'switch', form: {defaultValue: 1}}]}});
284
+ try {
285
+ await flush();
286
+ fixture.requests.find(event => event.params.url.endsWith('/field/9')).fail(new Error('offline'));
287
+ fixture.instance.value.onSubmit();
288
+ await flush();
289
+ assert.equal(fixture.writes().length, 0);
290
+ fixture.settings.visible = false;
291
+ await flush();
292
+ fixture.settings.id = 0;
293
+ fixture.settings.visible = true;
294
+ await flush();
295
+ assert.equal(fixture.instance.value.detailLoading, false);
296
+ assert.equal(fixture.instance.value.formState.user_show, 1);
297
+ assert.equal(fixture.instance.value.formState.file_type, '');
298
+ fixture.instance.value.formState.fields_name = 'New';
299
+ await flush();
300
+ fixture.instance.value.onSubmit();
301
+ fixture.instance.value.onSubmit();
302
+ await flush();
303
+ assert.equal(fixture.writes().length, 1);
304
+ fixture.writes()[0].fail(new Error('offline'));
305
+ fixture.instance.value.onSubmit();
306
+ await flush();
307
+ assert.equal(fixture.writes().length, 2);
308
+ assert.equal(fixture.writes()[1].params.method, 'post');
309
+ } finally {fixture.close();}
310
+ });
311
+
312
+ test('numeric defaults use a numeric input and attachment restrictions stay hidden in the editor', async () => {
313
+ for (const type of ['number', 'attachment']) {
314
+ const fixture = mountEditor({template: true, detail: {fields_name: 'Existing', fields_type: type,
315
+ sort: 1, default_value: 123, file_type: '.pdf', option_value: []}});
316
+ try {
317
+ await flush();
318
+ assert.equal(fixture.controls.some(control => control.name === 'a-input' && control.value === '.pdf'), false);
319
+ if (type === 'number') {
320
+ assert.equal(fixture.controls.some(control => control.name === 'a-input-number' && control.value === 123), true);
321
+ assert.equal(fixture.controls.some(control => control.name === 'a-input' && control.value === 123), false);
322
+ }
323
+ } finally {fixture.close();}
324
+ }
325
+ });
326
+
327
+ test('system field actions preserve can_edit while all system field deletion stays disabled', async () => {
328
+ const previousWindow = global.window;
329
+ global.window = {addEventListener() {}, removeEventListener() {}};
330
+ const compiled = loadScript(path.resolve(__dirname, '../src/components/PublicExtensionField/index.vue'), {}, true);
331
+ const record = Vue.reactive({id: 9, is_system: 1, can_edit: true});
332
+ const actions = [];
333
+ const components = Object.fromEntries(Object.keys(compiled.default.components).map(name => [name, {
334
+ inheritAttrs: false, setup(_, {attrs, slots}) {return () => {
335
+ if (name === 'a-button') actions.push({...attrs});
336
+ return Vue.h('div', name === 'CustomTable' ? slots['column-action']({record}) : slots.default?.());
337
+ };},
338
+ }]));
339
+ const app = renderer.createApp({render: () => Vue.h({...compiled.default, components, render: compiled.render}, {visible: true})});
340
+ app.config.warnHandler = () => {};
341
+ try {
342
+ app.mount({children: []});
343
+ await flush();
344
+ assert.equal(actions.length, 2);
345
+ assert.equal(typeof actions[0].onClick, 'function');
346
+ assert.notEqual(actions[0].disabled, '');
347
+ assert.equal(actions[1].disabled, '');
348
+ record.can_edit = false;
349
+ actions.length = 0;
350
+ await flush();
351
+ assert.equal(actions.length, 2);
352
+ assert.equal(actions[0].disabled, '');
353
+ assert.equal(actions[1].disabled, '');
354
+ } finally {app.unmount(); global.window = previousWindow;}
355
+ });