@truenewx/tnxvue3 2.6.0
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/README.md +3 -0
- package/package.json +76 -0
- package/sample/App.vue +19 -0
- package/sample/main.js +11 -0
- package/sample/pages/index.vue +79 -0
- package/sample/pages/info.vue +28 -0
- package/sample/tnx.js +31 -0
- package/src/aj-captcha/Verify/VerifyPoints.vue +258 -0
- package/src/aj-captcha/Verify/VerifySlide.vue +379 -0
- package/src/aj-captcha/Verify.vue +375 -0
- package/src/aj-captcha/api/index.js +19 -0
- package/src/aj-captcha/utils/ase.js +11 -0
- package/src/aj-captcha/utils/util.js +35 -0
- package/src/ant-design/tnxad-theme.css +5 -0
- package/src/ant-design/tnxad.css +8 -0
- package/src/ant-design/tnxad.js +23 -0
- package/src/element-plus/alert/Alert.vue +112 -0
- package/src/element-plus/avatar/Avatar.vue +124 -0
- package/src/element-plus/button/Button.vue +184 -0
- package/src/element-plus/check-icon/CheckIcon.vue +61 -0
- package/src/element-plus/close-error-button/CloseErrorButton.vue +45 -0
- package/src/element-plus/curd/Curd.vue +224 -0
- package/src/element-plus/date-picker/DatePicker.vue +206 -0
- package/src/element-plus/date-range/DateRange.vue +78 -0
- package/src/element-plus/datetime-picker/DateTimePicker.vue +129 -0
- package/src/element-plus/detail-form/DetailForm.vue +88 -0
- package/src/element-plus/dialog/Dialog.vue +259 -0
- package/src/element-plus/dialog/DialogContent.vue +13 -0
- package/src/element-plus/drawer/Drawer.vue +175 -0
- package/src/element-plus/dropdown-item/DropdownItem.vue +30 -0
- package/src/element-plus/enum-select/EnumSelect.vue +125 -0
- package/src/element-plus/fetch-cascader/FetchCascader.vue +138 -0
- package/src/element-plus/fetch-select/FetchSelect.vue +166 -0
- package/src/element-plus/fetch-tags/FetchTags.vue +122 -0
- package/src/element-plus/fss-upload/FssUpload.vue +306 -0
- package/src/element-plus/fss-view/FssView.vue +163 -0
- package/src/element-plus/icon/Icon.vue +221 -0
- package/src/element-plus/input-number/InputNumber.vue +150 -0
- package/src/element-plus/paged/Paged.vue +76 -0
- package/src/element-plus/permission-tree/PermissionTree.vue +184 -0
- package/src/element-plus/query-form/QueryForm.vue +138 -0
- package/src/element-plus/query-table/QueryTable.vue +402 -0
- package/src/element-plus/region-cascader/RegionCascader.vue +108 -0
- package/src/element-plus/select/Select.vue +446 -0
- package/src/element-plus/slider/Slider.vue +88 -0
- package/src/element-plus/steps-nav/StepsNav.vue +57 -0
- package/src/element-plus/submit-form/SubmitForm.vue +236 -0
- package/src/element-plus/table-column/TableColumn.vue +32 -0
- package/src/element-plus/tabs/Tabs.vue +93 -0
- package/src/element-plus/tnxel.css +890 -0
- package/src/element-plus/tnxel.js +528 -0
- package/src/element-plus/transfer/Transfer.vue +117 -0
- package/src/element-plus/upload/Upload.vue +856 -0
- package/src/percent/Percent.vue +12 -0
- package/src/text/Text.vue +33 -0
- package/src/tnxvue-cli.js +64 -0
- package/src/tnxvue-router.js +161 -0
- package/src/tnxvue-validator.js +365 -0
- package/src/tnxvue.css +12 -0
- package/src/tnxvue.js +343 -0
package/src/tnxvue.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// tnxvue.js
|
|
2
|
+
/**
|
|
3
|
+
* 基于Vue 3的扩展支持
|
|
4
|
+
*/
|
|
5
|
+
import tnxcore from '@truenewx/tnxcore';
|
|
6
|
+
import validator from './tnxvue-validator';
|
|
7
|
+
import createRouter from './tnxvue-router';
|
|
8
|
+
import Text from './text/Text.vue';
|
|
9
|
+
import Percent from './percent/Percent.vue';
|
|
10
|
+
import CaptchaVerify from './aj-captcha/Verify.vue';
|
|
11
|
+
import * as Vue from 'vue';
|
|
12
|
+
import mitt from 'mitt';
|
|
13
|
+
import './tnxvue.css';
|
|
14
|
+
|
|
15
|
+
const components = {
|
|
16
|
+
Div: {
|
|
17
|
+
name: 'TnxvueDiv',
|
|
18
|
+
template: '<div><slot></slot></div>'
|
|
19
|
+
},
|
|
20
|
+
Span: {
|
|
21
|
+
name: 'TnxvueSpan',
|
|
22
|
+
template: '<span><slot></slot></span>'
|
|
23
|
+
},
|
|
24
|
+
Text,
|
|
25
|
+
Percent,
|
|
26
|
+
CaptchaVerify,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const tnxvue = Object.assign({}, tnxcore, {
|
|
30
|
+
libs: Object.assign({}, tnxcore.libs, {
|
|
31
|
+
Vue: Vue
|
|
32
|
+
}),
|
|
33
|
+
components,
|
|
34
|
+
router: {
|
|
35
|
+
instance: null,
|
|
36
|
+
create(VueRouter, menu, fnImportPage) {
|
|
37
|
+
return createRouter(VueRouter, menu, fnImportPage);
|
|
38
|
+
},
|
|
39
|
+
beforeLeave(router, from) {
|
|
40
|
+
window.tnx.app.page.stopCache(router, from.path);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
createVueInstance(rootComponent, router, rootProps) {
|
|
44
|
+
let vm = Vue.createApp(rootComponent, rootProps);
|
|
45
|
+
vm.use(this);
|
|
46
|
+
if (router) {
|
|
47
|
+
vm.use(router);
|
|
48
|
+
router.app = window.tnx.app;
|
|
49
|
+
window.tnx.router.instance = vm.config.globalProperties.$router;
|
|
50
|
+
} else if (window.tnx.router.instance) {
|
|
51
|
+
vm.config.globalProperties.$router = window.tnx.router.instance;
|
|
52
|
+
}
|
|
53
|
+
vm.config.unwrapInjectedRef = true;
|
|
54
|
+
window.tnx.app.eventBus = mitt();
|
|
55
|
+
window.tnx.app.eventBus.once = function (name, handler) {
|
|
56
|
+
this.all.set(name, [handler]);
|
|
57
|
+
}
|
|
58
|
+
return vm;
|
|
59
|
+
},
|
|
60
|
+
install(vm) {
|
|
61
|
+
for (let key of Object.keys(this.components)) {
|
|
62
|
+
const component = this.components[key];
|
|
63
|
+
vm.component(component.name, component);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
dialog(content, title, buttons, options, contentProps) {
|
|
67
|
+
// 默认不实现,由UI框架扩展层实现
|
|
68
|
+
throw new Error('Unsupported function');
|
|
69
|
+
},
|
|
70
|
+
drawer(content, title, buttons, options, contentProps) {
|
|
71
|
+
// 默认不实现,由UI框架扩展层实现
|
|
72
|
+
throw new Error('Unsupported function');
|
|
73
|
+
},
|
|
74
|
+
open(component, props, options) {
|
|
75
|
+
options = options || {};
|
|
76
|
+
|
|
77
|
+
let mode = options.mode;
|
|
78
|
+
if (component.methods?.dialog) {
|
|
79
|
+
options = Object.assign({}, component.methods.dialog(props), options);
|
|
80
|
+
} else if (component.methods?.drawer) {
|
|
81
|
+
options = Object.assign({}, component.methods.drawer(props), options);
|
|
82
|
+
mode = 'drawer';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const title = component.title || options.title;
|
|
86
|
+
const buttons = options.buttons || this.getDefaultDialogButtons(options.type, options.click, options.theme);
|
|
87
|
+
if (options.buttonText) {
|
|
88
|
+
if (!Array.isArray(options.buttonText)) {
|
|
89
|
+
options.buttonText = [options.buttonText];
|
|
90
|
+
}
|
|
91
|
+
for (let i = 0; i < buttons.length; i++) {
|
|
92
|
+
let buttonText = options.buttonText[i];
|
|
93
|
+
if (buttonText) {
|
|
94
|
+
buttons[i].text = buttonText;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
delete options.mode;
|
|
99
|
+
delete options.title;
|
|
100
|
+
delete options.type;
|
|
101
|
+
delete options.click;
|
|
102
|
+
if (mode === 'drawer') {
|
|
103
|
+
return this.drawer(component, title, buttons, options, props);
|
|
104
|
+
}
|
|
105
|
+
return this.dialog(component, title, buttons, options, props);
|
|
106
|
+
},
|
|
107
|
+
getDefaultDialogButtons(type, callback, theme) {
|
|
108
|
+
if (callback !== false) {
|
|
109
|
+
if (type === 'none') {
|
|
110
|
+
return [];
|
|
111
|
+
} else if (type === 'confirm') {
|
|
112
|
+
return [{
|
|
113
|
+
text: '取消',
|
|
114
|
+
click(close) {
|
|
115
|
+
if (typeof callback === 'function') {
|
|
116
|
+
return callback.call(this, false, close);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, {
|
|
120
|
+
text: '确定',
|
|
121
|
+
type: theme || 'primary',
|
|
122
|
+
click(close) {
|
|
123
|
+
if (typeof callback === 'function') {
|
|
124
|
+
return callback.call(this, true, close);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}];
|
|
128
|
+
} else {
|
|
129
|
+
return [{
|
|
130
|
+
text: '确定',
|
|
131
|
+
type: theme || 'primary',
|
|
132
|
+
click(close) {
|
|
133
|
+
if (typeof callback === 'function') {
|
|
134
|
+
return callback.call(this, close);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return [];
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
Object.assign(tnxvue.util, {
|
|
145
|
+
/**
|
|
146
|
+
* 判断指定对象是否组件实例
|
|
147
|
+
* @param obj 对象
|
|
148
|
+
* @returns {boolean} 是否组件实例
|
|
149
|
+
*/
|
|
150
|
+
isComponent: function (obj) {
|
|
151
|
+
return (typeof obj === 'object') && (typeof obj.render === 'function');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
tnxvue.app.isProduction = function () {
|
|
156
|
+
if (process && process.env && process.env.NODE_ENV !== 'production') {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
tnxvue.app.toDevUrl = function (url, portIndex, replacement) {
|
|
163
|
+
if (!this.isProduction()) {
|
|
164
|
+
let index = url.indexOf(':', url.indexOf('//'));
|
|
165
|
+
if (index > 0) { // 必须带有端口号才可替换
|
|
166
|
+
let prefix = url.substring(0, index + 1); // 端口号之前的部分
|
|
167
|
+
portIndex = portIndex || 1; // 开发环境端口与正式环境端口不同点的位置,如:8080之于8880,则portIndex为1
|
|
168
|
+
replacement = replacement || '0'; // 开发环境端口在不同于正式环境端口位置要替代的值,如8080之于8880,则replacement为'0'
|
|
169
|
+
|
|
170
|
+
let path = ''; // 路由路径
|
|
171
|
+
let wellIndex = url.indexOf('#');
|
|
172
|
+
if (wellIndex > 0) { // 如果有路由路径,则将url拆成两部分,以便于后续处理
|
|
173
|
+
path = url.substring(wellIndex);
|
|
174
|
+
url = url.substring(0, wellIndex);
|
|
175
|
+
}
|
|
176
|
+
// 开发环境路径不包含contextPath,去掉url中的contenxtPath
|
|
177
|
+
let replaceEndIndex = prefix.length + portIndex + 1;
|
|
178
|
+
let slashIndex = url.indexOf('/', prefix.length);
|
|
179
|
+
url = url.substring(0, prefix.length + portIndex) + replacement
|
|
180
|
+
+ url.substring(replaceEndIndex, slashIndex > replaceEndIndex ? slashIndex : undefined) + path;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return url;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 元数据到async-validator组件规则的转换处理
|
|
187
|
+
tnxvue.app.validator = validator;
|
|
188
|
+
tnxvue.app.rpc.getMeta = tnxvue.util.function.around(tnxvue.app.rpc.getMeta, function (getMeta, url, callback, app) {
|
|
189
|
+
getMeta.call(tnxvue.app.rpc, url, function (meta) {
|
|
190
|
+
if (meta) { // meta已被缓存,所以直接修改其内容,以便同步缓存
|
|
191
|
+
meta.$rules = validator.getRules(meta);
|
|
192
|
+
if (typeof callback === 'function') {
|
|
193
|
+
callback.call(this, meta);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}, app);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
tnxvue.app.page.init = tnxvue.util.function.around(tnxvue.app.page.init, function (init, page, container) {
|
|
200
|
+
if (container.tagName === 'BODY') { // vue不推荐以body为挂载目标,故从body下获取第一个div作为容器
|
|
201
|
+
for (let i = 0; i < container.children.length; i++) {
|
|
202
|
+
const child = container.children[i];
|
|
203
|
+
if (child.tagName === 'DIV') {
|
|
204
|
+
container = child;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
init.call(this, page, container);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
Object.assign(tnxvue.app.page, {
|
|
213
|
+
startCache: function (router, model, intervalMillis, ignoredFields) {
|
|
214
|
+
if (localStorage && intervalMillis && intervalMillis > 1000) { // 缓存间隔必须超过1秒
|
|
215
|
+
let path = this._readCache(router, undefined, function (cache) {
|
|
216
|
+
Object.assign(model, cache.model);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (path) {
|
|
220
|
+
let _this = this;
|
|
221
|
+
let intervalId = setInterval(function () {
|
|
222
|
+
_this._storeCache(router, path, intervalId, model, ignoredFields);
|
|
223
|
+
}, intervalMillis);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return model;
|
|
227
|
+
},
|
|
228
|
+
_readCache: function (router, path, callback) {
|
|
229
|
+
if (localStorage) {
|
|
230
|
+
path = path || router.app.$route.path || '/';
|
|
231
|
+
let cache = localStorage[path];
|
|
232
|
+
if (cache) {
|
|
233
|
+
cache = window.tnx.util.string.parseJson(cache);
|
|
234
|
+
if (typeof callback === 'function') {
|
|
235
|
+
callback.call(this, cache);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return path;
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
_storeCache: function (router, path, intervalId, model, ignoredFields) {
|
|
242
|
+
if (path && intervalId) {
|
|
243
|
+
let data = {};
|
|
244
|
+
if (Array.isArray(ignoredFields) && ignoredFields.length) {
|
|
245
|
+
Object.keys(model).forEach(key => {
|
|
246
|
+
if (!ignoredFields.contains(key)) {
|
|
247
|
+
data[key] = model[key];
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
} else {
|
|
251
|
+
data = model;
|
|
252
|
+
}
|
|
253
|
+
localStorage[path] = tnxvue.util.string.toJson({
|
|
254
|
+
intervalId: intervalId,
|
|
255
|
+
model: data,
|
|
256
|
+
ignored: ignoredFields,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
saveCache: function (router, model) {
|
|
261
|
+
let intervalId;
|
|
262
|
+
let ignoredFields;
|
|
263
|
+
let path = this._readCache(router, undefined, function (cache) {
|
|
264
|
+
intervalId = cache.intervalId;
|
|
265
|
+
ignoredFields = cache.ignored;
|
|
266
|
+
});
|
|
267
|
+
this._storeCache(router, path, intervalId, model, ignoredFields);
|
|
268
|
+
},
|
|
269
|
+
stopCache: function (router, path) {
|
|
270
|
+
return this._readCache(router, path, function (cache) {
|
|
271
|
+
clearInterval(cache.intervalId);
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
clearCache: function (router) {
|
|
275
|
+
let path = this.stopCache(router);
|
|
276
|
+
if (path) {
|
|
277
|
+
delete localStorage[path];
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
/**
|
|
281
|
+
* 前端页面模型转换为后端命令模型,检查文件上传是否完成,去掉后端不需要的多余字段,转换多层嵌入字段数据使其符合服务端命令模型的基本要求
|
|
282
|
+
* @param model 前端页面模型
|
|
283
|
+
* @param refs 页面中的组件引用集
|
|
284
|
+
* @param validFieldNames 有效的字段名称集,如有指定则清除模型中的无效字段
|
|
285
|
+
*/
|
|
286
|
+
toCommandModel: function (vm, model, validFieldNames) {
|
|
287
|
+
let result = {};
|
|
288
|
+
if (model) {
|
|
289
|
+
if (vm.$refs) {
|
|
290
|
+
let refKeys = Object.keys(vm.$refs);
|
|
291
|
+
for (let refKey of refKeys) {
|
|
292
|
+
let ref = vm.$refs[refKey];
|
|
293
|
+
if (typeof ref.getLocationUrl === 'function') {
|
|
294
|
+
if (ref.validateUploaded() === false) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
let fieldNames = Object.keys(model);
|
|
301
|
+
for (let fieldName of fieldNames) {
|
|
302
|
+
if (!validFieldNames || !validFieldNames.length || validFieldNames.contains(fieldName)) {
|
|
303
|
+
if (fieldName.contains('__')) {
|
|
304
|
+
let path = fieldName.replace('__', '.');
|
|
305
|
+
tnxvue.util.object.setValue(result, path, model[fieldName]);
|
|
306
|
+
} else {
|
|
307
|
+
result[fieldName] = model[fieldName];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
},
|
|
314
|
+
/**
|
|
315
|
+
* 转换多层嵌入字段数据使其符合前端页面模型的基本要求
|
|
316
|
+
* @param model 服务端视图模型
|
|
317
|
+
*/
|
|
318
|
+
toPageModel: function (model) {
|
|
319
|
+
let expanded = this._expandRefFields(model);
|
|
320
|
+
while (expanded) {
|
|
321
|
+
expanded = this._expandRefFields(model);
|
|
322
|
+
}
|
|
323
|
+
return model;
|
|
324
|
+
},
|
|
325
|
+
_expandRefFields: function (model) {
|
|
326
|
+
let expanded = false;
|
|
327
|
+
Object.keys(model).forEach(key => {
|
|
328
|
+
let value = model[key];
|
|
329
|
+
if (value && typeof value === 'object') {
|
|
330
|
+
Object.keys(value).forEach(refKey => {
|
|
331
|
+
model[key + '__' + refKey] = value[refKey];
|
|
332
|
+
});
|
|
333
|
+
delete model[key];
|
|
334
|
+
expanded = true;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
return expanded;
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
window.tnx = tnxvue;
|
|
342
|
+
|
|
343
|
+
export default tnxvue;
|