diff --git a/lib/app/communications/customer.js b/lib/app/communications/customer.js index 467f737..1963dd9 100644 --- a/lib/app/communications/customer.js +++ b/lib/app/communications/customer.js @@ -360,7 +360,7 @@ export default class CustomerCommunication { ); this._var.followers.appendChild(item); if (span.scrollWidth > span.offsetWidth) { - tips.splice(0, 0, r('P_WO_NAME_COLON', 'Name:') + ` ${c.Name}`); + tips.splice(0, 0, r('P_WO_NAME_COLON', 'Name:') + ` ${f.Name}`); } setTooltip(span, tips.join('\n')); } diff --git a/lib/ui.js b/lib/ui.js index baf83ad..c64b90e 100644 --- a/lib/ui.js +++ b/lib/ui.js @@ -4,6 +4,7 @@ import { createElement } from "./functions"; import { createIcon, changeIcon, resolveIcon } from "./ui/icon"; import { createCheckbox, createRadiobox, resolveCheckbox } from "./ui/checkbox"; import { setTooltip, resolveTooltip } from "./ui/tooltip"; +import { createTab } from "./ui/tab"; import { Dropdown } from "./ui/dropdown"; import { Grid } from "./ui/grid/grid"; import { GridColumn, GridInputColumn, GridDropdownColumn, GridCheckboxColumn, GridIconColumn, GridTextColumn, GridDateColumn } from './ui/grid/column'; @@ -25,6 +26,8 @@ export { // tooltip setTooltip, resolveTooltip, + // tab + createTab, // dropdown Dropdown, // grid diff --git a/lib/ui/css/popup.scss b/lib/ui/css/popup.scss index 05f63cb..27b333f 100644 --- a/lib/ui/css/popup.scss +++ b/lib/ui/css/popup.scss @@ -70,6 +70,10 @@ $buttonHeight: 28px; cursor: move; } + >.ui-popup-header-title.no-move { + cursor: default; + } + >.ui-popup-header-icons { flex: 0 0 auto; padding: 10px 12px 6px 0; diff --git a/lib/ui/css/tab.scss b/lib/ui/css/tab.scss new file mode 100644 index 0000000..f6b25c9 --- /dev/null +++ b/lib/ui/css/tab.scss @@ -0,0 +1,47 @@ +.ui-tab-container { + display: flex; + flex-direction: column; + + --header-line-height: 24px; + --header-padding: 6px 6px 0; + --header-hover-color: #b9b9b9; + --header-selected-color: rgb(173, 84, 12); + + >.ui-tab-header { + flex: 0 0 auto; + display: flex; + flex-wrap: wrap; + line-height: var(--header-line-height); + padding: var(--header-padding); + border-bottom: 1px solid var(--border-color); + user-select: none; + + >.ui-tab-title { + border: 1px solid transparent; + background-color: var(--bg-color); + cursor: pointer; + padding: 0 12px; + + &:hover { + color: var(--header-hover-color); + } + + &.selected { + border-color: var(--border-color); + border-bottom-color: var(--bg-color); + color: var(--header-selected-color); + } + } + } + + >.ui-tab-page { + flex: 1 1 auto; + padding: 6px; + overflow: auto; + width: 100%; + height: 100%; + box-sizing: border-box; + position: relative; + display: none; + } +} \ No newline at end of file diff --git a/lib/ui/grid/grid.js b/lib/ui/grid/grid.js index 2b70bd3..44ef7cc 100644 --- a/lib/ui/grid/grid.js +++ b/lib/ui/grid/grid.js @@ -16,6 +16,8 @@ import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCh * @version 1.0.2 */ +const ScriptPath = (self.document == null ? self.location.href : self.document.currentScript.src).replace(/\?.+$/, ''); + const ColumnChangedType = { Reorder: 'reorder', Resize: 'resize', @@ -2092,7 +2094,7 @@ export class Grid { * }> * } * ``` - * @param {boolean} compressed - 是否压缩 + * @param {boolean} [compressed=true] - 是否采用 deflate 压缩 * @returns {Promise} 返回 `Uint8Array` 数据对象 * @since 1.0.2 */ @@ -2150,15 +2152,14 @@ export class Grid { sortKey: this.sortKey, sortArray: this.sortArray }; + const json = JSON.stringify(data); + const uncompressed = json => new TextEncoder('utf-8').encode(json); if (compressed === false) { - const encoder = new TextEncoder(); - const uncompressed = encoder.encode(JSON.stringify(data)); - return Promise.resolve(uncompressed); + return Promise.resolve(uncompressed(json)); } - const js = new Blob(['function h(e,t,o){if(null==e)return"";let r;const l={},h={};let s="",n="",p="",a=2,f=3,c=2;const u=[];let i=0,d=0;for(let w=0;w>=1}else{r=1;for(let e=0;e>=1}0==--a&&(a=Math.pow(2,c),c++),delete h[p]}else{r=l[p];for(let e=0;e>=1}0==--a&&(a=Math.pow(2,c),c++),l[n]=f++,p=String(s)}if(""!==p){if(Object.prototype.hasOwnProperty.call(h,p)){if(p.charCodeAt(0)<256){for(let e=0;e>=1}else{r=1;for(let e=0;e>=1}0==--a&&(a=Math.pow(2,c),c++),delete h[p]}else{r=l[p];for(let e=0;e>=1}0==--a&&(a=Math.pow(2,c),c++)}r=2;for(let e=0;e>=1;let w=!0;do{i<<=1,d==t-1?(u.push(o(i)),w=!1):d++}while(w);return u.join("")}function s(e){return null==e?"":h(e,16,e=>String.fromCharCode(e))}function i(e){const t=s(e),o=new Uint8Array(2*t.length);for(let e=0,r=t.length;e>>8,o[2*e+1]=r%256}return o}self.addEventListener("message",function(e){this.self.postMessage(i(e.data))},!1);']); - return new Promise((resolve, reject) => { + return new Promise(resolve => { let working; - const url = URL.createObjectURL(js); + const url = URL.createObjectURL(new Blob([`let wasm,WASM_VECTOR_LEN=0,encoder=new TextEncoder("utf-8"),cachegetUint8Memory0=null;function getUint8Memory0(){return null!==cachegetUint8Memory0&&cachegetUint8Memory0.buffer===wasm.memory.buffer||(cachegetUint8Memory0=new Uint8Array(wasm.memory.buffer)),cachegetUint8Memory0}let cachegetInt32Memory0=null;function getInt32Memory0(){return null!==cachegetInt32Memory0&&cachegetInt32Memory0.buffer===wasm.memory.buffer||(cachegetInt32Memory0=new Int32Array(wasm.memory.buffer)),cachegetInt32Memory0}function passArray8ToWasm0(e,t){const n=t(1*e.length);return getUint8Memory0().set(e,n/1),WASM_VECTOR_LEN=e.length,n}function getArrayU8FromWasm0(e,t){return getUint8Memory0().subarray(e/1,e/1+t)}function deflate_encode_raw(e){var t=passArray8ToWasm0(e,wasm.__wbindgen_malloc),n=WASM_VECTOR_LEN;wasm.deflate_encode_raw(8,t,n);var r=getInt32Memory0()[2],a=getInt32Memory0()[3],s=getArrayU8FromWasm0(r,a).slice();return wasm.__wbindgen_free(r,1*a),s}self.addEventListener("message",e=>{const t=e.data.type;if("init"===t)if("function"==typeof WebAssembly.instantiateStreaming){const t={},n=fetch(e.data.module);WebAssembly.instantiateStreaming(n,t).then(({instance:e})=>{wasm=e.exports,self.postMessage({type:"init",result:0})}).catch(e=>n.then(t=>{"application/wasm"!==t.headers.get("Content-Type")?self.postMessage({type:"init",error:"\`WebAssembly.instantiateStreaming\` failed because your server does not serve wasm with \`application/wasm\` MIME type. Original error: "+e.message}):self.postMessage({type:"init",error:e.message})}))}else self.postMessage({type:"init",error:"no \`WebAssembly.instantiateStreaming\`"});else if("compress"===t)if(null==wasm)self.postMessage({error:"no \`wasm\` instance"});else{let t=deflate_encode_raw(encoder.encode(e.data.text));self.postMessage(t,[t.buffer])}});`])); const worker = new Worker(url); /** * @private @@ -2171,26 +2172,37 @@ export class Grid { URL.revokeObjectURL(url); next(data); } - // 超过 60 秒则拒绝 + // 超过 30 秒则返回无压缩数据 const timer = setTimeout(() => { if (working) { - terminate(reject, { message: 'timeout' }); + // terminate(reject, { message: 'timeout' }); + terminate(resolve, { data: uncompressed(json), error: 'timeout' }); } - }, 60000); + }, 30000); worker.addEventListener('message', e => { if (working) { - clearTimeout(timer); - terminate(resolve, e.data); + if (e.data.error != null) { + // terminate(reject, { message: e.data.error }); + terminate(resolve, { data: uncompressed(json), error: e.data.error }); + } else { + if (e.data.type === 'init') { + worker.postMessage({ type: 'compress', text: json }); + } else { + clearTimeout(timer); + terminate(resolve, { type: 'compressed', data: e.data }); + } + } } }) worker.addEventListener('error', e => { if (working) { clearTimeout(timer); - terminate(reject, e); + // terminate(reject, e); + terminate(resolve, { data: uncompressed(json), error: e.message }); } }) working = true; - worker.postMessage(JSON.stringify(data)); + worker.postMessage({ type: 'init', module: ScriptPath.replace(/ui\.min\.js$/, 'wasm_flate_bg.wasm') }); }); } diff --git a/lib/ui/popup.d.ts b/lib/ui/popup.d.ts index 7392ff0..2e45afc 100644 --- a/lib/ui/popup.d.ts +++ b/lib/ui/popup.d.ts @@ -13,6 +13,8 @@ interface PopupOptions { zIndex?: number; /** 是否在获取焦点时修改 z-index */ changeZIndex?: boolean; + /** 是否允许关闭 */ + closable?: boolean; /** 是否允许移动 */ movable?: boolean; /** 是否允许修改大小 */ diff --git a/lib/ui/popup.js b/lib/ui/popup.js index dd7df7d..d44fe75 100644 --- a/lib/ui/popup.js +++ b/lib/ui/popup.js @@ -172,7 +172,11 @@ export class Popup { let title = option.title; if (!(title instanceof HTMLElement)) { title = createElement('div', t => { - t.className = 'ui-popup-header-title'; + if (option.movable === false) { + t.className = 'ui-popup-header-title no-move'; + } else { + t.className = 'ui-popup-header-title'; + } t.innerText = title; }); } @@ -244,15 +248,17 @@ export class Popup { }); icons.appendChild(collapse); } - const cancel = createIcon('fa-regular', 'times'); - cancel.tabIndex = tabIndex + 3; - cancel.addEventListener('keypress', e => { - if (e.key === ' ' || e.key === 'Enter') { - this.close(); - } - }); - cancel.addEventListener('click', () => this.close()); - icons.appendChild(cancel); + if (option.closable !== false) { + const cancel = createIcon('fa-regular', 'times'); + cancel.tabIndex = tabIndex + 3; + cancel.addEventListener('keypress', e => { + if (e.key === ' ' || e.key === 'Enter') { + this.close(); + } + }); + cancel.addEventListener('click', () => this.close()); + icons.appendChild(cancel); + } }); header.appendChild(icons); }), diff --git a/lib/ui/tab.js b/lib/ui/tab.js new file mode 100644 index 0000000..b334fd5 --- /dev/null +++ b/lib/ui/tab.js @@ -0,0 +1,43 @@ +import "./css/tab.scss"; + +/** + * Tab 页创建参数类 + * @typedef TabOption + * @property {HTMLElement} container - 父容器 + */ + +import { createElement } from "../functions"; + +/** + * 创建 Tab 页 + * @param {TabOption | HTMLElement} options - 创建选项 + */ +export function createTab(options) { + if (options instanceof HTMLElement) { + options = { container: options }; + } + let container; + if (options?.container instanceof HTMLElement) { + container = options.container; + if (!container.classList.contains('ui-tab-container')) { + container.classList.add('ui-tab-container'); + } + } else { + container = createElement('div', 'ui-tab-container'); + } + container.replaceChildren( + createElement('div', header => { + header.className = 'ui-tab-header'; + header.addEventListener('click', e => { + const title = e.target; + if (title.classList.contains('ui-tab-title')) { + // title + header.querySelectorAll('.ui-tab-title').forEach(t => t === title ? t.classList.add('selected') : t.classList.remove('selected')); + // pages + const page = title.dataset.for; + container.querySelectorAll('[data-page]').forEach(p => p.style.display = p.dataset.page === page ? 'block' : ''); + } + }); + }) + ); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8cf2ba6..5c58636 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "jsdoc": "^4.0.2", "postcss-preset-env": "^9.5.0", "sass": "^1.71.1", - "typedoc": "^0.25.11", + "typedoc": "^0.25.12", "vite": "^5.1.5", "vite-plugin-externals": "^0.6.2" } @@ -1823,9 +1823,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001594", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001594.tgz", - "integrity": "sha512-VblSX6nYqyJVs8DKFMldE2IVCJjZ225LW00ydtUWwh5hk9IfkTOffO6r8gJNsH0qqqeAF8KrbMYA2VEwTlGW5g==", + "version": "1.0.30001597", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz", + "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==", "dev": true, "funding": [ { @@ -1991,9 +1991,9 @@ } }, "node_modules/cssdb": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.11.1.tgz", - "integrity": "sha512-F0nEoX/Rv8ENTHsjMPGHd9opdjGfXkgRBafSUGnQKPzGZFB7Lm0BbT10x21TMOCrKLbVsJ0NoCDMk6AfKqw8/A==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.11.2.tgz", + "integrity": "sha512-lhQ32TFkc1X4eTefGfYPvgovRSzIMofHkigfH8nWtyRL4XJLsRhJFreRvEgKzept7x1rjBuy3J/MurXLaFxW/A==", "dev": true, "funding": [ { @@ -2038,9 +2038,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.694", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.694.tgz", - "integrity": "sha512-kM3SwvGTYpBFJSc8jm4IYVMIOzDmAGd/Ry96O9elRiM6iEwHKNKhtXyFGzpfMMIGZD84W4/hyaULlMmNVvLQlQ==", + "version": "1.4.699", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.699.tgz", + "integrity": "sha512-I7q3BbQi6e4tJJN5CRcyvxhK0iJb34TV8eJQcgh+fR2fQ8miMgZcEInckCo1U9exDHbfz7DLDnFn8oqH/VcRKw==", "dev": true }, "node_modules/entities": { @@ -3463,9 +3463,9 @@ "dev": true }, "node_modules/typedoc": { - "version": "0.25.11", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.11.tgz", - "integrity": "sha512-5MbI1W/FOG6oXsd8bdssQidSTeKh8Kt3xA5uKVzI+K99uzP8EGN45uPnPvQesyaWdD+89s4wCQdtWEd8QUbiRg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.12.tgz", + "integrity": "sha512-F+qhkK2VoTweDXd1c42GS/By2DvI2uDF4/EpG424dTexSHdtCH52C6IcAvMA6jR3DzAWZjHpUOW+E02kyPNUNw==", "dev": true, "dependencies": { "lunr": "^2.3.9", @@ -3480,13 +3480,13 @@ "node": ">= 16" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" } }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true, "peer": true, "bin": { diff --git a/package.json b/package.json index 9e4205f..91cd26d 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "jsdoc": "^4.0.2", "postcss-preset-env": "^9.5.0", "sass": "^1.71.1", - "typedoc": "^0.25.11", + "typedoc": "^0.25.12", "vite": "^5.1.5", "vite-plugin-externals": "^0.6.2" }