UNPKG

@truenewx/tnxvue3

Version:

互联网技术解决方案:Vue3扩展支持

440 lines (422 loc) 17.8 kB
// tnxvue.js /** * 基于Vue 3的扩展支持 */ import tnxcore from '../../tnxcore/src/tnxcore'; import validator from './tnxvue-validator'; import createRouter from './tnxvue-router'; import Text from './text/Text.vue'; import Percent from './percent/Percent.vue'; import CaptchaVerify from './aj-captcha/Verify.vue'; import * as Vue from 'vue'; import mitt from 'mitt'; import './tnxvue.css'; export const build = tnxcore.build; export default build('tnxvue', () => { const tnxvue = Object.assign({}, tnxcore, { components: { Div: { name: 'TnxvueDiv', template: '<div><slot></slot></div>' }, Span: { name: 'TnxvueSpan', template: '<span><slot></slot></span>' }, Text, Percent, CaptchaVerify, }, router: { instance: null, create(VueRouter, menu, fnImportPage) { return createRouter(VueRouter, menu, fnImportPage); }, beforeLeave(router, from) { window.tnx.app.page.stopCache(router, from.path); } }, /** * 深度监听指定对象,在created()中调用才能生效 * @param vm 页面vue实例 * @param target 要监听的对象 * @param handler 处理函数 */ deepWatch(vm, target, handler) { vm.$watch(() => { try { return JSON.stringify(target); } catch (e) { console.error(e); } return undefined; }, (newValue, oldValue) => { try { if (newValue !== oldValue) { const newObject = JSON.parse(newValue); const oldObject = JSON.parse(oldValue); this.deepCompare(newObject, oldObject, '', handler); } } catch (e) { console.error(e); } }, {deep: true}); }, deepCompare(object1, object2, path = '', handler) { if (object1) { const keys = Object.keys(object1); keys.forEach(key => { const fullPath = path ? `${path}.${key}` : key; if (object2) { if (Array.isArray(object1[key]) && Array.isArray(object2[key])) { object1[key].forEach((item, index) => { this.deepCompare(item, object2[key][index], `${fullPath}[${index}]`, handler); }); } else if (typeof object1[key] === 'object' && typeof object2[key] === 'object') { this.deepCompare(object1[key], object2[key], fullPath, handler); } else if (object1[key] !== object2[key]) { handler(object1[key], object2[key], fullPath); } } else { handler(object1[key], undefined, fullPath); } }); } }, createVueInstance(rootComponent, router, rootProps) { let vm = Vue.createApp(rootComponent, rootProps); vm.use(this); if (router) { vm.use(router); router.app = window.tnx.app; window.tnx.router.instance = vm.config.globalProperties.$router; } else if (window.tnx.router.instance) { vm.config.globalProperties.$router = window.tnx.router.instance; } if (!window.tnx.app.eventBus) { window.tnx.app.eventBus = window.tnx.app.eventBus || mitt(); window.tnx.app.eventBus.once = function (name, handler) { this.all.set(name, [handler]); } } return vm; }, install(vm) { for (let key of Object.keys(this.components)) { const component = this.components[key]; vm.component(component.name, component); } }, nextTickTimeout(vm, handler, timeout) { vm.$nextTick(() => { setTimeout(handler, timeout); }); }, dialog(content, title, buttons, options, contentProps) { // 默认不实现,由UI框架扩展层实现 throw new Error('Unsupported function'); }, drawer(content, title, buttons, options, contentProps) { // 默认不实现,由UI框架扩展层实现 throw new Error('Unsupported function'); }, open(component, props, options) { options = options || {}; let mode = options.mode; if (component.methods) { if (component.methods.dialog) { options = Object.assign({}, component.methods.dialog(props), options); } else if (component.methods.drawer) { options = Object.assign({}, component.methods.drawer(props), options); mode = 'drawer'; } } const title = component.title || options.title; const buttons = options.buttons || this.getDefaultDialogButtons(options.type, options.click, options.theme); if (options.buttonText) { if (!Array.isArray(options.buttonText)) { options.buttonText = [options.buttonText]; } for (let i = 0; i < buttons.length; i++) { let buttonText = options.buttonText[i]; if (buttonText) { buttons[i].text = buttonText; } } } delete options.mode; delete options.title; delete options.type; delete options.click; if (mode === 'drawer') { return this.drawer(component, title, buttons, options, props); } return this.dialog(component, title, buttons, options, props); }, getDefaultDialogButtons(type, callback, theme) { if (callback !== false) { if (type === 'none') { return []; } else if (type === 'confirm') { return [{ text: '确定', type: theme || 'primary', click(close) { if (typeof callback === 'function') { return callback.call(this, true, close); } } }, { text: '取消', click(close) { if (typeof callback === 'function') { return callback.call(this, false, close); } } }]; } else if (type === 'close') { return [{ text: '关闭', type: theme, click(close) { if (typeof callback === 'function') { return callback.call(this, close); } } }]; } else { return [{ text: '确定', type: theme || 'primary', click(close) { if (typeof callback === 'function') { return callback.call(this, close); } } }]; } } return []; }, }); tnxvue.libs.Vue = Vue; Object.assign(tnxvue.util, { /** * 判断指定对象是否组件实例 * @param obj 对象 * @returns {boolean} 是否组件实例 */ isComponent: function (obj) { return (typeof obj === 'object') && (typeof obj.render === 'function'); } }); tnxvue.app.isProduction = function () { try { return !(process && process.env && process.env.NODE_ENV !== 'production'); } catch (e) { // process未定义时会出错,此时为生产模式 return true; } }; tnxvue.app.toDevUrl = function (url, portIndex, replacement) { if (!this.isProduction()) { let index = url.indexOf(':', url.indexOf('//')); if (index > 0) { // 必须带有端口号才可替换 let prefix = url.substring(0, index + 1); // 端口号之前的部分 portIndex = portIndex || 1; // 开发环境端口与正式环境端口不同点的位置,如:8080之于8880,则portIndex为1 replacement = replacement || '0'; // 开发环境端口在不同于正式环境端口位置要替代的值,如8080之于8880,则replacement为'0' let path = ''; // 路由路径 let wellIndex = url.indexOf('#'); if (wellIndex > 0) { // 如果有路由路径,则将url拆成两部分,以便于后续处理 path = url.substring(wellIndex); url = url.substring(0, wellIndex); } // 开发环境路径不包含contextPath,去掉url中的contenxtPath let replaceEndIndex = prefix.length + portIndex + 1; let slashIndex = url.indexOf('/', prefix.length); url = url.substring(0, prefix.length + portIndex) + replacement + url.substring(replaceEndIndex, slashIndex > replaceEndIndex ? slashIndex : undefined) + path; } } return url; } // 元数据到async-validator组件规则的转换处理 tnxvue.app.validator = validator; tnxvue.app.rpc.getMeta = tnxvue.util.function.around(tnxvue.app.rpc.getMeta, function (getMeta, url, callback, app) { getMeta.call(tnxvue.app.rpc, url, function (meta) { if (meta) { // meta已被缓存,所以直接修改其内容,以便同步缓存 meta.$rules = validator.getRules(meta); if (typeof callback === 'function') { callback.call(this, meta); } } }, app); }); tnxvue.app.page.init = tnxvue.util.function.around(tnxvue.app.page.init, function (init, page, container) { if (container.tagName === 'BODY') { // vue不推荐以body为挂载目标,故从body下获取第一个div作为容器 for (let i = 0; i < container.children.length; i++) { const child = container.children[i]; if (child.tagName === 'DIV') { container = child; break; } } } init.call(this, page, container); }); Object.assign(tnxvue.app.page, { // TODO 路由缓存迁移至tnxvue-router.js中 startCache: function (router, model, intervalMillis, ignoredFields) { if (localStorage && intervalMillis && intervalMillis > 1000) { // 缓存间隔必须超过1秒 let path = this._readCache(router, undefined, function (cache) { Object.assign(model, cache.model); }); if (path) { let _this = this; let intervalId = setInterval(function () { _this._storeCache(router, path, intervalId, model, ignoredFields); }, intervalMillis); } } return model; }, _readCache: function (router, path, callback) { if (localStorage) { path = path || router.app.$route.path || '/'; let cache = localStorage[path]; if (cache) { cache = window.tnx.util.string.parseJson(cache); if (typeof callback === 'function') { callback.call(this, cache); } } return path; } }, _storeCache: function (router, path, intervalId, model, ignoredFields) { if (path && intervalId) { let data = {}; if (Array.isArray(ignoredFields) && ignoredFields.length) { Object.keys(model).forEach(key => { if (!ignoredFields.contains(key)) { data[key] = model[key]; } }); } else { data = model; } localStorage[path] = tnxvue.util.string.toJson({ intervalId: intervalId, model: data, ignored: ignoredFields, }); } }, saveCache: function (router, model) { let intervalId; let ignoredFields; let path = this._readCache(router, undefined, function (cache) { intervalId = cache.intervalId; ignoredFields = cache.ignored; }); this._storeCache(router, path, intervalId, model, ignoredFields); }, stopCache: function (router, path) { return this._readCache(router, path, function (cache) { clearInterval(cache.intervalId); }); }, clearCache: function (router) { let path = this.stopCache(router); if (path) { delete localStorage[path]; } }, /** * 前端页面模型转换为后端命令模型,检查文件上传是否完成,去掉后端不需要的多余字段,转换多层嵌入字段数据使其符合服务端命令模型的基本要求 * @param model 前端页面模型 * @param refs 页面中的组件引用集 * @param validFieldNames 有效的字段名称集,如有指定则清除模型中的无效字段 */ toCommandModel: function (vm, model, validFieldNames) { let result = {}; if (model) { if (vm.$refs) { let refKeys = Object.keys(vm.$refs); for (let refKey of refKeys) { let ref = vm.$refs[refKey]; if (typeof ref.getLocationUrl === 'function') { if (ref.validateUploaded() === false) { return null; } } } } let fieldNames = Object.keys(model); for (let fieldName of fieldNames) { if (!validFieldNames || !validFieldNames.length || validFieldNames.contains(fieldName)) { if (fieldName.contains('__')) { let path = fieldName.replace('__', '.'); tnxvue.util.object.setValue(result, path, model[fieldName]); } else { result[fieldName] = model[fieldName]; } } } } return result; }, /** * 转换多层嵌入字段数据使其符合前端页面模型的基本要求 * @param model 服务端视图模型 */ toPageModel: function (model) { let expanded = this._expandRefFields(model); while (expanded) { expanded = this._expandRefFields(model); } return model; }, _expandRefFields: function (model) { let expanded = false; Object.keys(model).forEach(key => { let value = model[key]; if (value && typeof value === 'object') { Object.keys(value).forEach(refKey => { model[key + '__' + refKey] = value[refKey]; }); delete model[key]; expanded = true; } }); return expanded; } }); /** * 页面级缓存支持 */ tnxvue.app.page.cache = { _getName: function (key) { return 'cache_' + key; }, get: function (vm, key, defaultValue) { let name = this._getName(key); let cache = vm.$root[name]; if (cache === undefined) { cache = defaultValue; vm.$root[name] = cache; } return cache; }, set: function (vm, key, value) { let name = this._getName(key); if (value === undefined) { delete vm.$root[name]; } else { vm.$root[name] = value; } } } return tnxvue; });