n-design-readonly-plugin 1.0.2 → 1.0.3
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/LICENSE +21 -0
- package/README.md +3 -3
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.es.js +68 -66
- package/dist/withReadonly.d.ts +7 -71
- package/package.json +3 -4
- package/.DS_Store +0 -0
- package/.gitignore +0 -31
- package/n-design-readonly-plugin-1.0.0.tgz +0 -0
- package/playground/App.vue +0 -77
- package/playground/index.html +0 -16
- package/playground/main.ts +0 -12
- package/playground/reset.css +0 -126
- package/src/index.ts +0 -52
- package/src/types/env.d.ts +0 -11
- package/src/withReadonly.tsx +0 -414
- package/tsconfig.json +0 -19
- package/vite.config.playground.ts +0 -17
- package/vite.config.ts +0 -20
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pangsh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -31,14 +31,14 @@ import App from './App.vue';
|
|
|
31
31
|
|
|
32
32
|
// 引入 n-designv3(必须)
|
|
33
33
|
import NDesignV3 from 'n-designv3';
|
|
34
|
-
import 'n-designv3/dist/
|
|
34
|
+
import 'n-designv3/dist/nancal.variable.min.css';
|
|
35
35
|
|
|
36
36
|
// 引入本插件
|
|
37
|
-
import
|
|
37
|
+
import NDesignReadolyPlugin from 'n-design-readonly-plugin';
|
|
38
38
|
|
|
39
39
|
const app = createApp(App);
|
|
40
40
|
app.use(NDesignV3);
|
|
41
|
-
app.use(
|
|
41
|
+
app.use(NDesignReadolyPlugin); // 注册后自动提供 NkInput, NkSelect 等组件
|
|
42
42
|
app.mount('#app');
|
|
43
43
|
|
|
44
44
|
```
|
package/dist/index.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const u=require("vue"),l=require("n-designv3");function R(e){if(e==null)return"";if(typeof e=="string")return e;if(Array.isArray(e))return e.map(R).join("");if(typeof e=="object"){if(typeof e.children=="string")return e.children;if(e?.default&&typeof e.default=="function"){const n=e.default();if(Array.isArray(n))return R(n)}e.children&&R(e.children)}return""}function L(e,n={}){const t=new Map,{value:c="value",label:s="label"}=n;if(!e)return t;const d=Array.isArray(e)?e:[e];for(const r of d)if(!(!r||typeof r!="object")){if(r.type&&(typeof r.type=="object"||typeof r.type=="function")){let f=r.type.name?.toLowerCase()||"";if(typeof r.type=="function"?f=r.type.displayName?.toLowerCase()||"":typeof r.type=="string"&&(f=r.type?.toLowerCase()||""),/option|radio|checkbox/i.test(f)){const o=r.props||{},a=o[c]??o.value,y=o[s]??o.label;if(a!=null){const w=R(r.children)||y||String(a);t.set(a,w)}}}r.children&&L(r.children,n).forEach((o,a)=>{t.has(a)||t.set(a,o)})}return t}function D(e,n,t){const{value:c="value",label:s="label",children:d="children"}=t,r=[];let f=e;for(const o of n){const a=f.find(y=>y[c]===o);if(a)r.push(a[s]||a[c]||String(o)),f=a[d]||[];else{r.push(String(o));break}}return r}function G(e,n,t){const{value:c="value",label:s="title",children:d="children"}=t,r=[],f=new Set(Array.isArray(n)?n:[n]);function o(a){for(const y of a)f.has(y[c])&&r.push(y[s]||y[c]),y[d]&&o(y[d])}return o(e),r}function H({modelValue:e,options:n,treeData:t,valueToLabel:c,fieldNames:s,slots:d,isSelect:r,isRadioGroup:f,isCheckboxGroup:o,isCheckbox:a,isRadio:y,isCascader:w,isTreeSelect:C,isSwitch:T,attrs:p,emptyText:g}){if(c)try{return c(e)||g}catch(i){console.warn("[ReadonlyHOC] valueToLabel error",i)}if(T){const i=p.checkedValue??p["checked-value"]??!0,h=p.uncheckedValue??p["un-checked-value"]??!1;return e===i?p.checkedChildren??p["checked-children"]??"开启":e===h?p.uncheckedChildren??p["un-checked-children"]??"关闭":String(e||g)}if(w&&Array.isArray(e)&&n?.length)return D(n,e,s).join(" / ")||g;if(C&&t?.length)return G(t,e,s).join(", ")||g;if(n?.length){const i=s.value||"value",h=s.label||"label",S=k=>{const x=n.find(m=>m[i]===k);return x?x[h]||x[i]:k==null?"":String(k)};return Array.isArray(e)?e.map(S).join(", ")||g:S(e)||g}else if((r||f||o)&&d?.default)try{const i=d.default(),h=L(i,s);if(h.size>0){const S=k=>k==null?"":h.get(k)||String(k);return Array.isArray(e)?e.map(S).join(", ")||g:S(e)||g}}catch(i){console.warn("[ReadonlyHOC] Failed to parse slot options",i)}else if(d?.default&&(a||y)&&typeof e=="boolean")try{const i=d.default(),h=R(i);if(h)return h}catch{}return e==null||e===""?g:Array.isArray(e)?e.join(", "):String(e)}function O(e){if(typeof e=="function")return e;const n=e.name||"",t=n.toLowerCase(),c=/select/i.test(t)&&!/tree|cascader/i.test(t),s=/radio/i.test(t)&&!/group/i.test(t),d=/radio.*group/i.test(t),r=/checkbox/i.test(t)&&!/group/i.test(t),f=/checkbox.*group/i.test(t),o=/switch/i.test(t),a=/cascader/i.test(t),y=/tree.*select/i.test(t),C=r||o||s?"checked":"value",T=`update:${C}`,p=/form$/i.test(t),g=/form.*item/i.test(t);return u.defineComponent({name:n,inheritAttrs:!1,props:{modelValue:{type:[String,Number,Boolean,Array,Object],default:void 0},[C]:{type:[String,Number,Boolean,Array,Object],default:void 0},readonly:{type:Boolean,default:void 0},emptyText:{type:String,default:"--"},valueToLabel:{type:Function,default:null}},emits:["update:modelValue",T],setup(i,{emit:h,slots:S,expose:k}){const x=u.inject("nkReadonly",u.ref(!1)),m=u.computed(()=>i.readonly??x.value),F=u.computed(()=>i.readonly??x.value);p&&u.provide("nkReadonly",F);const j=u.computed(()=>i[C]!==void 0?i[C]:i.modelValue),K=v=>{h("update:modelValue",v),h(T,v)},b=u.useAttrs(),P=u.computed(()=>{if(!m.value)return"";const v=b.fieldNames??b["field-names"]??{},N=b.options,I=b.treeData??b["tree-data"];return H({modelValue:j.value,options:N,treeData:I,valueToLabel:i.valueToLabel,fieldNames:v,slots:S,isSelect:c,isRadioGroup:d,isCheckboxGroup:f,isCheckbox:r,isRadio:s,isCascader:a,isTreeSelect:y,isSwitch:o,attrs:b,emptyText:i.emptyText})}),M={lineHeight:"32px",padding:"0 6px",display:"inline-block",minHeight:"32px",border:"none",backgroundColor:"none",cursor:"default",wordBreak:"break-all",color:"rgba(0, 0, 0, 0.65)"},A=u.ref();return p&&k({validate:()=>m.value?Promise.resolve({}):A.value?.validate?.(),validateFields:v=>m.value?Promise.resolve({}):A.value?.validateFields?.(v),resetFields:v=>!m.value&&A.value?.resetFields?.(v),clearValidate:v=>!m.value&&A.value?.clearValidate?.(v),scrollToField:v=>!m.value&&A.value?.scrollToField?.(v)}),()=>g?u.h(e,{...b,style:{...b.style||{},marginBottom:m.value?"10px":void 0}},S):p?u.h(e,{...b,rules:F.value?null:b.rules,disabled:F.value?!0:b.disabled,ref:A},S):m.value?u.h("span",{class:"nk-readonly-wrapper",style:M,role:"text",tabindex:0,"aria-readonly":"true"},P.value):u.h(e,{...b,[C]:j.value,[`onUpdate:${C}`]:K,ref:p?A:void 0},S)}})}const $={Input:l.Input,InputNumber:l.InputNumber,Textarea:l.Textarea,Select:l.Select,SelectOption:l.Select.Option,TreeSelect:l.TreeSelect,Cascader:l.Cascader,DatePicker:l.DatePicker,TimePicker:l.TimePicker,Radio:l.Radio,Checkbox:l.Checkbox,RadioGroup:l.Radio.Group,CheckboxGroup:l.Checkbox.Group,Switch:l.Switch,Form:l.Form,FormItem:l.Form.Item},E={install(e){for(const[n,t]of Object.entries($))if(t){const c=O(t);e.component(`Nk${n}`,c)}}};exports.default=E;exports.withReadonly=O;
|
package/dist/index.d.ts
CHANGED
package/dist/index.es.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { defineComponent as E, inject as
|
|
2
|
-
import { Form as K, Checkbox as O, Radio as N, Select as M, Switch as
|
|
3
|
-
function
|
|
1
|
+
import { defineComponent as E, inject as U, ref as T, computed as w, provide as z, useAttrs as q, h as j } from "vue";
|
|
2
|
+
import { Form as K, Checkbox as O, Radio as N, Select as M, Switch as B, TimePicker as J, DatePicker as Q, Cascader as W, TreeSelect as X, Textarea as Y, InputNumber as Z, Input as _ } from "n-designv3";
|
|
3
|
+
function C(e) {
|
|
4
4
|
if (e == null) return "";
|
|
5
5
|
if (typeof e == "string") return e;
|
|
6
|
-
if (Array.isArray(e)) return e.map(
|
|
6
|
+
if (Array.isArray(e)) return e.map(C).join("");
|
|
7
7
|
if (typeof e == "object") {
|
|
8
8
|
if (typeof e.children == "string")
|
|
9
9
|
return e.children;
|
|
10
10
|
if (e?.default && typeof e.default == "function") {
|
|
11
11
|
const n = e.default();
|
|
12
12
|
if (Array.isArray(n))
|
|
13
|
-
return
|
|
13
|
+
return C(n);
|
|
14
14
|
}
|
|
15
|
-
e.children &&
|
|
15
|
+
e.children && C(e.children);
|
|
16
16
|
}
|
|
17
17
|
return "";
|
|
18
18
|
}
|
|
@@ -25,15 +25,15 @@ function P(e, n = {}) {
|
|
|
25
25
|
if (r.type && (typeof r.type == "object" || typeof r.type == "function")) {
|
|
26
26
|
let u = r.type.name?.toLowerCase() || "";
|
|
27
27
|
if (typeof r.type == "function" ? u = r.type.displayName?.toLowerCase() || "" : typeof r.type == "string" && (u = r.type?.toLowerCase() || ""), /option|radio|checkbox/i.test(u)) {
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
const x =
|
|
31
|
-
t.set(
|
|
28
|
+
const a = r.props || {}, o = a[l] ?? a.value, f = a[c] ?? a.label;
|
|
29
|
+
if (o != null) {
|
|
30
|
+
const x = C(r.children) || f || String(o);
|
|
31
|
+
t.set(o, x);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
r.children && P(r.children, n).forEach((
|
|
36
|
-
t.has(
|
|
35
|
+
r.children && P(r.children, n).forEach((a, o) => {
|
|
36
|
+
t.has(o) || t.set(o, a);
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
return t;
|
|
@@ -41,12 +41,12 @@ function P(e, n = {}) {
|
|
|
41
41
|
function V(e, n, t) {
|
|
42
42
|
const { value: l = "value", label: c = "label", children: s = "children" } = t, r = [];
|
|
43
43
|
let u = e;
|
|
44
|
-
for (const
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
r.push(
|
|
44
|
+
for (const a of n) {
|
|
45
|
+
const o = u.find((f) => f[l] === a);
|
|
46
|
+
if (o)
|
|
47
|
+
r.push(o[c] || o[l] || String(a)), u = o[s] || [];
|
|
48
48
|
else {
|
|
49
|
-
r.push(String(
|
|
49
|
+
r.push(String(a));
|
|
50
50
|
break;
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -54,11 +54,11 @@ function V(e, n, t) {
|
|
|
54
54
|
}
|
|
55
55
|
function ee(e, n, t) {
|
|
56
56
|
const { value: l = "value", label: c = "title", children: s = "children" } = t, r = [], u = new Set(Array.isArray(n) ? n : [n]);
|
|
57
|
-
function o
|
|
58
|
-
for (const f of
|
|
59
|
-
u.has(f[l]) && r.push(f[c] || f[l]), f[s] &&
|
|
57
|
+
function a(o) {
|
|
58
|
+
for (const f of o)
|
|
59
|
+
u.has(f[l]) && r.push(f[c] || f[l]), f[s] && a(f[s]);
|
|
60
60
|
}
|
|
61
|
-
return
|
|
61
|
+
return a(e), r;
|
|
62
62
|
}
|
|
63
63
|
function te({
|
|
64
64
|
modelValue: e,
|
|
@@ -69,11 +69,11 @@ function te({
|
|
|
69
69
|
slots: s,
|
|
70
70
|
isSelect: r,
|
|
71
71
|
isRadioGroup: u,
|
|
72
|
-
isCheckboxGroup:
|
|
73
|
-
isCheckbox:
|
|
72
|
+
isCheckboxGroup: a,
|
|
73
|
+
isCheckbox: o,
|
|
74
74
|
isRadio: f,
|
|
75
75
|
isCascader: x,
|
|
76
|
-
isTreeSelect:
|
|
76
|
+
isTreeSelect: S,
|
|
77
77
|
isSwitch: R,
|
|
78
78
|
attrs: d,
|
|
79
79
|
emptyText: b
|
|
@@ -90,72 +90,74 @@ function te({
|
|
|
90
90
|
}
|
|
91
91
|
if (x && Array.isArray(e) && n?.length)
|
|
92
92
|
return V(n, e, c).join(" / ") || b;
|
|
93
|
-
if (
|
|
93
|
+
if (S && t?.length)
|
|
94
94
|
return ee(t, e, c).join(", ") || b;
|
|
95
95
|
if (n?.length) {
|
|
96
|
-
const i = c.value || "value", y = c.label || "label",
|
|
97
|
-
const
|
|
98
|
-
return
|
|
96
|
+
const i = c.value || "value", y = c.label || "label", v = (g) => {
|
|
97
|
+
const A = n.find((k) => k[i] === g);
|
|
98
|
+
return A ? A[y] || A[i] : g == null ? "" : String(g);
|
|
99
99
|
};
|
|
100
|
-
return Array.isArray(e) ? e.map(
|
|
101
|
-
} else if ((r || u ||
|
|
100
|
+
return Array.isArray(e) ? e.map(v).join(", ") || b : v(e) || b;
|
|
101
|
+
} else if ((r || u || a) && s?.default)
|
|
102
102
|
try {
|
|
103
103
|
const i = s.default(), y = P(i, c);
|
|
104
104
|
if (y.size > 0) {
|
|
105
|
-
const
|
|
106
|
-
return Array.isArray(e) ? e.map(
|
|
105
|
+
const v = (g) => g == null ? "" : y.get(g) || String(g);
|
|
106
|
+
return Array.isArray(e) ? e.map(v).join(", ") || b : v(e) || b;
|
|
107
107
|
}
|
|
108
108
|
} catch (i) {
|
|
109
109
|
console.warn("[ReadonlyHOC] Failed to parse slot options", i);
|
|
110
110
|
}
|
|
111
|
-
else if (s?.default && (
|
|
111
|
+
else if (s?.default && (o || f) && typeof e == "boolean")
|
|
112
112
|
try {
|
|
113
|
-
const i = s.default(), y =
|
|
113
|
+
const i = s.default(), y = C(i);
|
|
114
114
|
if (y) return y;
|
|
115
115
|
} catch {
|
|
116
116
|
}
|
|
117
117
|
return e == null || e === "" ? b : Array.isArray(e) ? e.join(", ") : String(e);
|
|
118
118
|
}
|
|
119
119
|
function re(e) {
|
|
120
|
-
|
|
120
|
+
if (typeof e == "function")
|
|
121
|
+
return e;
|
|
122
|
+
const n = e.name || "", t = n.toLowerCase(), l = /select/i.test(t) && !/tree|cascader/i.test(t), c = /radio/i.test(t) && !/group/i.test(t), s = /radio.*group/i.test(t), r = /checkbox/i.test(t) && !/group/i.test(t), u = /checkbox.*group/i.test(t), a = /switch/i.test(t), o = /cascader/i.test(t), f = /tree.*select/i.test(t), S = r || a || c ? "checked" : "value", R = `update:${S}`, d = /form$/i.test(t), b = /form.*item/i.test(t);
|
|
121
123
|
return E({
|
|
122
124
|
name: n,
|
|
123
125
|
inheritAttrs: !1,
|
|
124
126
|
props: {
|
|
125
127
|
modelValue: { type: [String, Number, Boolean, Array, Object], default: void 0 },
|
|
126
|
-
[
|
|
128
|
+
[S]: { type: [String, Number, Boolean, Array, Object], default: void 0 },
|
|
127
129
|
readonly: { type: Boolean, default: void 0 },
|
|
128
130
|
emptyText: { type: String, default: "--" },
|
|
129
131
|
valueToLabel: { type: Function, default: null }
|
|
130
132
|
},
|
|
131
133
|
emits: ["update:modelValue", R],
|
|
132
|
-
setup(i, { emit: y, slots:
|
|
133
|
-
const
|
|
134
|
-
d &&
|
|
135
|
-
const L = w(() => i[
|
|
134
|
+
setup(i, { emit: y, slots: v, expose: g }) {
|
|
135
|
+
const A = U("nkReadonly", T(!1)), k = w(() => i.readonly ?? A.value), F = w(() => i.readonly ?? A.value);
|
|
136
|
+
d && z("nkReadonly", F);
|
|
137
|
+
const L = w(() => i[S] !== void 0 ? i[S] : i.modelValue), G = (h) => {
|
|
136
138
|
y("update:modelValue", h), y(R, h);
|
|
137
|
-
}, p =
|
|
139
|
+
}, p = q(), I = w(() => {
|
|
138
140
|
if (!k.value) return "";
|
|
139
|
-
const h = p.fieldNames ?? p["field-names"] ?? {},
|
|
141
|
+
const h = p.fieldNames ?? p["field-names"] ?? {}, H = p.options, $ = p.treeData ?? p["tree-data"];
|
|
140
142
|
return te({
|
|
141
143
|
modelValue: L.value,
|
|
142
|
-
options:
|
|
143
|
-
treeData:
|
|
144
|
+
options: H,
|
|
145
|
+
treeData: $,
|
|
144
146
|
valueToLabel: i.valueToLabel,
|
|
145
147
|
fieldNames: h,
|
|
146
|
-
slots:
|
|
148
|
+
slots: v,
|
|
147
149
|
isSelect: l,
|
|
148
150
|
isRadioGroup: s,
|
|
149
151
|
isCheckboxGroup: u,
|
|
150
152
|
isCheckbox: r,
|
|
151
153
|
isRadio: c,
|
|
152
|
-
isCascader:
|
|
154
|
+
isCascader: o,
|
|
153
155
|
isTreeSelect: f,
|
|
154
|
-
isSwitch:
|
|
156
|
+
isSwitch: a,
|
|
155
157
|
attrs: p,
|
|
156
158
|
emptyText: i.emptyText
|
|
157
159
|
});
|
|
158
|
-
}),
|
|
160
|
+
}), D = {
|
|
159
161
|
lineHeight: "32px",
|
|
160
162
|
padding: "0 6px",
|
|
161
163
|
display: "inline-block",
|
|
@@ -165,34 +167,34 @@ function re(e) {
|
|
|
165
167
|
cursor: "default",
|
|
166
168
|
wordBreak: "break-all",
|
|
167
169
|
color: "rgba(0, 0, 0, 0.65)"
|
|
168
|
-
},
|
|
169
|
-
return d &&
|
|
170
|
-
validate: () => k.value ? Promise.resolve({}) :
|
|
171
|
-
validateFields: (h) => k.value ? Promise.resolve({}) :
|
|
172
|
-
resetFields: (h) => !k.value &&
|
|
173
|
-
clearValidate: (h) => !k.value &&
|
|
174
|
-
scrollToField: (h) => !k.value &&
|
|
170
|
+
}, m = T();
|
|
171
|
+
return d && g({
|
|
172
|
+
validate: () => k.value ? Promise.resolve({}) : m.value?.validate?.(),
|
|
173
|
+
validateFields: (h) => k.value ? Promise.resolve({}) : m.value?.validateFields?.(h),
|
|
174
|
+
resetFields: (h) => !k.value && m.value?.resetFields?.(h),
|
|
175
|
+
clearValidate: (h) => !k.value && m.value?.clearValidate?.(h),
|
|
176
|
+
scrollToField: (h) => !k.value && m.value?.scrollToField?.(h)
|
|
175
177
|
}), () => b ? j(
|
|
176
178
|
e,
|
|
177
179
|
{
|
|
178
180
|
...p,
|
|
179
181
|
style: { ...p.style || {}, marginBottom: k.value ? "10px" : void 0 }
|
|
180
182
|
},
|
|
181
|
-
|
|
183
|
+
v
|
|
182
184
|
) : d ? j(
|
|
183
185
|
e,
|
|
184
186
|
{
|
|
185
187
|
...p,
|
|
186
188
|
rules: F.value ? null : p.rules,
|
|
187
189
|
disabled: F.value ? !0 : p.disabled,
|
|
188
|
-
ref:
|
|
190
|
+
ref: m
|
|
189
191
|
},
|
|
190
|
-
|
|
192
|
+
v
|
|
191
193
|
) : k.value ? j(
|
|
192
194
|
"span",
|
|
193
195
|
{
|
|
194
196
|
class: "nk-readonly-wrapper",
|
|
195
|
-
style:
|
|
197
|
+
style: D,
|
|
196
198
|
role: "text",
|
|
197
199
|
tabindex: 0,
|
|
198
200
|
"aria-readonly": "true"
|
|
@@ -202,11 +204,11 @@ function re(e) {
|
|
|
202
204
|
e,
|
|
203
205
|
{
|
|
204
206
|
...p,
|
|
205
|
-
[
|
|
206
|
-
[`onUpdate:${
|
|
207
|
-
ref: d ?
|
|
207
|
+
[S]: L.value,
|
|
208
|
+
[`onUpdate:${S}`]: G,
|
|
209
|
+
ref: d ? m : void 0
|
|
208
210
|
},
|
|
209
|
-
|
|
211
|
+
v
|
|
210
212
|
);
|
|
211
213
|
}
|
|
212
214
|
});
|
|
@@ -225,10 +227,10 @@ const ne = {
|
|
|
225
227
|
Checkbox: O,
|
|
226
228
|
RadioGroup: N.Group,
|
|
227
229
|
CheckboxGroup: O.Group,
|
|
228
|
-
Switch:
|
|
230
|
+
Switch: B,
|
|
229
231
|
Form: K,
|
|
230
232
|
FormItem: K.Item
|
|
231
|
-
},
|
|
233
|
+
}, oe = {
|
|
232
234
|
install(e) {
|
|
233
235
|
for (const [n, t] of Object.entries(ne))
|
|
234
236
|
if (t) {
|
|
@@ -238,6 +240,6 @@ const ne = {
|
|
|
238
240
|
}
|
|
239
241
|
};
|
|
240
242
|
export {
|
|
241
|
-
|
|
243
|
+
oe as default,
|
|
242
244
|
re as withReadonly
|
|
243
245
|
};
|
package/dist/withReadonly.d.ts
CHANGED
|
@@ -1,71 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} | {
|
|
9
|
-
type: BooleanConstructor;
|
|
10
|
-
default: undefined;
|
|
11
|
-
} | {
|
|
12
|
-
type: StringConstructor;
|
|
13
|
-
default: string;
|
|
14
|
-
} | {
|
|
15
|
-
type: PropType<ValueToLabelFn>;
|
|
16
|
-
default: null;
|
|
17
|
-
};
|
|
18
|
-
modelValue: {
|
|
19
|
-
type: PropType<FormModelValue>;
|
|
20
|
-
default: undefined;
|
|
21
|
-
};
|
|
22
|
-
readonly: {
|
|
23
|
-
type: BooleanConstructor;
|
|
24
|
-
default: undefined;
|
|
25
|
-
};
|
|
26
|
-
emptyText: {
|
|
27
|
-
type: StringConstructor;
|
|
28
|
-
default: string;
|
|
29
|
-
};
|
|
30
|
-
valueToLabel: {
|
|
31
|
-
type: PropType<ValueToLabelFn>;
|
|
32
|
-
default: null;
|
|
33
|
-
};
|
|
34
|
-
}, () => VNode<import('vue').RendererNode, import('vue').RendererElement, {
|
|
35
|
-
[key: string]: any;
|
|
36
|
-
}>, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, string[], string, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<import('vue').ExtractPropTypes<{
|
|
37
|
-
[x: string]: {
|
|
38
|
-
type: PropType<FormModelValue>;
|
|
39
|
-
default: undefined;
|
|
40
|
-
} | {
|
|
41
|
-
type: BooleanConstructor;
|
|
42
|
-
default: undefined;
|
|
43
|
-
} | {
|
|
44
|
-
type: StringConstructor;
|
|
45
|
-
default: string;
|
|
46
|
-
} | {
|
|
47
|
-
type: PropType<ValueToLabelFn>;
|
|
48
|
-
default: null;
|
|
49
|
-
};
|
|
50
|
-
modelValue: {
|
|
51
|
-
type: PropType<FormModelValue>;
|
|
52
|
-
default: undefined;
|
|
53
|
-
};
|
|
54
|
-
readonly: {
|
|
55
|
-
type: BooleanConstructor;
|
|
56
|
-
default: undefined;
|
|
57
|
-
};
|
|
58
|
-
emptyText: {
|
|
59
|
-
type: StringConstructor;
|
|
60
|
-
default: string;
|
|
61
|
-
};
|
|
62
|
-
valueToLabel: {
|
|
63
|
-
type: PropType<ValueToLabelFn>;
|
|
64
|
-
default: null;
|
|
65
|
-
};
|
|
66
|
-
}>> & {
|
|
67
|
-
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
68
|
-
}, {
|
|
69
|
-
[x: string]: FormModelValue | ValueToLabelFn;
|
|
70
|
-
}, {}>;
|
|
71
|
-
export {};
|
|
1
|
+
import { Component } from 'vue';
|
|
2
|
+
export declare function withReadonly(BaseComponent: Component): import('vue').FunctionalComponent<any, any, any> | {
|
|
3
|
+
new (...args: any[]): any;
|
|
4
|
+
__isFragment?: never;
|
|
5
|
+
__isTeleport?: never;
|
|
6
|
+
__isSuspense?: never;
|
|
7
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n-design-readonly-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "只读模式插件 for n-designv3",
|
|
5
5
|
"main": "./dist/index.cjs.js",
|
|
6
6
|
"module": "./dist/index.es.js",
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
""
|
|
16
|
+
"dist", "README.md", "LICENSE"
|
|
18
17
|
],
|
|
19
18
|
"scripts": {
|
|
20
19
|
"dev": "vite --config vite.config.playground.ts",
|
|
@@ -31,7 +30,7 @@
|
|
|
31
30
|
"hoc"
|
|
32
31
|
],
|
|
33
32
|
"author": "pangsh<psh2416623245@163.com>",
|
|
34
|
-
"license": "
|
|
33
|
+
"license": "MIT",
|
|
35
34
|
"packageManager": "pnpm@10.21.0",
|
|
36
35
|
"devDependencies": {
|
|
37
36
|
"@types/node": "^25.2.0",
|
package/.DS_Store
DELETED
|
Binary file
|
package/.gitignore
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Logs
|
|
2
|
-
logs
|
|
3
|
-
*.log
|
|
4
|
-
npm-debug.log*
|
|
5
|
-
yarn-debug.log*
|
|
6
|
-
yarn-error.log*
|
|
7
|
-
pnpm-debug.log*
|
|
8
|
-
lerna-debug.log*
|
|
9
|
-
|
|
10
|
-
node_modules
|
|
11
|
-
.DS_Store
|
|
12
|
-
dist
|
|
13
|
-
dist-ssr
|
|
14
|
-
coverage
|
|
15
|
-
*.local
|
|
16
|
-
package-lock.json
|
|
17
|
-
report.html
|
|
18
|
-
|
|
19
|
-
/cypress/videos/
|
|
20
|
-
/cypress/screenshots/
|
|
21
|
-
|
|
22
|
-
# Editor directories and files
|
|
23
|
-
.vscode/*
|
|
24
|
-
!.vscode/extensions.json
|
|
25
|
-
!.vscode/settings.json
|
|
26
|
-
.idea
|
|
27
|
-
*.suo
|
|
28
|
-
*.ntvs*
|
|
29
|
-
*.njsproj
|
|
30
|
-
*.sln
|
|
31
|
-
*.sw?
|
|
Binary file
|
package/playground/App.vue
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="form-wrapper">
|
|
3
|
-
<nk-form :readonly="false" :model="form" :rules="rules">
|
|
4
|
-
<nk-form-item label="用户名" name="name">
|
|
5
|
-
<nk-input placeholder="用户名" v-model:value="form.name"></nk-input>
|
|
6
|
-
</nk-form-item>
|
|
7
|
-
<n-row>
|
|
8
|
-
<n-col :span="24">
|
|
9
|
-
<nk-form-item label="下拉options" name="ep6ClusterObjId">
|
|
10
|
-
<nk-select
|
|
11
|
-
allowClear
|
|
12
|
-
placeholder="请选择"
|
|
13
|
-
:options="options"
|
|
14
|
-
:showSearch="false"
|
|
15
|
-
v-model:value="form.ep6ClusterObjId"
|
|
16
|
-
>
|
|
17
|
-
</nk-select>
|
|
18
|
-
</nk-form-item>
|
|
19
|
-
</n-col>
|
|
20
|
-
</n-row>
|
|
21
|
-
<n-row>
|
|
22
|
-
<n-col :span="24">
|
|
23
|
-
<nk-form-item label="下拉options" name="ep6ClusterObjId">
|
|
24
|
-
<n-select
|
|
25
|
-
allowClear
|
|
26
|
-
placeholder="请选择"
|
|
27
|
-
:options="options"
|
|
28
|
-
:showSearch="false"
|
|
29
|
-
v-model:value="form.ep6ClusterObjId"
|
|
30
|
-
>
|
|
31
|
-
</n-select>
|
|
32
|
-
</nk-form-item>
|
|
33
|
-
</n-col>
|
|
34
|
-
</n-row>
|
|
35
|
-
</nk-form>
|
|
36
|
-
</div>
|
|
37
|
-
</template>
|
|
38
|
-
|
|
39
|
-
<script lang="ts" setup>
|
|
40
|
-
import { reactive } from "vue";
|
|
41
|
-
|
|
42
|
-
const form = reactive({
|
|
43
|
-
name: "张三",
|
|
44
|
-
ep6ClusterObjId: "1",
|
|
45
|
-
});
|
|
46
|
-
const options = [
|
|
47
|
-
{
|
|
48
|
-
label: "选项1",
|
|
49
|
-
value: "1",
|
|
50
|
-
externalValue: "externalValue1",
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
label: "选项2",
|
|
54
|
-
value: "2",
|
|
55
|
-
externalValue: "externalValue2",
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
label: "选项3",
|
|
59
|
-
value: "3",
|
|
60
|
-
externalValue: "externalValue3",
|
|
61
|
-
},
|
|
62
|
-
];
|
|
63
|
-
const rules = {
|
|
64
|
-
name: [{ required: true, message: "请输入用户名" }],
|
|
65
|
-
ep6ClusterObjId: [{ required: true, message: "请选择" }],
|
|
66
|
-
};
|
|
67
|
-
</script>
|
|
68
|
-
<style>
|
|
69
|
-
.form-wrapper {
|
|
70
|
-
width: 100%;
|
|
71
|
-
height: 100vh;
|
|
72
|
-
padding: 20px;
|
|
73
|
-
box-sizing: border-box;
|
|
74
|
-
position: relative;
|
|
75
|
-
background-color: bisque;
|
|
76
|
-
}
|
|
77
|
-
</style>
|
package/playground/index.html
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="UTF-8" />
|
|
6
|
-
<link rel="icon" href="/favicon.ico" />
|
|
7
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
-
<title>NkReadonlyHOC Playground</title>
|
|
9
|
-
</head>
|
|
10
|
-
|
|
11
|
-
<body>
|
|
12
|
-
<div id="app"></div>
|
|
13
|
-
<script type="module" src="./main.ts"></script>
|
|
14
|
-
</body>
|
|
15
|
-
|
|
16
|
-
</html>
|
package/playground/main.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// main.ts
|
|
2
|
-
import { createApp } from 'vue';
|
|
3
|
-
import App from './App.vue';
|
|
4
|
-
import { NkReadonlyPlugin } from '../src/index';
|
|
5
|
-
import NDesign from 'n-designv3';
|
|
6
|
-
import 'n-designv3/dist/nancal.variable.min.css';
|
|
7
|
-
import './reset.css'
|
|
8
|
-
|
|
9
|
-
const app = createApp(App);
|
|
10
|
-
app.use(NDesign);
|
|
11
|
-
app.use(NkReadonlyPlugin); // ← 一行注册!
|
|
12
|
-
app.mount('#app');
|
package/playground/reset.css
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
html,
|
|
2
|
-
body,
|
|
3
|
-
div,
|
|
4
|
-
span,
|
|
5
|
-
applet,
|
|
6
|
-
object,
|
|
7
|
-
iframe,
|
|
8
|
-
p,
|
|
9
|
-
blockquote,
|
|
10
|
-
pre,
|
|
11
|
-
a,
|
|
12
|
-
abbr,
|
|
13
|
-
acronym,
|
|
14
|
-
address,
|
|
15
|
-
big,
|
|
16
|
-
cite,
|
|
17
|
-
code,
|
|
18
|
-
del,
|
|
19
|
-
dfn,
|
|
20
|
-
em,
|
|
21
|
-
img,
|
|
22
|
-
ins,
|
|
23
|
-
kbd,
|
|
24
|
-
q,
|
|
25
|
-
s,
|
|
26
|
-
samp,
|
|
27
|
-
small,
|
|
28
|
-
strike,
|
|
29
|
-
strong,
|
|
30
|
-
sub,
|
|
31
|
-
sup,
|
|
32
|
-
tt,
|
|
33
|
-
var,
|
|
34
|
-
b,
|
|
35
|
-
u,
|
|
36
|
-
i,
|
|
37
|
-
center,
|
|
38
|
-
dl,
|
|
39
|
-
dt,
|
|
40
|
-
dd,
|
|
41
|
-
ol,
|
|
42
|
-
ul,
|
|
43
|
-
li,
|
|
44
|
-
fieldset,
|
|
45
|
-
form,
|
|
46
|
-
label,
|
|
47
|
-
legend,
|
|
48
|
-
table,
|
|
49
|
-
caption,
|
|
50
|
-
tbody,
|
|
51
|
-
tfoot,
|
|
52
|
-
thead,
|
|
53
|
-
tr,
|
|
54
|
-
th,
|
|
55
|
-
td,
|
|
56
|
-
article,
|
|
57
|
-
aside,
|
|
58
|
-
canvas,
|
|
59
|
-
details,
|
|
60
|
-
embed,
|
|
61
|
-
figure,
|
|
62
|
-
figcaption,
|
|
63
|
-
footer,
|
|
64
|
-
header,
|
|
65
|
-
hgroup,
|
|
66
|
-
menu,
|
|
67
|
-
nav,
|
|
68
|
-
output,
|
|
69
|
-
ruby,
|
|
70
|
-
section,
|
|
71
|
-
summary,
|
|
72
|
-
time,
|
|
73
|
-
mark,
|
|
74
|
-
audio,
|
|
75
|
-
video {
|
|
76
|
-
margin: 0;
|
|
77
|
-
padding: 0;
|
|
78
|
-
border: 0;
|
|
79
|
-
font-size: 100%;
|
|
80
|
-
font: inherit;
|
|
81
|
-
// vertical-align: baseline;
|
|
82
|
-
}
|
|
83
|
-
/* HTML5 display-role reset for older browsers */
|
|
84
|
-
article,
|
|
85
|
-
aside,
|
|
86
|
-
details,
|
|
87
|
-
figcaption,
|
|
88
|
-
figure,
|
|
89
|
-
footer,
|
|
90
|
-
header,
|
|
91
|
-
hgroup,
|
|
92
|
-
menu,
|
|
93
|
-
nav,
|
|
94
|
-
section {
|
|
95
|
-
display: block;
|
|
96
|
-
}
|
|
97
|
-
body {
|
|
98
|
-
line-height: 1;
|
|
99
|
-
}
|
|
100
|
-
ol,
|
|
101
|
-
ul {
|
|
102
|
-
list-style: none;
|
|
103
|
-
}
|
|
104
|
-
blockquote,
|
|
105
|
-
q {
|
|
106
|
-
quotes: none;
|
|
107
|
-
}
|
|
108
|
-
blockquote:before,
|
|
109
|
-
blockquote:after,
|
|
110
|
-
q:before,
|
|
111
|
-
q:after {
|
|
112
|
-
content: '';
|
|
113
|
-
content: none;
|
|
114
|
-
}
|
|
115
|
-
table {
|
|
116
|
-
border-collapse: collapse;
|
|
117
|
-
border-spacing: 0;
|
|
118
|
-
}
|
|
119
|
-
html,
|
|
120
|
-
body {
|
|
121
|
-
width: 100%;
|
|
122
|
-
height: 100%;
|
|
123
|
-
padding: 0;
|
|
124
|
-
margin: 0;
|
|
125
|
-
font-family: 'PingFang SC-Medium', 'PingFang SC-Regular', 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', 'Arial', 'sans-serif';
|
|
126
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import type { App, Plugin } from 'vue';
|
|
3
|
-
import { withReadonly } from './withReadonly';
|
|
4
|
-
|
|
5
|
-
// 👇 直接从 n-designv3 导入组件(构建时 external,不打包)
|
|
6
|
-
import {
|
|
7
|
-
Input,
|
|
8
|
-
InputNumber,
|
|
9
|
-
Textarea,
|
|
10
|
-
Select,
|
|
11
|
-
TreeSelect,
|
|
12
|
-
Cascader,
|
|
13
|
-
DatePicker,
|
|
14
|
-
TimePicker,
|
|
15
|
-
Radio,
|
|
16
|
-
Checkbox,
|
|
17
|
-
Switch,
|
|
18
|
-
Form,
|
|
19
|
-
} from 'n-designv3';
|
|
20
|
-
|
|
21
|
-
const COMPONENTS = {
|
|
22
|
-
Input,
|
|
23
|
-
InputNumber,
|
|
24
|
-
Textarea,
|
|
25
|
-
Select,
|
|
26
|
-
SelectOption: Select.Option,
|
|
27
|
-
TreeSelect,
|
|
28
|
-
Cascader,
|
|
29
|
-
DatePicker,
|
|
30
|
-
TimePicker,
|
|
31
|
-
Radio,
|
|
32
|
-
Checkbox,
|
|
33
|
-
RadioGroup: Radio.Group,
|
|
34
|
-
CheckboxGroup: Checkbox.Group,
|
|
35
|
-
Switch,
|
|
36
|
-
Form,
|
|
37
|
-
FormItem: Form.Item,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const NkReadonlyPlugin: Plugin = {
|
|
41
|
-
install(app: App) {
|
|
42
|
-
for (const [name, Comp] of Object.entries(COMPONENTS)) {
|
|
43
|
-
if (Comp) {
|
|
44
|
-
const ReadonlyComp = withReadonly(Comp);
|
|
45
|
-
app.component(`Nk${name}`, ReadonlyComp);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// 可选:导出 HOC 供用户按需使用
|
|
52
|
-
export { withReadonly };
|
package/src/types/env.d.ts
DELETED
package/src/withReadonly.tsx
DELETED
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
// composables/withReadonly.ts
|
|
2
|
-
import { defineComponent, h, computed, inject, provide, ref, watch, useAttrs, type PropType, type Component, type VNode, Ref } from 'vue';
|
|
3
|
-
|
|
4
|
-
type FormModelValue = string | number | boolean | any[] | null | undefined;
|
|
5
|
-
type ValueToLabelFn = (value: FormModelValue) => string;
|
|
6
|
-
|
|
7
|
-
// ========================
|
|
8
|
-
// 工具函数:提取静态文本(用于单个 checkbox/radio/select )
|
|
9
|
-
// ========================
|
|
10
|
-
function extractStaticText(vnode: any): string {
|
|
11
|
-
if (vnode == null) return '';
|
|
12
|
-
if (typeof vnode === 'string') return vnode;
|
|
13
|
-
if (Array.isArray(vnode)) return vnode.map(extractStaticText).join('');
|
|
14
|
-
if (typeof vnode === 'object') {
|
|
15
|
-
if (typeof vnode.children === 'string') {
|
|
16
|
-
return vnode.children;
|
|
17
|
-
}
|
|
18
|
-
// 处理动态插槽内容
|
|
19
|
-
if (vnode?.default && typeof vnode.default === 'function') {
|
|
20
|
-
const dynamicContent = vnode.default();
|
|
21
|
-
if (Array.isArray(dynamicContent)) {
|
|
22
|
-
return extractStaticText(dynamicContent);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
if (vnode.children) {
|
|
26
|
-
extractStaticText(vnode.children);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return '';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ========================
|
|
33
|
-
// 从插槽中提取 value → label 映射(支持 n-option / n-radio / n-checkbox / n-select)
|
|
34
|
-
// ========================
|
|
35
|
-
function collectOptionLabelMapFromSlots(vnodes: VNode | VNode[] | undefined, fieldNames: Record<string, string> = {}): Map<any, string> {
|
|
36
|
-
const labelMap = new Map<any, string>();
|
|
37
|
-
const { value: valKey = 'value', label: labelKey = 'label' } = fieldNames;
|
|
38
|
-
|
|
39
|
-
if (!vnodes) return labelMap;
|
|
40
|
-
|
|
41
|
-
const nodes = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
42
|
-
|
|
43
|
-
for (const vnode of nodes) {
|
|
44
|
-
if (!vnode || typeof vnode !== 'object') continue;
|
|
45
|
-
|
|
46
|
-
// 处理组件节点
|
|
47
|
-
if (vnode.type && (typeof vnode.type === 'object' || typeof vnode.type === 'function')) {
|
|
48
|
-
let compName = (vnode.type as any).name?.toLowerCase() || '';
|
|
49
|
-
if (typeof vnode.type === 'function') {
|
|
50
|
-
compName = (vnode.type as any).displayName?.toLowerCase() || '';
|
|
51
|
-
} else if (typeof vnode.type === 'string') {
|
|
52
|
-
compName = (vnode.type as any)?.toLowerCase() || '';
|
|
53
|
-
}
|
|
54
|
-
if (/option|radio|checkbox/i.test(compName)) {
|
|
55
|
-
const props = vnode.props || {};
|
|
56
|
-
const value = props[valKey] ?? props.value;
|
|
57
|
-
const labelText = props[labelKey] ?? props.label;
|
|
58
|
-
if (value != null) {
|
|
59
|
-
const label = extractStaticText(vnode.children) || labelText || String(value);
|
|
60
|
-
labelMap.set(value, label);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 递归子节点
|
|
66
|
-
if (vnode.children) {
|
|
67
|
-
const childMap = collectOptionLabelMapFromSlots(vnode.children as any, fieldNames);
|
|
68
|
-
childMap.forEach((label, val) => {
|
|
69
|
-
if (!labelMap.has(val)) labelMap.set(val, label);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return labelMap;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ========================
|
|
78
|
-
// Cascader: 递归查找 label 路径
|
|
79
|
-
// ========================
|
|
80
|
-
function findCascaderLabels(options: any[], value: any[], fieldNames: Record<string, string>): string[] {
|
|
81
|
-
const { value: valKey = 'value', label: labelKey = 'label', children: childrenKey = 'children' } = fieldNames;
|
|
82
|
-
const labels: string[] = [];
|
|
83
|
-
let current = options;
|
|
84
|
-
|
|
85
|
-
for (const v of value) {
|
|
86
|
-
const node = current.find((item: any) => item[valKey] === v);
|
|
87
|
-
if (node) {
|
|
88
|
-
labels.push(node[labelKey] || node[valKey] || String(v));
|
|
89
|
-
current = node[childrenKey] || [];
|
|
90
|
-
} else {
|
|
91
|
-
labels.push(String(v));
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return labels;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ========================
|
|
99
|
-
// TreeSelect: 从 treeData 递归查找 label
|
|
100
|
-
// ========================
|
|
101
|
-
function findTreeSelectLabels(treeData: any[], value: any, fieldNames: Record<string, string>): string[] {
|
|
102
|
-
const { value: valKey = 'value', label: labelKey = 'title', children: childrenKey = 'children' } = fieldNames;
|
|
103
|
-
const labels: string[] = [];
|
|
104
|
-
const targetValues = new Set(Array.isArray(value) ? value : [value]);
|
|
105
|
-
|
|
106
|
-
function dfs(nodes: any[]) {
|
|
107
|
-
for (const node of nodes) {
|
|
108
|
-
if (targetValues.has(node[valKey])) {
|
|
109
|
-
labels.push(node[labelKey] || node[valKey]);
|
|
110
|
-
}
|
|
111
|
-
if (node[childrenKey]) {
|
|
112
|
-
dfs(node[childrenKey]);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
dfs(treeData);
|
|
118
|
-
return labels;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ========================
|
|
122
|
-
// 获取只读显示文本
|
|
123
|
-
// ========================
|
|
124
|
-
function getDisplayText({
|
|
125
|
-
modelValue,
|
|
126
|
-
options,
|
|
127
|
-
treeData,
|
|
128
|
-
valueToLabel,
|
|
129
|
-
fieldNames,
|
|
130
|
-
slots,
|
|
131
|
-
isSelect,
|
|
132
|
-
isRadioGroup,
|
|
133
|
-
isCheckboxGroup,
|
|
134
|
-
isCheckbox,
|
|
135
|
-
isRadio,
|
|
136
|
-
isCascader,
|
|
137
|
-
isTreeSelect,
|
|
138
|
-
isSwitch,
|
|
139
|
-
attrs,
|
|
140
|
-
emptyText,
|
|
141
|
-
}: {
|
|
142
|
-
modelValue: FormModelValue;
|
|
143
|
-
options?: any[];
|
|
144
|
-
treeData?: any[];
|
|
145
|
-
valueToLabel?: ValueToLabelFn;
|
|
146
|
-
fieldNames: Record<string, string>;
|
|
147
|
-
slots?: any;
|
|
148
|
-
isSelect: boolean;
|
|
149
|
-
isRadioGroup: boolean;
|
|
150
|
-
isCheckboxGroup: boolean;
|
|
151
|
-
isCheckbox: boolean;
|
|
152
|
-
isRadio: boolean;
|
|
153
|
-
isCascader: boolean;
|
|
154
|
-
isTreeSelect: boolean;
|
|
155
|
-
isSwitch: boolean;
|
|
156
|
-
attrs: Record<string, any>;
|
|
157
|
-
emptyText: string;
|
|
158
|
-
}): string {
|
|
159
|
-
// 1. 自定义映射
|
|
160
|
-
if (valueToLabel) {
|
|
161
|
-
try {
|
|
162
|
-
return valueToLabel(modelValue) || emptyText;
|
|
163
|
-
} catch (e) {
|
|
164
|
-
console.warn('[ReadonlyHOC] valueToLabel error', e);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// 2. Switch 特殊处理
|
|
169
|
-
if (isSwitch) {
|
|
170
|
-
const checkedVal = attrs.checkedValue ?? attrs['checked-value'] ?? true;
|
|
171
|
-
const uncheckedVal = attrs.uncheckedValue ?? attrs['un-checked-value'] ?? false;
|
|
172
|
-
if (modelValue === checkedVal) return attrs.checkedChildren ?? attrs['checked-children'] ?? '开启';
|
|
173
|
-
if (modelValue === uncheckedVal) return attrs.uncheckedChildren ?? attrs['un-checked-children'] ?? '关闭';
|
|
174
|
-
return String(modelValue || emptyText);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 3. Cascader
|
|
178
|
-
if (isCascader && Array.isArray(modelValue) && options?.length) {
|
|
179
|
-
return findCascaderLabels(options, modelValue, fieldNames).join(' / ') || emptyText;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// 4. TreeSelect
|
|
183
|
-
if (isTreeSelect && treeData?.length) {
|
|
184
|
-
return findTreeSelectLabels(treeData, modelValue, fieldNames).join(', ') || emptyText;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// 5. 动态 options(Select / RadioGroup / CheckboxGroup)
|
|
188
|
-
if (options?.length) {
|
|
189
|
-
const valKey = fieldNames.value || 'value';
|
|
190
|
-
const labelKey = fieldNames.label || 'label';
|
|
191
|
-
const getLabel = (val: any) => {
|
|
192
|
-
const opt = options.find(item => item[valKey] === val);
|
|
193
|
-
return opt ? opt[labelKey] || opt[valKey] : val === undefined || val === null ? '' : String(val);
|
|
194
|
-
};
|
|
195
|
-
if (Array.isArray(modelValue)) {
|
|
196
|
-
return modelValue.map(getLabel).join(', ') || emptyText;
|
|
197
|
-
} else {
|
|
198
|
-
return getLabel(modelValue) || emptyText;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 6. 静态插槽 fallback(Select / RadioGroup / CheckboxGroup)
|
|
203
|
-
else if ((isSelect || isRadioGroup || isCheckboxGroup) && slots?.default) {
|
|
204
|
-
try {
|
|
205
|
-
const slotContent = slots.default();
|
|
206
|
-
|
|
207
|
-
const labelMap = collectOptionLabelMapFromSlots(slotContent, fieldNames);
|
|
208
|
-
if (labelMap.size > 0) {
|
|
209
|
-
const getLabel = (val: any) => (val === undefined || val === null ? '' : labelMap.get(val) || String(val));
|
|
210
|
-
if (Array.isArray(modelValue)) {
|
|
211
|
-
return modelValue.map(getLabel).join(', ') || emptyText;
|
|
212
|
-
} else {
|
|
213
|
-
return getLabel(modelValue) || emptyText;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
} catch (e) {
|
|
217
|
-
console.warn('[ReadonlyHOC] Failed to parse slot options', e);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// 7. 单个 Checkbox / Radio 静态文本兜底
|
|
222
|
-
else if (slots?.default && (isCheckbox || isRadio) && typeof modelValue === 'boolean') {
|
|
223
|
-
try {
|
|
224
|
-
const content = slots.default();
|
|
225
|
-
const text = extractStaticText(content);
|
|
226
|
-
if (text) return text;
|
|
227
|
-
} catch (e) {
|
|
228
|
-
/* ignore */
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 8. 最终兜底
|
|
233
|
-
if (modelValue == null || modelValue === '') return emptyText;
|
|
234
|
-
return Array.isArray(modelValue) ? modelValue.join(', ') : String(modelValue);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// ========================
|
|
238
|
-
// 高阶组件
|
|
239
|
-
// ========================
|
|
240
|
-
export function withReadonly(BaseComponent: Component) {
|
|
241
|
-
const baseName = BaseComponent.name || '';
|
|
242
|
-
const componentName = baseName.toLowerCase();
|
|
243
|
-
|
|
244
|
-
const isSelect = /select/i.test(componentName) && !/tree|cascader/i.test(componentName);
|
|
245
|
-
const isRadio = /radio/i.test(componentName) && !/group/i.test(componentName);
|
|
246
|
-
const isRadioGroup = /radio.*group/i.test(componentName);
|
|
247
|
-
const isCheckbox = /checkbox/i.test(componentName) && !/group/i.test(componentName);
|
|
248
|
-
const isCheckboxGroup = /checkbox.*group/i.test(componentName);
|
|
249
|
-
const isSwitch = /switch/i.test(componentName);
|
|
250
|
-
const isCascader = /cascader/i.test(componentName);
|
|
251
|
-
const isTreeSelect = /tree.*select/i.test(componentName);
|
|
252
|
-
|
|
253
|
-
const isCheckType = isCheckbox || isSwitch || isRadio;
|
|
254
|
-
const nativeProp = isCheckType ? 'checked' : 'value';
|
|
255
|
-
const updateEvent = `update:${nativeProp}`;
|
|
256
|
-
|
|
257
|
-
const isForm = /form$/i.test(componentName);
|
|
258
|
-
const isFormItem = /form.*item/i.test(componentName);
|
|
259
|
-
|
|
260
|
-
return defineComponent({
|
|
261
|
-
name: baseName,
|
|
262
|
-
inheritAttrs: false,
|
|
263
|
-
props: {
|
|
264
|
-
modelValue: { type: [String, Number, Boolean, Array, Object] as PropType<FormModelValue>, default: undefined },
|
|
265
|
-
[nativeProp]: { type: [String, Number, Boolean, Array, Object] as PropType<FormModelValue>, default: undefined },
|
|
266
|
-
readonly: { type: Boolean, default: undefined },
|
|
267
|
-
emptyText: { type: String, default: '--' },
|
|
268
|
-
valueToLabel: { type: Function as PropType<ValueToLabelFn>, default: null },
|
|
269
|
-
},
|
|
270
|
-
emits: ['update:modelValue', updateEvent],
|
|
271
|
-
setup(props, { emit, slots, expose }) {
|
|
272
|
-
const globalReadonly = inject<Ref<boolean>>('nkReadonly', ref(false));
|
|
273
|
-
const isReadonly = computed(() => props.readonly ?? globalReadonly.value);
|
|
274
|
-
const formReadonly = computed(() => props.readonly ?? globalReadonly.value);
|
|
275
|
-
|
|
276
|
-
if (isForm) provide('nkReadonly', formReadonly);
|
|
277
|
-
|
|
278
|
-
const finalValue = computed(() => (props[nativeProp] !== undefined ? props[nativeProp] : props.modelValue));
|
|
279
|
-
|
|
280
|
-
const emitUpdate = (val: FormModelValue) => {
|
|
281
|
-
emit('update:modelValue', val);
|
|
282
|
-
emit(updateEvent, val);
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
// ✅ 使用 useAttrs() 保证响应式
|
|
286
|
-
const attrs = useAttrs();
|
|
287
|
-
|
|
288
|
-
const displayText = computed(() => {
|
|
289
|
-
if (!isReadonly.value) return '';
|
|
290
|
-
|
|
291
|
-
const fieldNames = attrs.fieldNames ?? attrs['field-names'] ?? {};
|
|
292
|
-
const options = attrs.options as any[] | undefined;
|
|
293
|
-
const treeData = (attrs.treeData ?? attrs['tree-data']) as any[] | undefined;
|
|
294
|
-
|
|
295
|
-
return getDisplayText({
|
|
296
|
-
modelValue: finalValue.value as FormModelValue,
|
|
297
|
-
options,
|
|
298
|
-
treeData,
|
|
299
|
-
valueToLabel: props.valueToLabel as ValueToLabelFn,
|
|
300
|
-
fieldNames: fieldNames as Record<string, string>,
|
|
301
|
-
slots,
|
|
302
|
-
isSelect,
|
|
303
|
-
isRadioGroup,
|
|
304
|
-
isCheckboxGroup,
|
|
305
|
-
isCheckbox,
|
|
306
|
-
isRadio,
|
|
307
|
-
isCascader,
|
|
308
|
-
isTreeSelect,
|
|
309
|
-
isSwitch,
|
|
310
|
-
attrs,
|
|
311
|
-
emptyText: props.emptyText as string,
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
// ⚠️ 开发警告
|
|
316
|
-
if (import.meta?.env?.DEV) {
|
|
317
|
-
watch(
|
|
318
|
-
() => isReadonly.value,
|
|
319
|
-
readonly => {
|
|
320
|
-
if (
|
|
321
|
-
readonly &&
|
|
322
|
-
!attrs.options &&
|
|
323
|
-
!attrs.treeData &&
|
|
324
|
-
!props.valueToLabel &&
|
|
325
|
-
slots?.default &&
|
|
326
|
-
(isSelect || isRadioGroup || isCheckboxGroup || isCascader || isTreeSelect)
|
|
327
|
-
) {
|
|
328
|
-
console.warn(
|
|
329
|
-
`[ReadonlyHOC] Detected slot-only usage for ${baseName} in readonly mode. ` +
|
|
330
|
-
`This works for static content, but will fail for async/dynamic data. ` +
|
|
331
|
-
`✅ Recommended: Use \`options\` or \`treeData\` prop instead.`
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
{ immediate: true }
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const readonlyStyle = {
|
|
340
|
-
lineHeight: '32px',
|
|
341
|
-
padding: '0 6px',
|
|
342
|
-
display: 'inline-block',
|
|
343
|
-
minHeight: '32px',
|
|
344
|
-
border: 'none',
|
|
345
|
-
backgroundColor: 'none',
|
|
346
|
-
cursor: 'default',
|
|
347
|
-
wordBreak: 'break-all',
|
|
348
|
-
color: 'rgba(0, 0, 0, 0.65)',
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
const formRef = ref<any>();
|
|
352
|
-
if (isForm) {
|
|
353
|
-
expose({
|
|
354
|
-
validate: () => (isReadonly.value ? Promise.resolve({}) : formRef.value?.validate?.()),
|
|
355
|
-
validateFields: (names?: string[]) => (isReadonly.value ? Promise.resolve({}) : formRef.value?.validateFields?.(names)),
|
|
356
|
-
resetFields: (names?: string[]) => !isReadonly.value && formRef.value?.resetFields?.(names),
|
|
357
|
-
clearValidate: (names?: string[]) => !isReadonly.value && formRef.value?.clearValidate?.(names),
|
|
358
|
-
scrollToField: (name: string) => !isReadonly.value && formRef.value?.scrollToField?.(name),
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return () => {
|
|
363
|
-
if (isFormItem) {
|
|
364
|
-
return h(
|
|
365
|
-
BaseComponent,
|
|
366
|
-
{
|
|
367
|
-
...attrs,
|
|
368
|
-
style: { ...(attrs.style || {}), marginBottom: isReadonly.value ? '10px' : undefined },
|
|
369
|
-
},
|
|
370
|
-
slots
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (isForm) {
|
|
375
|
-
return h(
|
|
376
|
-
BaseComponent,
|
|
377
|
-
{
|
|
378
|
-
...attrs,
|
|
379
|
-
rules: formReadonly.value ? null : attrs.rules,
|
|
380
|
-
disabled: formReadonly.value ? true : attrs.disabled,
|
|
381
|
-
ref: formRef,
|
|
382
|
-
},
|
|
383
|
-
slots
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (isReadonly.value) {
|
|
388
|
-
return h(
|
|
389
|
-
'span',
|
|
390
|
-
{
|
|
391
|
-
class: 'nk-readonly-wrapper',
|
|
392
|
-
style: readonlyStyle,
|
|
393
|
-
role: 'text',
|
|
394
|
-
tabindex: 0,
|
|
395
|
-
'aria-readonly': 'true',
|
|
396
|
-
},
|
|
397
|
-
displayText.value
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return h(
|
|
402
|
-
BaseComponent,
|
|
403
|
-
{
|
|
404
|
-
...attrs,
|
|
405
|
-
[nativeProp]: finalValue.value,
|
|
406
|
-
[`onUpdate:${nativeProp}`]: emitUpdate,
|
|
407
|
-
ref: isForm ? formRef : undefined,
|
|
408
|
-
},
|
|
409
|
-
slots
|
|
410
|
-
);
|
|
411
|
-
};
|
|
412
|
-
},
|
|
413
|
-
});
|
|
414
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"useDefineForClassFields": true,
|
|
5
|
-
"module": "ESNext",
|
|
6
|
-
"lib": ["ES2020", "DOM"],
|
|
7
|
-
"moduleResolution": "bundler",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"jsx": "preserve",
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"declaration": true,
|
|
13
|
-
"declarationDir": "./dist",
|
|
14
|
-
"outDir": "./dist",
|
|
15
|
-
"allowSyntheticDefaultImports": true,
|
|
16
|
-
"forceConsistentCasingInFileNames": true
|
|
17
|
-
},
|
|
18
|
-
"include": ["src"]
|
|
19
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
2
|
-
import vue from '@vitejs/plugin-vue'
|
|
3
|
-
import { resolve } from 'path'
|
|
4
|
-
|
|
5
|
-
export default defineConfig({
|
|
6
|
-
root: 'playground',
|
|
7
|
-
plugins: [vue()],
|
|
8
|
-
resolve: {
|
|
9
|
-
alias: {
|
|
10
|
-
'@': resolve(__dirname, 'src'),
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
server: {
|
|
14
|
-
host: '0.0.0.0',
|
|
15
|
-
port: 5200,
|
|
16
|
-
},
|
|
17
|
-
})
|
package/vite.config.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { resolve } from 'path';
|
|
2
|
-
import { defineConfig } from 'vite';
|
|
3
|
-
import dts from 'vite-plugin-dts';
|
|
4
|
-
|
|
5
|
-
export default defineConfig({
|
|
6
|
-
build: {
|
|
7
|
-
lib: {
|
|
8
|
-
entry: resolve(__dirname, 'src/index.ts'),
|
|
9
|
-
name: 'NDesignReadolyPlugin',
|
|
10
|
-
fileName: (format) => `index.${format}.js`,
|
|
11
|
-
formats: ['es', 'cjs']
|
|
12
|
-
},
|
|
13
|
-
rollupOptions: {
|
|
14
|
-
// external 排除 vue 和 n-designv3
|
|
15
|
-
external: ['vue','n-designv3'],
|
|
16
|
-
output: { globals: { vue: 'Vue' } }
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
plugins: [dts({ insertTypesEntry: true })]
|
|
20
|
-
});
|