@truenewx/tnxvue3 3.0.4 → 3.0.5

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": "@truenewx/tnxvue3",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "description": "互联网技术解决方案:Vue3扩展支持",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <BButton class="tnxbsv-button" :loading="loading" :disabled="loading">
2
+ <BButton class="tnxbsv-button" :variant="type || 'outline-secondary'" :loading="loading" :disabled="loading">
3
3
  <i class="me-1" :class="icon" v-if="icon"></i>
4
4
  <div>
5
5
  <slot></slot>
@@ -21,6 +21,7 @@ export default {
21
21
  name: 'TnxbsvButton',
22
22
  components: {BButton, Loading},
23
23
  props: {
24
+ type: String,
24
25
  icon: String,
25
26
  loading: Boolean,
26
27
  },
@@ -16,11 +16,11 @@
16
16
  <template #title>
17
17
  <div v-html="title" v-if="title"></div>
18
18
  </template>
19
- <div v-html="content" v-if="typeof content === 'string'"></div>
20
- <component :is="content" v-bind="contentProps" v-else></component>
19
+ <div ref="content" v-html="content" v-if="typeof content === 'string'"></div>
20
+ <component ref="content" :is="content" v-bind="contentProps" v-else></component>
21
21
  <template #footer>
22
22
  <TnxbsvButton v-for="(button, index) in buttons" :key="index"
23
- :variant="button.type || 'outline-secondary'"
23
+ :type="button.type"
24
24
  :loading="buttonLoadings[index]"
25
25
  @click="btnClick(index)"
26
26
  >
@@ -24,9 +24,20 @@ export default {
24
24
  type: Object, // key: 字段名,value: 验证规则数组
25
25
  default: () => ({}),
26
26
  },
27
+ disabled: Boolean,
27
28
  },
28
29
  data() {
29
- return {};
30
+ return {
31
+ fieldEventListeners: {},
32
+ };
33
+ },
34
+ watch: {
35
+ rules() {
36
+ this.initRules();
37
+ },
38
+ disabled() {
39
+ this.updateElementsDisabled();
40
+ },
30
41
  },
31
42
  mounted() {
32
43
  window.addEventListener('resize', this.updateLabelWidth);
@@ -34,16 +45,18 @@ export default {
34
45
  setTimeout(() => {
35
46
  this.initRules();
36
47
  this.updateLabelWidth();
48
+ this.updateElementsDisabled();
37
49
  });
38
50
  });
39
51
  },
40
52
  beforeUnmount() {
41
53
  window.removeEventListener('resize', this.updateLabelWidth);
54
+ this.clearEventListeners();
42
55
  },
43
56
  methods: {
44
57
  getFieldGroupElements() {
45
58
  let elements = {};
46
- let groups = this.$el.querySelectorAll(`#${this.id} .b-form-group[prop]`);
59
+ let groups = this.$el.querySelectorAll('.b-form-group[prop]');
47
60
  groups.forEach(label => {
48
61
  let fieldName = label.getAttribute('prop');
49
62
  if (fieldName) {
@@ -59,7 +72,34 @@ export default {
59
72
  }
60
73
  return fieldRules;
61
74
  },
75
+ addFieldEventListener(fieldName, element, eventType, handler) {
76
+ if (!this.fieldEventListeners[fieldName]) {
77
+ this.fieldEventListeners[fieldName] = {};
78
+ }
79
+ if (!this.fieldEventListeners[fieldName][eventType]) {
80
+ this.fieldEventListeners[fieldName][eventType] = [];
81
+ }
82
+ element.addEventListener(eventType, handler);
83
+ this.fieldEventListeners[fieldName][eventType].push(handler);
84
+ },
85
+ clearEventListeners() {
86
+ for (let fieldName in this.fieldEventListeners) {
87
+ let listeners = this.fieldEventListeners[fieldName];
88
+ for (let eventType in listeners) {
89
+ let handlers = listeners[eventType];
90
+ let fieldElement = this.getFieldElement(fieldName);
91
+ if (fieldElement) {
92
+ handlers.forEach(handler => {
93
+ fieldElement.removeEventListener(eventType, handler);
94
+ });
95
+ }
96
+ }
97
+ }
98
+ this.fieldEventListeners = {}; // 清空映射表
99
+ },
62
100
  initRules() {
101
+ this.clearEventListeners();
102
+
63
103
  let fieldGroupElements = this.getFieldGroupElements();
64
104
  for (let fieldName in fieldGroupElements) {
65
105
  let fieldGroupElement = fieldGroupElements[fieldName];
@@ -77,12 +117,12 @@ export default {
77
117
  let fieldElement = fieldGroupElement.querySelector('input, select, textarea');
78
118
  if (fieldElement) {
79
119
  if (existsChangeValidator) {
80
- fieldElement.addEventListener('change', () => {
120
+ this.addFieldEventListener(fieldName, fieldElement, 'change', () => {
81
121
  this.validateField(fieldName, 'change');
82
122
  });
83
123
  }
84
124
  if (existsBlurValidator) {
85
- fieldElement.addEventListener('blur', () => {
125
+ this.addFieldEventListener(fieldName, fieldElement, 'blur', () => {
86
126
  this.validateField(fieldName, 'blur');
87
127
  });
88
128
  }
@@ -114,22 +154,24 @@ export default {
114
154
  this.clearValidate(); // 清除之前的反馈信息
115
155
  this.$el.classList.remove('was-validated'); // 移除已验证样式
116
156
 
117
- let valid = true;
118
- let fieldGroupElements = this.getFieldGroupElements();
119
- for (let fieldName in fieldGroupElements) {
120
- if (!this.validateField(fieldName)) {
121
- valid = false;
157
+ return new Promise((resolve, reject) => {
158
+ let errors = {};
159
+ let fieldGroupElements = this.getFieldGroupElements();
160
+ for (let fieldName in fieldGroupElements) {
161
+ let fieldErrorMessages = this.validateField(fieldName);
162
+ if (fieldErrorMessages.length) {
163
+ errors[fieldName] = fieldErrorMessages;
164
+ }
122
165
  }
123
- }
124
-
125
- if (!valid) {
126
- this.$el.classList.add('was-validated'); // 添加已验证样式
127
- }
128
-
129
- return valid;
166
+ if (Object.keys(errors).length) {
167
+ this.$el.classList.add('was-validated'); // 添加已验证样式
168
+ reject(errors);
169
+ }
170
+ resolve();
171
+ });
130
172
  },
131
173
  getFieldElement(fieldName) {
132
- let groupElement = this.$el.querySelector(`#${this.id} .b-form-group[prop="${fieldName}"]`);
174
+ let groupElement = this.$el.querySelector(`.b-form-group[prop="${fieldName}"]`);
133
175
  if (groupElement) {
134
176
  return groupElement.querySelector('input, textarea, select');
135
177
  }
@@ -138,27 +180,26 @@ export default {
138
180
  validateField(fieldName, trigger) {
139
181
  const fieldValue = this.model[fieldName];
140
182
  const fieldRules = this.getFieldRules(fieldName);
141
- const fieldErrors = [];
183
+ const fieldErrorMessages = [];
142
184
 
143
185
  fieldRules.forEach(fieldRule => {
144
186
  if (typeof fieldRule.validator === 'function' && (!trigger || trigger === fieldRule.trigger)) {
145
187
  fieldRule.validator(fieldRule, fieldValue, (error) => {
146
188
  if (error) {
147
- fieldErrors.push(error.message);
189
+ fieldErrorMessages.push(error.message);
148
190
  }
149
191
  });
150
192
  }
151
193
  // 没有validator()方法,则不进行校验
152
194
  });
153
195
 
154
- if (fieldErrors.length > 0) {
155
- this.setFieldInvalidFeedback(fieldName, fieldErrors);
156
- return false;
196
+ if (fieldErrorMessages.length) {
197
+ this.setFieldInvalidFeedback(fieldName, fieldErrorMessages);
157
198
  } else {
158
199
  // 清除错误状态
159
200
  this.removeFieldInvalidFeedback(fieldName);
160
- return true;
161
201
  }
202
+ return fieldErrorMessages;
162
203
  },
163
204
  setFieldInvalidFeedback(fieldName, messages) {
164
205
  let fieldElement = this.getFieldElement(fieldName);
@@ -199,6 +240,18 @@ export default {
199
240
  const invalidFeedbackElements = this.$el.querySelectorAll('.invalid-feedback');
200
241
  invalidFeedbackElements.forEach(el => el.remove());
201
242
  },
243
+ scrollToField(fieldName) {
244
+ let fieldElement = this.getFieldElement(fieldName);
245
+ if (fieldElement) {
246
+ fieldElement.scrollIntoView({behavior: 'smooth', block: 'center'});
247
+ }
248
+ },
249
+ updateElementsDisabled() {
250
+ let elements = this.$el.querySelectorAll('input, textarea, select, button');
251
+ elements.forEach(element => {
252
+ element.disabled = this.disabled;
253
+ });
254
+ },
202
255
  }
203
256
  }
204
257
  </script>
@@ -0,0 +1,176 @@
1
+ <template>
2
+ <TnxbsvForm ref="form" :model="model" :rules="validationRules" :disabled="disabled">
3
+ <slot></slot>
4
+ <TnxbsvFormGroup v-if="submit !== undefined && submit !== null">
5
+ <div class="flex-v-center">
6
+ <TnxbsvButton :type="theme || 'primary'" :size="size" @click="toSubmit" v-if="submit !== false">
7
+ {{ _submitText }}
8
+ </TnxbsvButton>
9
+ <TnxbsvButton class="ms-2" :size="size" @click="toCancel" v-if="cancel !== false">
10
+ {{ cancelText }}
11
+ </TnxbsvButton>
12
+ </div>
13
+ </TnxbsvFormGroup>
14
+ </TnxbsvForm>
15
+ </template>
16
+
17
+ <script>
18
+ import TnxbsvForm from '../form/Form.vue';
19
+ import TnxbsvFormGroup from '../form/FormGroup.vue';
20
+ import TnxbsvButton from '../button/Button.vue';
21
+
22
+ export default {
23
+ name: 'TnxbsvSubmitForm',
24
+ components: {TnxbsvForm, TnxbsvFormGroup, TnxbsvButton},
25
+ props: {
26
+ model: Object,
27
+ rules: [String, Object], // 加载字段校验规则的URL地址,或规则集对象
28
+ rulesApp: { // 加载字段校验规则的应用名称
29
+ type: String,
30
+ default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载,确保更改的值生效
31
+ },
32
+ rulesLoaded: Function, // 规则集加载后的附加处理函数,仅在rules为字符串类型的URL地址时有效
33
+ submit: {
34
+ type: [Function, Boolean],
35
+ default: null,
36
+ },
37
+ submitText: String,
38
+ theme: String,
39
+ cancel: {
40
+ type: [String, Function, Boolean],
41
+ default: true
42
+ },
43
+ cancelText: {
44
+ type: String,
45
+ default: '取消'
46
+ },
47
+ errorFocus: {
48
+ type: Boolean,
49
+ default: false,
50
+ },
51
+ size: String,
52
+ },
53
+ emits: ['rules-loaded', 'meta'],
54
+ data() {
55
+ return {
56
+ validationRules: {},
57
+ disabled: false,
58
+ fieldNames: [],
59
+ };
60
+ },
61
+ computed: {
62
+ _submitText() {
63
+ if (this.submitText) {
64
+ return this.submitText;
65
+ }
66
+ return this.cancel === false ? '保存' : '提交';
67
+ }
68
+ },
69
+ watch: {
70
+ rules() {
71
+ this.initRules();
72
+ },
73
+ },
74
+ mounted() {
75
+ this.initRules();
76
+ },
77
+ methods: {
78
+ initRules() {
79
+ if (this.rules) {
80
+ if (typeof this.rules === 'string') {
81
+ window.tnx.app.rpc.getMeta(this.rules, meta => {
82
+ if (this.rulesLoaded) {
83
+ this.rulesLoaded(meta.$rules);
84
+ } else {
85
+ this.$emit('rules-loaded', meta.$rules);
86
+ }
87
+ this.validationRules = meta.$rules;
88
+ delete meta.$rules;
89
+ this.$emit('meta', meta);
90
+ this.fieldNames = Object.keys(meta);
91
+ }, this.rulesApp);
92
+ } else {
93
+ this.validationRules = {};
94
+ Object.keys(this.rules).forEach(fieldName => {
95
+ let fieldRules = this.rules[fieldName] || [];
96
+ if (!Array.isArray(fieldRules)) {
97
+ fieldRules = [fieldRules];
98
+ }
99
+ if (fieldRules.length) {
100
+ this.validationRules[fieldName] = fieldRules.filter((rule, index) => {
101
+ let valid = rule !== undefined && rule !== null;
102
+ if (!valid) {
103
+ console.error(`invalid rules[${index}] for field "${fieldName}": `, rule);
104
+ }
105
+ return valid;
106
+ });
107
+ }
108
+ });
109
+ }
110
+ } else {
111
+ this.validationRules = {};
112
+ }
113
+ },
114
+ validate(callback, errorFocus) {
115
+ this.$refs.form.validate().then(() => {
116
+ if (typeof callback === 'function') {
117
+ callback(true);
118
+ }
119
+ }).catch(errors => {
120
+ if (this.errorFocus && errorFocus !== false) {
121
+ this.$nextTick(() => {
122
+ this.focusError.call(this, errors);
123
+ });
124
+ }
125
+ if (typeof callback === 'function') {
126
+ callback(false);
127
+ }
128
+ });
129
+ },
130
+ focusError(errors) {
131
+ let fieldNames = Object.keys(errors);
132
+ let fieldName = fieldNames[0];
133
+ if (fieldName) {
134
+ let fieldElement = this.$refs.form.getFieldElement(fieldName);
135
+ if (fieldElement) {
136
+ fieldElement.focus();
137
+ return;
138
+ }
139
+ // 没有找到错误字段输入框,则滚动到错误栏目处
140
+ this.$refs.form.scrollToField(fieldName);
141
+ }
142
+ },
143
+ disable(disabled) {
144
+ this.disabled = disabled !== false;
145
+ },
146
+ toSubmit(callback, disabled) {
147
+ this.validate(success => {
148
+ if (success) {
149
+ if (typeof callback !== 'function') {
150
+ callback = this.submit;
151
+ }
152
+ if (typeof callback === 'function') {
153
+ if (disabled !== false) {
154
+ this.disable();
155
+ }
156
+ callback(this);
157
+ }
158
+ }
159
+ });
160
+ },
161
+ toCancel() {
162
+ if (typeof this.cancel === 'function') {
163
+ this.cancel();
164
+ } else if (typeof this.cancel === 'string') {
165
+ this.$router.back(this.cancel);
166
+ } else if (this.cancel !== false) {
167
+ this.$router.back();
168
+ }
169
+ },
170
+ }
171
+ }
172
+ </script>
173
+
174
+ <style>
175
+
176
+ </style>
@@ -15,12 +15,13 @@ import Loading from './loading/Loading.vue';
15
15
  import Paged from './paged/Paged.vue';
16
16
  import QueryTable from './query-table/QueryTable.vue';
17
17
  import Select from './select/Select.vue';
18
+ import SubmitForm from './submit-form/SubmitForm.vue';
18
19
 
19
20
  export const build = tnxvue.build;
20
21
 
21
22
  export default build('tnxbsv', () => {
22
23
  const components = Object.assign({}, tnxvue.components, {
23
- Button, Dialog, EnumSelect, Form, FormGroup, Loading, Paged, QueryTable, Select,
24
+ Button, Dialog, EnumSelect, Form, FormGroup, Loading, Paged, QueryTable, Select, SubmitForm,
24
25
  });
25
26
 
26
27
  const tnxbsv = Object.assign({}, tnxjq, tnxvue, {
package/src/tnxvue.js CHANGED
@@ -2,8 +2,8 @@
2
2
  /**
3
3
  * 基于Vue 3的扩展支持
4
4
  */
5
- import tnxcore from '@truenewx/tnxcore';
6
- // import tnxcore from '../../core/src/tnxcore';
5
+ // import tnxcore from '@truenewx/tnxcore';
6
+ import tnxcore from '../../core/src/tnxcore';
7
7
  import validator from './tnxvue-validator';
8
8
  import createRouter from './tnxvue-router';
9
9
  import Text from './text/Text.vue';