diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..a4aea535 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +element-plus-admin-doc.cn diff --git a/README.html b/README.html new file mode 100644 index 00000000..207908e3 --- /dev/null +++ b/README.html @@ -0,0 +1,39 @@ + + + + + + vue-element-plus-admin-doc | vue-element-plus-admin + + + + + + + + + + + + + + + + +

vue-element-plus-admin-doc

如何本地开发

# 克隆本仓库
+$ git clone https://github.com/kailong321200875/vue-element-plus-admin-doc.git
+
+# 安装依赖
+$ yarn
+
+# 启动开发服务器
+$ npm run dev
+
+ + + + \ No newline at end of file diff --git a/assets/Home.3ecc1b19.js b/assets/Home.3ecc1b19.js new file mode 100644 index 00000000..992351a7 --- /dev/null +++ b/assets/Home.3ecc1b19.js @@ -0,0 +1 @@ +import{f as e,u as a,g as t,h as s,i as l,o as i,c as o,b as r,e as n,t as c,_ as v,p as u,j as f,F as d,r as m,w as p,k as h,l as k}from"./app.7e863e47.js";u("data-v-e065f044");const x={key:0,class:"home-hero"},y={key:0,class:"figure"},g={key:1,id:"main-title",class:"title"},$={key:2,class:"description"};f();var _=e({expose:[],setup(e){const u=a(),f=t(),d=s((()=>f.value.heroImage||m.value||h.value||_.value)),m=s((()=>null!==f.value.heroText)),p=s((()=>f.value.heroText||u.value.title)),h=s((()=>null!==f.value.tagline)),k=s((()=>f.value.tagline||u.value.description)),_=s((()=>f.value.actionLink&&f.value.actionText)),I=s((()=>f.value.altActionLink&&f.value.altActionText));return(e,a)=>l(d)?(i(),o("header",x,[e.$frontmatter.heroImage?(i(),o("figure",y,[r("img",{class:"image",src:e.$withBase(e.$frontmatter.heroImage),alt:e.$frontmatter.heroAlt},null,8,["src","alt"])])):n("v-if",!0),l(m)?(i(),o("h1",g,c(l(p)),1)):n("v-if",!0),l(h)?(i(),o("p",$,c(l(k)),1)):n("v-if",!0),l(_)?(i(),o(v,{key:3,item:{link:l(f).actionLink,text:l(f).actionText},class:"action"},null,8,["item"])):n("v-if",!0),l(I)?(i(),o(v,{key:4,item:{link:l(f).altActionLink,text:l(f).altActionText},class:"action alt"},null,8,["item"])):n("v-if",!0)])):n("v-if",!0)}});_.__scopeId="data-v-e065f044",u("data-v-9c9c2344");const I={key:0,class:"home-features"},T={class:"wrapper"},A={class:"container"},L={class:"features"},b={key:0,class:"title"},w={key:1,class:"details"};f();var j=e({expose:[],setup(e){const a=t(),v=s((()=>a.value.features&&a.value.features.length>0)),u=s((()=>a.value.features?a.value.features:[]));return(e,a)=>l(v)?(i(),o("div",I,[r("div",T,[r("div",A,[r("div",L,[(i(!0),o(d,null,m(l(u),((e,a)=>(i(),o("section",{key:a,class:"feature"},[e.title?(i(),o("h2",b,c(e.title),1)):n("v-if",!0),e.details?(i(),o("p",w,c(e.details),1)):n("v-if",!0)])))),128))])])])])):n("v-if",!0)}});j.__scopeId="data-v-9c9c2344";const B={},C=p();u("data-v-44324124");const F={key:0,class:"footer"},q={class:"container"},z={class:"text"};f();const D=C(((e,a)=>e.$frontmatter.footer?(i(),o("footer",F,[r("div",q,[r("p",z,c(e.$frontmatter.footer),1)])])):n("v-if",!0)));B.render=D,B.__scopeId="data-v-44324124",u("data-v-1fd43058");const E={class:"home","aria-labelledby":"main-title"},G={class:"home-content"};f();var H=e({expose:[],setup:e=>(e,a)=>{const t=h("Content");return i(),o("main",E,[r(_),k(e.$slots,"hero",{},void 0,!0),r(j),r("div",G,[r(t)]),k(e.$slots,"features",{},void 0,!0),r(B),k(e.$slots,"footer",{},void 0,!0)])}});H.__scopeId="data-v-1fd43058";export default H; diff --git a/assets/README.md.6bedca9d.js b/assets/README.md.6bedca9d.js new file mode 100644 index 00000000..c4778018 --- /dev/null +++ b/assets/README.md.6bedca9d.js @@ -0,0 +1 @@ +import{o as e,c as n,a}from"./app.7e863e47.js";const s='{"title":"vue-element-plus-admin-doc","description":"","frontmatter":{},"headers":[{"level":2,"title":"如何本地开发","slug":"如何本地开发"}],"relativePath":"README.md","lastUpdated":1718353615647}',t={},o=a('

vue-element-plus-admin-doc

如何本地开发

# 克隆本仓库\n$ git clone https://github.com/kailong321200875/vue-element-plus-admin-doc.git\n\n# 安装依赖\n$ yarn\n\n# 启动开发服务器\n$ npm run dev\n
',3);t.render=function(a,s,t,l,d,c){return e(),n("div",null,[o])};export default t;export{s as __pageData}; diff --git a/assets/README.md.6bedca9d.lean.js b/assets/README.md.6bedca9d.lean.js new file mode 100644 index 00000000..dc5754d9 --- /dev/null +++ b/assets/README.md.6bedca9d.lean.js @@ -0,0 +1 @@ +import{o as e,c as n,a}from"./app.7e863e47.js";const s='{"title":"vue-element-plus-admin-doc","description":"","frontmatter":{},"headers":[{"level":2,"title":"如何本地开发","slug":"如何本地开发"}],"relativePath":"README.md","lastUpdated":1718353615647}',t={},o=a('',3);t.render=function(a,s,t,l,d,c){return e(),n("div",null,[o])};export default t;export{s as __pageData}; diff --git a/assets/app.7e863e47.js b/assets/app.7e863e47.js new file mode 100644 index 00000000..b19912de --- /dev/null +++ b/assets/app.7e863e47.js @@ -0,0 +1,15 @@ +var e=Object.defineProperty,t=Object.defineProperties,n=Object.getOwnPropertyDescriptors,o=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,l=Object.prototype.propertyIsEnumerable,s=(t,n,o)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[n]=o,i=(e,t)=>{for(var n in t||(t={}))r.call(t,n)&&s(e,n,t[n]);if(o)for(var n of o(t))l.call(t,n)&&s(e,n,t[n]);return e},c=(e,o)=>t(e,n(o));function a(e,t){const n=Object.create(null),o=e.split(",");for(let r=0;r!!n[e.toLowerCase()]:e=>!!n[e]}const u=a("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt"),d=a("itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly");function p(e){if(T(e)){const t={};for(let n=0;n{if(e){const n=e.split(h);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function v(e){let t="";if(P(e))t=e;else if(T(e))for(let n=0;nnull==e?"":j(e)?JSON.stringify(e,b,2):String(e),b=(e,t)=>A(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:I(t)?{[`Set(${t.size})`]:[...t.values()]}:!j(t)||T(t)||V(t)?t:String(t),y={},x=[],k=()=>{},_=()=>!1,w=/^on[^a-z]/,C=e=>w.test(e),S=e=>e.startsWith("onUpdate:"),$=Object.assign,E=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},O=Object.prototype.hasOwnProperty,L=(e,t)=>O.call(e,t),T=Array.isArray,A=e=>"[object Map]"===U(e),I=e=>"[object Set]"===U(e),M=e=>"function"==typeof e,P=e=>"string"==typeof e,R=e=>"symbol"==typeof e,j=e=>null!==e&&"object"==typeof e,F=e=>j(e)&&M(e.then)&&M(e.catch),N=Object.prototype.toString,U=e=>N.call(e),V=e=>"[object Object]"===U(e),B=e=>P(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,z=a(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),D=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},W=/-(\w)/g,H=D((e=>e.replace(W,((e,t)=>t?t.toUpperCase():"")))),q=/\B([A-Z])/g,G=D((e=>e.replace(q,"-$1").toLowerCase())),K=D((e=>e.charAt(0).toUpperCase()+e.slice(1))),J=D((e=>e?`on${K(e)}`:"")),Y=(e,t)=>e!==t&&(e==e||t==t),X=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},Z=e=>{const t=parseFloat(e);return isNaN(t)?e:t},ee=new WeakMap,te=[];let ne;const oe=Symbol(""),re=Symbol("");function le(e,t=y){(function(e){return e&&!0===e._isEffect})(e)&&(e=e.raw);const n=function(e,t){const n=function(){if(!n.active)return e();if(!te.includes(n)){ce(n);try{return ue.push(ae),ae=!0,te.push(n),ne=n,e()}finally{te.pop(),pe(),ne=te[te.length-1]}}};return n.id=ie++,n.allowRecurse=!!t.allowRecurse,n._isEffect=!0,n.active=!0,n.raw=e,n.deps=[],n.options=t,n}(e,t);return t.lazy||n(),n}function se(e){e.active&&(ce(e),e.options.onStop&&e.options.onStop(),e.active=!1)}let ie=0;function ce(e){const{deps:t}=e;if(t.length){for(let n=0;n{e&&e.forEach((e=>{(e!==ne||e.allowRecurse)&&i.add(e)}))};if("clear"===t)s.forEach(c);else if("length"===n&&T(e))s.forEach(((e,t)=>{("length"===t||t>=o)&&c(e)}));else switch(void 0!==n&&c(s.get(n)),t){case"add":T(e)?B(n)&&c(s.get("length")):(c(s.get(oe)),A(e)&&c(s.get(re)));break;case"delete":T(e)||(c(s.get(oe)),A(e)&&c(s.get(re)));break;case"set":A(e)&&c(s.get(oe))}i.forEach((e=>{e.options.scheduler?e.options.scheduler(e):e()}))}const me=a("__proto__,__v_isRef,__isVue"),ve=new Set(Object.getOwnPropertyNames(Symbol).map((e=>Symbol[e])).filter(R)),ge=_e(),be=_e(!1,!0),ye=_e(!0),xe=_e(!0,!0),ke={};function _e(e=!1,t=!1){return function(n,o,r){if("__v_isReactive"===o)return!e;if("__v_isReadonly"===o)return e;if("__v_raw"===o&&r===(e?t?Qe:Xe:t?Ye:Je).get(n))return n;const l=T(n);if(!e&&l&&L(ke,o))return Reflect.get(ke,o,r);const s=Reflect.get(n,o,r);if(R(o)?ve.has(o):me(o))return s;if(e||fe(n,0,o),t)return s;if(at(s)){return!l||!B(o)?s.value:s}return j(s)?e?tt(s):et(s):s}}["includes","indexOf","lastIndexOf"].forEach((e=>{const t=Array.prototype[e];ke[e]=function(...e){const n=st(this);for(let t=0,r=this.length;t{const t=Array.prototype[e];ke[e]=function(...e){de();const n=t.apply(this,e);return pe(),n}}));function we(e=!1){return function(t,n,o,r){let l=t[n];if(!e&&(o=st(o),l=st(l),!T(t)&&at(l)&&!at(o)))return l.value=o,!0;const s=T(t)&&B(n)?Number(n)!0,deleteProperty:(e,t)=>!0},$e=$({},Ce,{get:be,set:we(!0)});$({},Se,{get:xe});const Ee=e=>j(e)?et(e):e,Oe=e=>j(e)?tt(e):e,Le=e=>e,Te=e=>Reflect.getPrototypeOf(e);function Ae(e,t,n=!1,o=!1){const r=st(e=e.__v_raw),l=st(t);t!==l&&!n&&fe(r,0,t),!n&&fe(r,0,l);const{has:s}=Te(r),i=o?Le:n?Oe:Ee;return s.call(r,t)?i(e.get(t)):s.call(r,l)?i(e.get(l)):void(e!==r&&e.get(t))}function Ie(e,t=!1){const n=this.__v_raw,o=st(n),r=st(e);return e!==r&&!t&&fe(o,0,e),!t&&fe(o,0,r),e===r?n.has(e):n.has(e)||n.has(r)}function Me(e,t=!1){return e=e.__v_raw,!t&&fe(st(e),0,oe),Reflect.get(e,"size",e)}function Pe(e){e=st(e);const t=st(this);return Te(t).has.call(t,e)||(t.add(e),he(t,"add",e,e)),this}function Re(e,t){t=st(t);const n=st(this),{has:o,get:r}=Te(n);let l=o.call(n,e);l||(e=st(e),l=o.call(n,e));const s=r.call(n,e);return n.set(e,t),l?Y(t,s)&&he(n,"set",e,t):he(n,"add",e,t),this}function je(e){const t=st(this),{has:n,get:o}=Te(t);let r=n.call(t,e);r||(e=st(e),r=n.call(t,e)),o&&o.call(t,e);const l=t.delete(e);return r&&he(t,"delete",e,void 0),l}function Fe(){const e=st(this),t=0!==e.size,n=e.clear();return t&&he(e,"clear",void 0,void 0),n}function Ne(e,t){return function(n,o){const r=this,l=r.__v_raw,s=st(l),i=t?Le:e?Oe:Ee;return!e&&fe(s,0,oe),l.forEach(((e,t)=>n.call(o,i(e),i(t),r)))}}function Ue(e,t,n){return function(...o){const r=this.__v_raw,l=st(r),s=A(l),i="entries"===e||e===Symbol.iterator&&s,c="keys"===e&&s,a=r[e](...o),u=n?Le:t?Oe:Ee;return!t&&fe(l,0,c?re:oe),{next(){const{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:i?[u(e[0]),u(e[1])]:u(e),done:t}},[Symbol.iterator](){return this}}}}function Ve(e){return function(...t){return"delete"!==e&&this}}const Be={get(e){return Ae(this,e)},get size(){return Me(this)},has:Ie,add:Pe,set:Re,delete:je,clear:Fe,forEach:Ne(!1,!1)},ze={get(e){return Ae(this,e,!1,!0)},get size(){return Me(this)},has:Ie,add:Pe,set:Re,delete:je,clear:Fe,forEach:Ne(!1,!0)},De={get(e){return Ae(this,e,!0)},get size(){return Me(this,!0)},has(e){return Ie.call(this,e,!0)},add:Ve("add"),set:Ve("set"),delete:Ve("delete"),clear:Ve("clear"),forEach:Ne(!0,!1)},We={get(e){return Ae(this,e,!0,!0)},get size(){return Me(this,!0)},has(e){return Ie.call(this,e,!0)},add:Ve("add"),set:Ve("set"),delete:Ve("delete"),clear:Ve("clear"),forEach:Ne(!0,!0)};function He(e,t){const n=t?e?We:ze:e?De:Be;return(t,o,r)=>"__v_isReactive"===o?!e:"__v_isReadonly"===o?e:"__v_raw"===o?t:Reflect.get(L(n,o)&&o in t?n:t,o,r)}["keys","values","entries",Symbol.iterator].forEach((e=>{Be[e]=Ue(e,!1,!1),De[e]=Ue(e,!0,!1),ze[e]=Ue(e,!1,!0),We[e]=Ue(e,!0,!0)}));const qe={get:He(!1,!1)},Ge={get:He(!1,!0)},Ke={get:He(!0,!1)},Je=new WeakMap,Ye=new WeakMap,Xe=new WeakMap,Qe=new WeakMap;function Ze(e){return e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}((e=>U(e).slice(8,-1))(e))}function et(e){return e&&e.__v_isReadonly?e:nt(e,!1,Ce,qe,Je)}function tt(e){return nt(e,!0,Se,Ke,Xe)}function nt(e,t,n,o,r){if(!j(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const l=r.get(e);if(l)return l;const s=Ze(e);if(0===s)return e;const i=new Proxy(e,2===s?o:n);return r.set(e,i),i}function ot(e){return rt(e)?ot(e.__v_raw):!(!e||!e.__v_isReactive)}function rt(e){return!(!e||!e.__v_isReadonly)}function lt(e){return ot(e)||rt(e)}function st(e){return e&&st(e.__v_raw)||e}function it(e){return Q(e,"__v_skip",!0),e}const ct=e=>j(e)?et(e):e;function at(e){return Boolean(e&&!0===e.__v_isRef)}function ut(e){return function(e,t=!1){if(at(e))return e;return new dt(e,t)}(e)}class dt{constructor(e,t){this._rawValue=e,this._shallow=t,this.__v_isRef=!0,this._value=t?e:ct(e)}get value(){return fe(st(this),0,"value"),this._value}set value(e){Y(st(e),this._rawValue)&&(this._rawValue=e,this._value=this._shallow?e:ct(e),he(st(this),"set","value",e))}}function pt(e){return at(e)?e.value:e}const ft={get:(e,t,n)=>pt(Reflect.get(e,t,n)),set:(e,t,n,o)=>{const r=e[t];return at(r)&&!at(n)?(r.value=n,!0):Reflect.set(e,t,n,o)}};function ht(e){return ot(e)?e:new Proxy(e,ft)}function mt(e){const t=T(e)?new Array(e.length):{};for(const n in e)t[n]=gt(e,n);return t}class vt{constructor(e,t){this._object=e,this._key=t,this.__v_isRef=!0}get value(){return this._object[this._key]}set value(e){this._object[this._key]=e}}function gt(e,t){return at(e[t])?e[t]:new vt(e,t)}class bt{constructor(e,t,n){this._setter=t,this._dirty=!0,this.__v_isRef=!0,this.effect=le(e,{lazy:!0,scheduler:()=>{this._dirty||(this._dirty=!0,he(st(this),"set","value"))}}),this.__v_isReadonly=n}get value(){const e=st(this);return e._dirty&&(e._value=this.effect(),e._dirty=!1),fe(e,0,"value"),e._value}set value(e){this._setter(e)}}function yt(e,t,n,o){let r;try{r=o?e(...o):e()}catch(l){kt(l,t,n)}return r}function xt(e,t,n,o){if(M(e)){const r=yt(e,t,n,o);return r&&F(r)&&r.catch((e=>{kt(e,t,n)})),r}const r=[];for(let l=0;l>>1;Bt(Ct[e])-1?Ct.splice(t,0,e):Ct.push(e),Ft()}}function Ft(){_t||wt||(wt=!0,Mt=It.then(zt))}function Nt(e,t,n,o){T(e)?n.push(...e):t&&t.includes(e,e.allowRecurse?o+1:o)||n.push(e),Ft()}function Ut(e,t=null){if($t.length){for(Pt=t,Et=[...new Set($t)],$t.length=0,Ot=0;OtBt(e)-Bt(t))),At=0;Atnull==e.id?1/0:e.id;function zt(e){wt=!1,_t=!0,Ut(e),Ct.sort(((e,t)=>Bt(e)-Bt(t)));try{for(St=0;Ste.trim())):t&&(r=n.map(Z))}let i,c=o[i=J(t)]||o[i=J(H(t))];!c&&l&&(c=o[i=J(G(t))]),c&&xt(c,e,6,r);const a=o[i+"Once"];if(a){if(e.emitted){if(e.emitted[i])return}else e.emitted={};e.emitted[i]=!0,xt(a,e,6,r)}}function Wt(e,t,n=!1){const o=t.emitsCache,r=o.get(e);if(void 0!==r)return r;const l=e.emits;let s={},i=!1;if(!M(e)){const o=e=>{const n=Wt(e,t,!0);n&&(i=!0,$(s,n))};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}return l||i?(T(l)?l.forEach((e=>s[e]=null)):$(s,l),o.set(e,s),s):(o.set(e,null),null)}function Ht(e,t){return!(!e||!C(t))&&(t=t.slice(2).replace(/Once$/,""),L(e,t[0].toLowerCase()+t.slice(1))||L(e,G(t))||L(e,t))}let qt=null,Gt=null;function Kt(e){const t=qt;return qt=e,Gt=e&&e.type.__scopeId||null,t}function Jt(e){Gt=e}function Yt(){Gt=null}const Xt=e=>Qt;function Qt(e,t=qt,n){if(!t)return e;if(e._n)return e;const o=(...n)=>{o._d&&To(-1);const r=Kt(t),l=e(...n);return Kt(r),o._d&&To(1),l};return o._n=!0,o._c=!0,o._d=!0,o}function Zt(e){const{type:t,vnode:n,proxy:o,withProxy:r,props:l,propsOptions:[s],slots:i,attrs:c,emit:a,render:u,renderCache:d,data:p,setupState:f,ctx:h,inheritAttrs:m}=e;let v;const g=Kt(e);try{let e;if(4&n.shapeFlag){const t=r||o;v=zo(u.call(t,t,d,l,f,p,h)),e=c}else{const n=t;0,v=zo(n.length>1?n(l,{attrs:c,slots:i,emit:a}):n(l,null)),e=t.props?c:en(c)}let g=v;if(e&&!1!==m){const t=Object.keys(e),{shapeFlag:n}=g;t.length&&(1&n||6&n)&&(s&&t.some(S)&&(e=tn(e,s)),g=No(g,e))}0,n.dirs&&(g.dirs=g.dirs?g.dirs.concat(n.dirs):n.dirs),n.transition&&(g.transition=n.transition),v=g}catch(b){$o.length=0,kt(b,e,1),v=Fo(Co)}return Kt(g),v}const en=e=>{let t;for(const n in e)("class"===n||"style"===n||C(n))&&((t||(t={}))[n]=e[n]);return t},tn=(e,t)=>{const n={};for(const o in e)S(o)&&o.slice(9)in t||(n[o]=e[o]);return n};function nn(e,t,n){const o=Object.keys(t);if(o.length!==Object.keys(e).length)return!0;for(let r=0;r1)return n&&M(t)?t.call(o.proxy):t}}const ln={};function sn(e,t,n){return cn(e,t,n)}function cn(e,t,{immediate:n,deep:o,flush:r,onTrack:l,onTrigger:s}=y,i=tr){let c,a,u=!1,d=!1;if(at(e)?(c=()=>e.value,u=!!e._shallow):ot(e)?(c=()=>e,o=!0):T(e)?(d=!0,u=e.some(ot),c=()=>e.map((e=>at(e)?e.value:ot(e)?dn(e):M(e)?yt(e,i,2):void 0))):c=M(e)?t?()=>yt(e,i,2):()=>{if(!i||!i.isUnmounted)return a&&a(),xt(e,i,3,[p])}:k,t&&o){const e=c;c=()=>dn(e())}let p=e=>{a=v.options.onStop=()=>{yt(e,i,4)}},f=d?[]:ln;const h=()=>{if(v.active)if(t){const e=v();(o||u||(d?e.some(((e,t)=>Y(e,f[t]))):Y(e,f)))&&(a&&a(),xt(t,i,3,[e,f===ln?void 0:f,p]),f=e)}else v()};let m;h.allowRecurse=!!t,m="sync"===r?h:"post"===r?()=>ho(h,i&&i.suspense):()=>{!i||i.isMounted?function(e){Nt(e,Et,$t,Ot)}(h):h()};const v=le(c,{lazy:!0,onTrack:l,onTrigger:s,scheduler:m});return cr(v,i),t?n?h():f=v():"post"===r?ho(v,i&&i.suspense):v(),()=>{se(v),i&&E(i.effects,v)}}function an(e,t,n){const o=this.proxy,r=P(e)?e.includes(".")?un(o,e):()=>o[e]:e.bind(o,o);let l;return M(t)?l=t:(l=t.handler,n=t),cn(r,l.bind(o),n,this)}function un(e,t){const n=t.split(".");return()=>{let t=e;for(let e=0;e{dn(e,t)}));else if(V(e))for(const n in e)dn(e[n],t);return e}function pn(e){return M(e)?{setup:e,name:e.name}:e}const fn=e=>!!e.type.__asyncLoader;function hn(e){M(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:o,delay:r=200,timeout:l,suspensible:s=!0,onError:i}=e;let c,a=null,u=0;const d=()=>{let e;return a||(e=a=t().catch((e=>{if(e=e instanceof Error?e:new Error(String(e)),i)return new Promise(((t,n)=>{i(e,(()=>t((u++,a=null,d()))),(()=>n(e)),u+1)}));throw e})).then((t=>e!==a&&a?a:(t&&(t.__esModule||"Module"===t[Symbol.toStringTag])&&(t=t.default),c=t,t))))};return pn({name:"AsyncComponentWrapper",__asyncLoader:d,get __asyncResolved(){return c},setup(){const e=tr;if(c)return()=>mn(c,e);const t=t=>{a=null,kt(t,e,13,!o)};if(s&&e.suspense)return d().then((t=>()=>mn(t,e))).catch((e=>(t(e),()=>o?Fo(o,{error:e}):null)));const i=ut(!1),u=ut(),p=ut(!!r);return r&&setTimeout((()=>{p.value=!1}),r),null!=l&&setTimeout((()=>{if(!i.value&&!u.value){const e=new Error(`Async component timed out after ${l}ms.`);t(e),u.value=e}}),l),d().then((()=>{i.value=!0,e.parent&&vn(e.parent.vnode)&&jt(e.parent.update)})).catch((e=>{t(e),u.value=e})),()=>i.value&&c?mn(c,e):u.value&&o?Fo(o,{error:u.value}):n&&!p.value?Fo(n):void 0}})}function mn(e,{vnode:{ref:t,props:n,children:o}}){const r=Fo(e,n,o);return r.ref=t,r}const vn=e=>e.type.__isKeepAlive;function gn(e,t){yn(e,"a",t)}function bn(e,t){yn(e,"da",t)}function yn(e,t,n=tr){const o=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}e()});if(kn(t,o,n),n){let e=n.parent;for(;e&&e.parent;)vn(e.parent.vnode)&&xn(o,t,n,e),e=e.parent}}function xn(e,t,n,o){const r=kn(t,e,o,!0);On((()=>{E(o[t],r)}),n)}function kn(e,t,n=tr,o=!1){if(n){const r=n[e]||(n[e]=[]),l=t.__weh||(t.__weh=(...o)=>{if(n.isUnmounted)return;de(),or(n);const r=xt(t,n,e,o);return or(null),pe(),r});return o?r.unshift(l):r.push(l),l}}const _n=e=>(t,n=tr)=>(!lr||"sp"===e)&&kn(e,t,n),wn=_n("bm"),Cn=_n("m"),Sn=_n("bu"),$n=_n("u"),En=_n("bum"),On=_n("um"),Ln=_n("sp"),Tn=_n("rtg"),An=_n("rtc");function In(e,t=tr){kn("ec",e,t)}let Mn=!0;function Pn(e){const t=Fn(e),n=e.proxy,o=e.ctx;Mn=!1,t.beforeCreate&&Rn(t.beforeCreate,e,"bc");const{data:r,computed:l,methods:s,watch:i,provide:c,inject:a,created:u,beforeMount:d,mounted:p,beforeUpdate:f,updated:h,activated:m,deactivated:v,beforeDestroy:g,beforeUnmount:b,destroyed:x,unmounted:_,render:w,renderTracked:C,renderTriggered:S,errorCaptured:$,serverPrefetch:E,expose:O,inheritAttrs:L,components:A,directives:I,filters:P}=t;if(a&&function(e,t,n=k){T(e)&&(e=Bn(e));for(const o in e){const n=e[o];j(n)?t[o]="default"in n?rn(n.from||o,n.default,!0):rn(n.from||o):t[o]=rn(n)}}(a,o,null),s)for(const y in s){const e=s[y];M(e)&&(o[y]=e.bind(n))}if(r){const t=r.call(n,n);j(t)&&(e.data=et(t))}if(Mn=!0,l)for(const y in l){const e=l[y],t=ur({get:M(e)?e.bind(n,n):M(e.get)?e.get.bind(n,n):k,set:!M(e)&&M(e.set)?e.set.bind(n):k});Object.defineProperty(o,y,{enumerable:!0,configurable:!0,get:()=>t.value,set:e=>t.value=e})}if(i)for(const y in i)jn(i[y],o,n,y);if(c){const e=M(c)?c.call(n):c;Reflect.ownKeys(e).forEach((t=>{!function(e,t){if(tr){let n=tr.provides;const o=tr.parent&&tr.parent.provides;o===n&&(n=tr.provides=Object.create(o)),n[e]=t}}(t,e[t])}))}function R(e,t){T(t)?t.forEach((t=>e(t.bind(n)))):t&&e(t.bind(n))}if(u&&Rn(u,e,"c"),R(wn,d),R(Cn,p),R(Sn,f),R($n,h),R(gn,m),R(bn,v),R(In,$),R(An,C),R(Tn,S),R(En,b),R(On,_),R(Ln,E),T(O))if(O.length){const t=e.exposed||(e.exposed=ht({}));O.forEach((e=>{t[e]=gt(n,e)}))}else e.exposed||(e.exposed=y);w&&e.render===k&&(e.render=w),null!=L&&(e.inheritAttrs=L),A&&(e.components=A),I&&(e.directives=I)}function Rn(e,t,n){xt(T(e)?e.map((e=>e.bind(t.proxy))):e.bind(t.proxy),t,n)}function jn(e,t,n,o){const r=o.includes(".")?un(n,o):()=>n[o];if(P(e)){const n=t[e];M(n)&&sn(r,n)}else if(M(e))sn(r,e.bind(n));else if(j(e))if(T(e))e.forEach((e=>jn(e,t,n,o)));else{const o=M(e.handler)?e.handler.bind(n):t[e.handler];M(o)&&sn(r,o,e)}}function Fn(e){const t=e.type,{mixins:n,extends:o}=t,{mixins:r,optionsCache:l,config:{optionMergeStrategies:s}}=e.appContext,i=l.get(t);let c;return i?c=i:r.length||n||o?(c={},r.length&&r.forEach((e=>Nn(c,e,s,!0))),Nn(c,t,s)):c=t,l.set(t,c),c}function Nn(e,t,n,o=!1){const{mixins:r,extends:l}=t;l&&Nn(e,l,n,!0),r&&r.forEach((t=>Nn(e,t,n,!0)));for(const s in t)if(o&&"expose"===s);else{const o=Un[s]||n&&n[s];e[s]=o?o(e[s],t[s]):t[s]}return e}const Un={data:Vn,props:Dn,emits:Dn,methods:Dn,computed:Dn,beforeCreate:zn,created:zn,beforeMount:zn,mounted:zn,beforeUpdate:zn,updated:zn,beforeDestroy:zn,destroyed:zn,activated:zn,deactivated:zn,errorCaptured:zn,serverPrefetch:zn,components:Dn,directives:Dn,watch:function(e,t){if(!e)return t;if(!t)return e;const n=$(Object.create(null),e);for(const o in t)n[o]=zn(e[o],t[o]);return n},provide:Vn,inject:function(e,t){return Dn(Bn(e),Bn(t))}};function Vn(e,t){return t?e?function(){return $(M(e)?e.call(this,this):e,M(t)?t.call(this,this):t)}:t:e}function Bn(e){if(T(e)){const t={};for(let n=0;n{c=!0;const[n,o]=Gn(e,t,!0);$(s,n),o&&i.push(...o)};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}if(!l&&!c)return o.set(e,x),x;if(T(l))for(let u=0;u-1,n[1]=o<0||t-1||L(n,"default"))&&i.push(e)}}}const a=[s,i];return o.set(e,a),a}function Kn(e){return"$"!==e[0]}function Jn(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:""}function Yn(e,t){return Jn(e)===Jn(t)}function Xn(e,t){return T(t)?t.findIndex((t=>Yn(t,e))):M(t)&&Yn(t,e)?0:-1}const Qn=e=>"_"===e[0]||"$stable"===e,Zn=e=>T(e)?e.map(zo):[zo(e)],eo=(e,t,n)=>{const o=Qt((e=>Zn(t(e))),n);return o._c=!1,o},to=(e,t,n)=>{const o=e._ctx;for(const r in e){if(Qn(r))continue;const n=e[r];if(M(n))t[r]=eo(0,n,o);else if(null!=n){const e=Zn(n);t[r]=()=>e}}},no=(e,t)=>{const n=Zn(t);e.slots.default=()=>n};function oo(e,t){if(null===qt)return e;const n=qt.proxy,o=e.dirs||(e.dirs=[]);for(let r=0;r(l.has(e)||(e&&M(e.install)?(l.add(e),e.install(i,...t)):M(e)&&(l.add(e),e(i,...t))),i),mixin:e=>(r.mixins.includes(e)||r.mixins.push(e),i),component:(e,t)=>t?(r.components[e]=t,i):r.components[e],directive:(e,t)=>t?(r.directives[e]=t,i):r.directives[e],mount(l,c,a){if(!s){const u=Fo(n,o);return u.appContext=r,c&&t?t(u,l):e(u,l,a),s=!0,i._container=l,l.__vue_app__=i,u.component.proxy}},unmount(){s&&(e(null,i._container),delete i._container.__vue_app__)},provide:(e,t)=>(r.provides[e]=t,i)};return i}}let co=!1;const ao=e=>/svg/.test(e.namespaceURI)&&"foreignObject"!==e.tagName,uo=e=>8===e.nodeType;function po(e){const{mt:t,p:n,o:{patchProp:o,nextSibling:r,parentNode:l,remove:s,insert:i,createComment:c}}=e,a=(n,o,s,i,c,m=!1)=>{const v=uo(n)&&"["===n.data,g=()=>f(n,o,s,i,c,v),{type:b,ref:y,shapeFlag:x}=o,k=n.nodeType;o.el=n;let _=null;switch(b){case wo:3!==k?_=g():(n.data!==o.children&&(co=!0,n.data=o.children),_=r(n));break;case Co:_=8!==k||v?g():r(n);break;case So:if(1===k){_=n;const e=!o.children.length;for(let t=0;t{i=i||!!t.dynamicChildren;const{props:c,patchFlag:a,shapeFlag:u,dirs:p}=t;if(-1!==a){if(p&&ro(t,null,n,"created"),c)if(!i||16&a||32&a)for(const t in c)!z(t)&&C(t)&&o(e,t,null,c[t]);else c.onClick&&o(e,"onClick",null,c.onClick);let f;if((f=c&&c.onVnodeBeforeMount)&&go(f,n,t),p&&ro(t,null,n,"beforeMount"),((f=c&&c.onVnodeMounted)||p)&&on((()=>{f&&go(f,n,t),p&&ro(t,null,n,"mounted")}),r),16&u&&(!c||!c.innerHTML&&!c.textContent)){let o=d(e.firstChild,t,e,n,r,l,i);for(;o;){co=!0;const e=o;o=o.nextSibling,s(e)}}else 8&u&&e.textContent!==t.children&&(co=!0,e.textContent=t.children)}return e.nextSibling},d=(e,t,o,r,l,s,i)=>{i=i||!!t.dynamicChildren;const c=t.children,u=c.length;for(let d=0;d{const{slotScopeIds:u}=t;u&&(s=s?s.concat(u):u);const p=l(e),f=d(r(e),t,p,n,o,s,a);return f&&uo(f)&&"]"===f.data?r(t.anchor=f):(co=!0,i(t.anchor=c("]"),p,f),f)},f=(e,t,o,i,c,a)=>{if(co=!0,t.el=null,a){const t=h(e);for(;;){const n=r(e);if(!n||n===t)break;s(n)}}const u=r(e),d=l(e);return s(e),n(null,t,d,u,o,i,ao(d),c),u},h=e=>{let t=0;for(;e;)if((e=r(e))&&uo(e)&&("["===e.data&&t++,"]"===e.data)){if(0===t)return r(e);t--}return e};return[(e,t)=>{co=!1,a(t.firstChild,e,null,null,null),Vt(),co&&console.error("Hydration completed but contains mismatches.")},a]}const fo={scheduler:jt,allowRecurse:!0},ho=on,mo=(e,t,n,o,r=!1)=>{if(T(e))return void e.forEach(((e,l)=>mo(e,t&&(T(t)?t[l]:t),n,o,r)));if(fn(o)&&!r)return;const l=4&o.shapeFlag?o.component.exposed||o.component.proxy:o.el,s=r?null:l,{i:i,r:c}=e,a=t&&t.r,u=i.refs===y?i.refs={}:i.refs,d=i.setupState;if(null!=a&&a!==c&&(P(a)?(u[a]=null,L(d,a)&&(d[a]=null)):at(a)&&(a.value=null)),P(c)){const e=()=>{u[c]=s,L(d,c)&&(d[c]=s)};s?(e.id=-1,ho(e,n)):e()}else if(at(c)){const e=()=>{c.value=s};s?(e.id=-1,ho(e,n)):e()}else M(c)&&yt(c,i,12,[s,u])};function vo(e){return function(e,t){const{insert:n,remove:o,patchProp:r,forcePatchProp:l,createElement:s,createText:i,createComment:c,setText:a,setElementText:u,parentNode:d,nextSibling:p,setScopeId:f=k,cloneNode:h,insertStaticContent:m}=e,v=(e,t,n,o=null,r=null,l=null,s=!1,i=null,c=!1)=>{e&&!Mo(e,t)&&(o=te(e),K(e,r,l,!0),e=null),-2===t.patchFlag&&(c=!1,t.dynamicChildren=null);const{type:a,ref:u,shapeFlag:d}=t;switch(a){case wo:g(e,t,n,o);break;case Co:b(e,t,n,o);break;case So:null==e&&_(t,n,o,s);break;case _o:P(e,t,n,o,r,l,s,i,c);break;default:1&d?S(e,t,n,o,r,l,s,i,c):6&d?R(e,t,n,o,r,l,s,i,c):(64&d||128&d)&&a.process(e,t,n,o,r,l,s,i,c,oe)}null!=u&&r&&mo(u,e&&e.ref,l,t||e,!t)},g=(e,t,o,r)=>{if(null==e)n(t.el=i(t.children),o,r);else{const n=t.el=e.el;t.children!==e.children&&a(n,t.children)}},b=(e,t,o,r)=>{null==e?n(t.el=c(t.children||""),o,r):t.el=e.el},_=(e,t,n,o)=>{[e.el,e.anchor]=m(e.children,t,n,o,e.el&&[e.el,e.anchor])},w=({el:e,anchor:t},o,r)=>{let l;for(;e&&e!==t;)l=p(e),n(e,o,r),e=l;n(t,o,r)},C=({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=p(e),o(e),e=n;o(t)},S=(e,t,n,o,r,l,s,i,c)=>{s=s||"svg"===t.type,null==e?E(t,n,o,r,l,s,i,c):A(e,t,r,l,s,i,c)},E=(e,t,o,l,i,c,a,d)=>{let p,f;const{type:m,props:v,shapeFlag:g,transition:b,patchFlag:y,dirs:x}=e;if(e.el&&void 0!==h&&-1===y)p=e.el=h(e.el);else{if(p=e.el=s(e.type,c,v&&v.is,v),8&g?u(p,e.children):16&g&&T(e.children,p,null,l,i,c&&"foreignObject"!==m,a,d||!!e.dynamicChildren),x&&ro(e,null,l,"created"),v){for(const t in v)z(t)||r(p,t,null,v[t],c,e.children,l,i,ee);(f=v.onVnodeBeforeMount)&&go(f,l,e)}O(p,e,e.scopeId,a,l)}x&&ro(e,null,l,"beforeMount");const k=(!i||i&&!i.pendingBranch)&&b&&!b.persisted;k&&b.beforeEnter(p),n(p,t,o),((f=v&&v.onVnodeMounted)||k||x)&&ho((()=>{f&&go(f,l,e),k&&b.enter(p),x&&ro(e,null,l,"mounted")}),i)},O=(e,t,n,o,r)=>{if(n&&f(e,n),o)for(let l=0;l{for(let a=c;a{const a=t.el=e.el;let{patchFlag:d,dynamicChildren:p,dirs:f}=t;d|=16&e.patchFlag;const h=e.props||y,m=t.props||y;let v;if((v=m.onVnodeBeforeUpdate)&&go(v,n,t,e),f&&ro(t,e,n,"beforeUpdate"),d>0){if(16&d)M(a,t,h,m,n,o,s);else if(2&d&&h.class!==m.class&&r(a,"class",null,m.class,s),4&d&&r(a,"style",h.style,m.style,s),8&d){const i=t.dynamicProps;for(let t=0;t{v&&go(v,n,t,e),f&&ro(t,e,n,"updated")}),o)},I=(e,t,n,o,r,l,s)=>{for(let i=0;i{if(n!==o){for(const a in o){if(z(a))continue;const u=o[a],d=n[a];(u!==d||l&&l(e,a))&&r(e,a,d,u,c,t.children,s,i,ee)}if(n!==y)for(const l in n)z(l)||l in o||r(e,l,n[l],null,c,t.children,s,i,ee)}},P=(e,t,o,r,l,s,c,a,u)=>{const d=t.el=e?e.el:i(""),p=t.anchor=e?e.anchor:i("");let{patchFlag:f,dynamicChildren:h,slotScopeIds:m}=t;h&&(u=!0),m&&(a=a?a.concat(m):m),null==e?(n(d,o,r),n(p,o,r),T(t.children,o,p,l,s,c,a,u)):f>0&&64&f&&h&&e.dynamicChildren?(I(e.dynamicChildren,h,o,l,s,c,a),(null!=t.key||l&&t===l.subTree)&&bo(e,t,!0)):B(e,t,o,p,l,s,c,a,u)},R=(e,t,n,o,r,l,s,i,c)=>{t.slotScopeIds=i,null==e?512&t.shapeFlag?r.ctx.activate(t,n,o,s,c):j(t,n,o,r,l,s,c):N(e,t,c)},j=(e,t,n,o,r,l,s)=>{const i=e.component=function(e,t,n){const o=e.type,r=(t?t.appContext:e.appContext)||Zo,l={uid:er++,vnode:e,type:o,parent:t,appContext:r,root:null,next:null,subTree:null,update:null,render:null,proxy:null,exposed:null,withProxy:null,effects:null,provides:t?t.provides:Object.create(r.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:Gn(o,r),emitsOptions:Wt(o,r),emit:null,emitted:null,propsDefaults:y,inheritAttrs:o.inheritAttrs,ctx:y,data:y,props:y,attrs:y,slots:y,refs:y,setupState:y,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};return l.ctx={_:l},l.root=t?t.root:l,l.emit=Dt.bind(null,l),l}(e,o,r);if(vn(e)&&(i.ctx.renderer=oe),function(e,t=!1){lr=t;const{props:n,children:o}=e.vnode,r=rr(e);Wn(e,n,r,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=st(t),Q(t,"_",n)):to(t,e.slots={})}else e.slots={},t&&no(e,t);Q(e.slots,Po,1)})(e,o);const l=r?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=it(new Proxy(e.ctx,Xo));const{setup:o}=n;if(o){const n=e.setupContext=o.length>1?function(e){const t=t=>{e.exposed=ht(t)};return{attrs:e.attrs,slots:e.slots,emit:e.emit,expose:t}}(e):null;tr=e,de();const r=yt(o,e,0,[e.props,n]);if(pe(),tr=null,F(r)){if(t)return r.then((t=>{sr(e,t)})).catch((t=>{kt(t,e,0)}));e.asyncDep=r}else sr(e,r)}else ir(e)}(e,t):void 0;lr=!1}(i),i.asyncDep){if(r&&r.registerDep(i,U),!e.el){const e=i.subTree=Fo(Co);b(null,e,t,n)}}else U(i,e,t,n,r,l,s)},N=(e,t,n)=>{const o=t.component=e.component;if(function(e,t,n){const{props:o,children:r,component:l}=e,{props:s,children:i,patchFlag:c}=t,a=l.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&c>=0))return!(!r&&!i||i&&i.$stable)||o!==s&&(o?!s||nn(o,s,a):!!s);if(1024&c)return!0;if(16&c)return o?nn(o,s,a):!!s;if(8&c){const e=t.dynamicProps;for(let t=0;tSt&&Ct.splice(t,1)}(o.update),o.update()}else t.component=e.component,t.el=e.el,o.vnode=t},U=(e,t,n,o,r,l,s)=>{e.update=le((function(){if(e.isMounted){let t,{next:n,bu:o,u:i,parent:c,vnode:a}=e,u=n;n?(n.el=a.el,V(e,n,s)):n=a,o&&X(o),(t=n.props&&n.props.onVnodeBeforeUpdate)&&go(t,c,n,a);const p=Zt(e),f=e.subTree;e.subTree=p,v(f,p,d(f.el),te(f),e,r,l),n.el=p.el,null===u&&function({vnode:e,parent:t},n){for(;t&&t.subTree===e;)(e=t.vnode).el=n,t=t.parent}(e,p.el),i&&ho(i,r),(t=n.props&&n.props.onVnodeUpdated)&&ho((()=>go(t,c,n,a)),r)}else{let s;const{el:i,props:c}=t,{bm:a,m:u,parent:d}=e;if(a&&X(a),(s=c&&c.onVnodeBeforeMount)&&go(s,d,t),i&&ie){const n=()=>{e.subTree=Zt(e),ie(i,e.subTree,e,r,null)};fn(t)?t.type.__asyncLoader().then((()=>!e.isUnmounted&&n())):n()}else{const s=e.subTree=Zt(e);v(null,s,n,o,e,r,l),t.el=s.el}if(u&&ho(u,r),s=c&&c.onVnodeMounted){const e=t;ho((()=>go(s,d,e)),r)}256&t.shapeFlag&&e.a&&ho(e.a,r),e.isMounted=!0,t=n=o=null}}),fo)},V=(e,t,n)=>{t.component=e;const o=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,o){const{props:r,attrs:l,vnode:{patchFlag:s}}=e,i=st(r),[c]=e.propsOptions;let a=!1;if(!(o||s>0)||16&s){let o;Hn(e,t,r,l)&&(a=!0);for(const l in i)t&&(L(t,l)||(o=G(l))!==l&&L(t,o))||(c?!n||void 0===n[l]&&void 0===n[o]||(r[l]=qn(c,i,l,void 0,e,!0)):delete r[l]);if(l!==i)for(const e in l)t&&L(t,e)||(delete l[e],a=!0)}else if(8&s){const n=e.vnode.dynamicProps;for(let o=0;o{const{vnode:o,slots:r}=e;let l=!0,s=y;if(32&o.shapeFlag){const e=t._;e?n&&1===e?l=!1:($(r,t),n||1!==e||delete r._):(l=!t.$stable,to(t,r)),s=t}else t&&(no(e,t),s={default:1});if(l)for(const i in r)Qn(i)||i in s||delete r[i]})(e,t.children,n),de(),Ut(void 0,e.update),pe()},B=(e,t,n,o,r,l,s,i,c=!1)=>{const a=e&&e.children,d=e?e.shapeFlag:0,p=t.children,{patchFlag:f,shapeFlag:h}=t;if(f>0){if(128&f)return void W(a,p,n,o,r,l,s,i,c);if(256&f)return void D(a,p,n,o,r,l,s,i,c)}8&h?(16&d&&ee(a,r,l),p!==a&&u(n,p)):16&d?16&h?W(a,p,n,o,r,l,s,i,c):ee(a,r,l,!0):(8&d&&u(n,""),16&h&&T(p,n,o,r,l,s,i,c))},D=(e,t,n,o,r,l,s,i,c)=>{t=t||x;const a=(e=e||x).length,u=t.length,d=Math.min(a,u);let p;for(p=0;pu?ee(e,r,l,!0,!1,d):T(t,n,o,r,l,s,i,c,d)},W=(e,t,n,o,r,l,s,i,c)=>{let a=0;const u=t.length;let d=e.length-1,p=u-1;for(;a<=d&&a<=p;){const o=e[a],u=t[a]=c?Do(t[a]):zo(t[a]);if(!Mo(o,u))break;v(o,u,n,null,r,l,s,i,c),a++}for(;a<=d&&a<=p;){const o=e[d],a=t[p]=c?Do(t[p]):zo(t[p]);if(!Mo(o,a))break;v(o,a,n,null,r,l,s,i,c),d--,p--}if(a>d){if(a<=p){const e=p+1,d=ep)for(;a<=d;)K(e[a],r,l,!0),a++;else{const f=a,h=a,m=new Map;for(a=h;a<=p;a++){const e=t[a]=c?Do(t[a]):zo(t[a]);null!=e.key&&m.set(e.key,a)}let g,b=0;const y=p-h+1;let k=!1,_=0;const w=new Array(y);for(a=0;a=y){K(o,r,l,!0);continue}let u;if(null!=o.key)u=m.get(o.key);else for(g=h;g<=p;g++)if(0===w[g-h]&&Mo(o,t[g])){u=g;break}void 0===u?K(o,r,l,!0):(w[u-h]=a+1,u>=_?_=u:k=!0,v(o,t[u],n,null,r,l,s,i,c),b++)}const C=k?function(e){const t=e.slice(),n=[0];let o,r,l,s,i;const c=e.length;for(o=0;o0&&(t[o]=n[l-1]),n[l]=o)}}l=n.length,s=n[l-1];for(;l-- >0;)n[l]=s,s=t[s];return n}(w):x;for(g=C.length-1,a=y-1;a>=0;a--){const e=h+a,d=t[e],p=e+1{const{el:s,type:i,transition:c,children:a,shapeFlag:u}=e;if(6&u)return void q(e.component.subTree,t,o,r);if(128&u)return void e.suspense.move(t,o,r);if(64&u)return void i.move(e,t,o,oe);if(i===_o){n(s,t,o);for(let e=0;ec.enter(s)),l);else{const{leave:e,delayLeave:r,afterLeave:l}=c,i=()=>n(s,t,o),a=()=>{e(s,(()=>{i(),l&&l()}))};r?r(s,i,a):a()}else n(s,t,o)},K=(e,t,n,o=!1,r=!1)=>{const{type:l,props:s,ref:i,children:c,dynamicChildren:a,shapeFlag:u,patchFlag:d,dirs:p}=e;if(null!=i&&mo(i,null,n,e,!0),256&u)return void t.ctx.deactivate(e);const f=1&u&&p;let h;if((h=s&&s.onVnodeBeforeUnmount)&&go(h,t,e),6&u)Z(e.component,n,o);else{if(128&u)return void e.suspense.unmount(n,o);f&&ro(e,null,t,"beforeUnmount"),64&u?e.type.remove(e,t,n,r,oe,o):a&&(l!==_o||d>0&&64&d)?ee(a,t,n,!1,!0):(l===_o&&(128&d||256&d)||!r&&16&u)&&ee(c,t,n),o&&J(e)}((h=s&&s.onVnodeUnmounted)||f)&&ho((()=>{h&&go(h,t,e),f&&ro(e,null,t,"unmounted")}),n)},J=e=>{const{type:t,el:n,anchor:r,transition:l}=e;if(t===_o)return void Y(n,r);if(t===So)return void C(e);const s=()=>{o(n),l&&!l.persisted&&l.afterLeave&&l.afterLeave()};if(1&e.shapeFlag&&l&&!l.persisted){const{leave:t,delayLeave:o}=l,r=()=>t(n,s);o?o(e.el,s,r):r()}else s()},Y=(e,t)=>{let n;for(;e!==t;)n=p(e),o(e),e=n;o(t)},Z=(e,t,n)=>{const{bum:o,effects:r,update:l,subTree:s,um:i}=e;if(o&&X(o),r)for(let c=0;c{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},ee=(e,t,n,o=!1,r=!1,l=0)=>{for(let s=l;s6&e.shapeFlag?te(e.component.subTree):128&e.shapeFlag?e.suspense.next():p(e.anchor||e.el),ne=(e,t,n)=>{null==e?t._vnode&&K(t._vnode,null,null,!0):v(t._vnode||null,e,t,null,null,null,n),Vt(),t._vnode=e},oe={p:v,um:K,m:q,r:J,mt:j,mc:T,pc:B,pbc:I,n:te,o:e};let re,ie;t&&([re,ie]=t(oe));return{render:ne,hydrate:re,createApp:io(ne,re)}}(e,po)}function go(e,t,n,o=null){xt(e,t,7,[n,o])}function bo(e,t,n=!1){const o=e.children,r=t.children;if(T(o)&&T(r))for(let l=0;l0?Eo||x:null,$o.pop(),Eo=$o[$o.length-1]||null,Lo>0&&Eo&&Eo.push(l),l}function Io(e){return!!e&&!0===e.__v_isVNode}function Mo(e,t){return e.type===t.type&&e.key===t.key}const Po="__vInternal",Ro=({key:e})=>null!=e?e:null,jo=({ref:e})=>null!=e?P(e)||at(e)||M(e)?{i:qt,r:e}:e:null,Fo=function(e,t=null,n=null,o=0,r=null,l=!1){e&&e!==xo||(e=Co);if(Io(e)){const o=No(e,t,!0);return n&&Wo(o,n),o}s=e,M(s)&&"__vccOpts"in s&&(e=e.__vccOpts);var s;if(t){(lt(t)||Po in t)&&(t=$({},t));let{class:e,style:n}=t;e&&!P(e)&&(t.class=v(e)),j(n)&&(lt(n)&&!T(n)&&(n=$({},n)),t.style=p(n))}const i=P(e)?1:(e=>e.__isSuspense)(e)?128:(e=>e.__isTeleport)(e)?64:j(e)?4:M(e)?2:0,c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ro(t),ref:t&&jo(t),scopeId:Gt,slotScopeIds:null,children:null,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:o,dynamicProps:r,dynamicChildren:null,appContext:null};Wo(c,n),128&i&&e.normalize(c);Lo>0&&!l&&Eo&&(o>0||6&i)&&32!==o&&Eo.push(c);return c};function No(e,t,n=!1){const{props:o,ref:r,patchFlag:l,children:s}=e,i=t?Ho(o||{},t):o;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:i,key:i&&Ro(i),ref:t&&t.ref?n&&r?T(r)?r.concat(jo(t)):[r,jo(t)]:jo(t):r,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:s,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==_o?-1===l?16:16|l:l,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&No(e.ssContent),ssFallback:e.ssFallback&&No(e.ssFallback),el:e.el,anchor:e.anchor}}function Uo(e=" ",t=0){return Fo(wo,null,e,t)}function Vo(e,t){const n=Fo(So,null,e);return n.staticCount=t,n}function Bo(e="",t=!1){return t?(Oo(),Ao(Co,null,e)):Fo(Co,null,e)}function zo(e){return null==e||"boolean"==typeof e?Fo(Co):T(e)?Fo(_o,null,e.slice()):"object"==typeof e?Do(e):Fo(wo,null,String(e))}function Do(e){return null===e.el?e:No(e)}function Wo(e,t){let n=0;const{shapeFlag:o}=e;if(null==t)t=null;else if(T(t))n=16;else if("object"==typeof t){if(1&o||64&o){const n=t.default;return void(n&&(n._c&&(n._d=!1),Wo(e,n()),n._c&&(n._d=!0)))}{n=32;const o=t._;o||Po in t?3===o&&qt&&(1===qt.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=qt}}else M(t)?(t={default:t,_ctx:qt},n=32):(t=String(t),64&o?(n=16,t=[Uo(t)]):n=8);e.children=t,e.shapeFlag|=n}function Ho(...e){const t=$({},e[0]);for(let n=1;n!Io(e)||e.type!==Co&&!(e.type===_o&&!Ko(e.children))))?e:null}const Jo=e=>e?rr(e)?e.exposed?e.exposed:e.proxy:Jo(e.parent):null,Yo=$(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Jo(e.parent),$root:e=>Jo(e.root),$emit:e=>e.emit,$options:e=>Fn(e),$forceUpdate:e=>()=>jt(e.update),$nextTick:e=>Rt.bind(e.proxy),$watch:e=>an.bind(e)}),Xo={get({_:e},t){const{ctx:n,setupState:o,data:r,props:l,accessCache:s,type:i,appContext:c}=e;let a;if("$"!==t[0]){const i=s[t];if(void 0!==i)switch(i){case 0:return o[t];case 1:return r[t];case 3:return n[t];case 2:return l[t]}else{if(o!==y&&L(o,t))return s[t]=0,o[t];if(r!==y&&L(r,t))return s[t]=1,r[t];if((a=e.propsOptions[0])&&L(a,t))return s[t]=2,l[t];if(n!==y&&L(n,t))return s[t]=3,n[t];Mn&&(s[t]=4)}}const u=Yo[t];let d,p;return u?("$attrs"===t&&fe(e,0,t),u(e)):(d=i.__cssModules)&&(d=d[t])?d:n!==y&&L(n,t)?(s[t]=3,n[t]):(p=c.config.globalProperties,L(p,t)?p[t]:void 0)},set({_:e},t,n){const{data:o,setupState:r,ctx:l}=e;if(r!==y&&L(r,t))r[t]=n;else if(o!==y&&L(o,t))o[t]=n;else if(L(e.props,t))return!1;return("$"!==t[0]||!(t.slice(1)in e))&&(l[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:o,appContext:r,propsOptions:l}},s){let i;return void 0!==n[s]||e!==y&&L(e,s)||t!==y&&L(t,s)||(i=l[0])&&L(i,s)||L(o,s)||L(Yo,s)||L(r.config.globalProperties,s)}},Qo=$({},Xo,{get(e,t){if(t!==Symbol.unscopables)return Xo.get(e,t,e)},has:(e,t)=>"_"!==t[0]&&!u(t)}),Zo=lo();let er=0;let tr=null;const nr=()=>tr||qt,or=e=>{tr=e};function rr(e){return 4&e.vnode.shapeFlag}let lr=!1;function sr(e,t,n){M(t)?e.render=t:j(t)&&(e.setupState=ht(t)),ir(e)}function ir(e,t,n){const o=e.type;e.render||(e.render=o.render||k,e.render._rc&&(e.withProxy=new Proxy(e.ctx,Qo))),tr=e,de(),Pn(e),pe(),tr=null}function cr(e,t=tr){t&&(t.effects||(t.effects=[])).push(e)}function ar(e){return M(e)&&e.displayName||e.name}function ur(e){const t=function(e){let t,n;return M(e)?(t=e,n=k):(t=e.get,n=e.set),new bt(t,n,M(e)||!e.set)}(e);return cr(t.effect),t}function dr(e,t,n){const o=arguments.length;return 2===o?j(t)&&!T(t)?Io(t)?Fo(e,null,[t]):Fo(e,t):Fo(e,null,t):(o>3?n=Array.prototype.slice.call(arguments,2):3===o&&Io(n)&&(n=[n]),Fo(e,t,n))}const pr="3.1.2",fr="http://www.w3.org/2000/svg",hr="undefined"!=typeof document?document:null,mr={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,o)=>{const r=t?hr.createElementNS(fr,e):hr.createElement(e,n?{is:n}:void 0);return"select"===e&&o&&null!=o.multiple&&r.setAttribute("multiple",o.multiple),r},createText:e=>hr.createTextNode(e),createComment:e=>hr.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>hr.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},cloneNode(e){const t=e.cloneNode(!0);return"_value"in e&&(t._value=e._value),t},insertStaticContent(e,t,n,o,r){if(r){let e,o,[l,s]=r;for(;;){let r=l.cloneNode(!0);if(e||(e=r),t.insertBefore(r,n),l===s){o=r;break}l=l.nextSibling}return[e,o]}const l=n?n.previousSibling:t.lastChild;if(n){let r,l=!1;n instanceof Element?r=n:(l=!0,r=o?hr.createElementNS(fr,"g"):hr.createElement("div"),t.insertBefore(r,n)),r.insertAdjacentHTML("beforebegin",e),l&&t.removeChild(r)}else t.insertAdjacentHTML("beforeend",e);return[l?l.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};const vr=/\s*!important$/;function gr(e,t,n){if(T(n))n.forEach((n=>gr(e,t,n)));else if(t.startsWith("--"))e.setProperty(t,n);else{const o=function(e,t){const n=yr[t];if(n)return n;let o=H(t);if("filter"!==o&&o in e)return yr[t]=o;o=K(o);for(let r=0;rdocument.createEvent("Event").timeStamp&&(kr=()=>performance.now());const e=navigator.userAgent.match(/firefox\/(\d+)/i);_r=!!(e&&Number(e[1])<=53)}let wr=0;const Cr=Promise.resolve(),Sr=()=>{wr=0};function $r(e,t,n,o,r=null){const l=e._vei||(e._vei={}),s=l[t];if(o&&s)s.value=o;else{const[n,i]=function(e){let t;if(Er.test(e)){let n;for(t={};n=e.match(Er);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[G(e.slice(2)),t]}(t);if(o){!function(e,t,n,o){e.addEventListener(t,n,o)}(e,n,l[t]=function(e,t){const n=e=>{const o=e.timeStamp||kr();(_r||o>=n.attached-1)&&xt(function(e,t){if(T(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e(t)))}return t}(e,n.value),t,5,[e])};return n.value=e,n.attached=(()=>wr||(Cr.then(Sr),wr=kr()))(),n}(o,r),i)}else s&&(!function(e,t,n,o){e.removeEventListener(t,n,o)}(e,n,s,i),l[t]=void 0)}}const Er=/(?:Once|Passive|Capture)$/;const Or=/^on[a-z]/;const Lr={beforeMount(e,{value:t},{transition:n}){e._vod="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):Tr(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:o}){!t!=!n&&(o?t?(o.beforeEnter(e),Tr(e,!0),o.enter(e)):o.leave(e,(()=>{Tr(e,!1)})):Tr(e,t))},beforeUnmount(e,{value:t}){Tr(e,t)}};function Tr(e,t){e.style.display=t?e._vod:"none"}const Ar=$({patchProp:(e,t,n,o,r=!1,l,s,i,c)=>{switch(t){case"class":!function(e,t,n){if(null==t&&(t=""),n)e.setAttribute("class",t);else{const n=e._vtc;n&&(t=(t?[t,...n]:[...n]).join(" ")),e.className=t}}(e,o,r);break;case"style":!function(e,t,n){const o=e.style;if(n)if(P(n)){if(t!==n){const t=o.display;o.cssText=n,"_vod"in e&&(o.display=t)}}else{for(const e in n)gr(o,e,n[e]);if(t&&!P(t))for(const e in t)null==n[e]&&gr(o,e,"")}else e.removeAttribute("style")}(e,n,o);break;default:C(t)?S(t)||$r(e,t,0,o,s):function(e,t,n,o){if(o)return"innerHTML"===t||!!(t in e&&Or.test(t)&&M(n));if("spellcheck"===t||"draggable"===t)return!1;if("form"===t)return!1;if("list"===t&&"INPUT"===e.tagName)return!1;if("type"===t&&"TEXTAREA"===e.tagName)return!1;if(Or.test(t)&&P(n))return!1;return t in e}(e,t,o,r)?function(e,t,n,o,r,l,s){if("innerHTML"===t||"textContent"===t)return o&&s(o,r,l),void(e[t]=null==n?"":n);if("value"===t&&"PROGRESS"!==e.tagName){e._value=n;const o=null==n?"":n;return e.value!==o&&(e.value=o),void(null==n&&e.removeAttribute(t))}if(""===n||null==n){const o=typeof e[t];if(""===n&&"boolean"===o)return void(e[t]=!0);if(null==n&&"string"===o)return e[t]="",void e.removeAttribute(t);if("number"===o)return e[t]=0,void e.removeAttribute(t)}try{e[t]=n}catch(i){}}(e,t,o,l,s,i,c):("true-value"===t?e._trueValue=o:"false-value"===t&&(e._falseValue=o),function(e,t,n,o,r){if(o&&t.startsWith("xlink:"))null==n?e.removeAttributeNS(xr,t.slice(6,t.length)):e.setAttributeNS(xr,t,n);else{const o=d(t);null==n||o&&!1===n?e.removeAttribute(t):e.setAttribute(t,o?"":n)}}(e,t,o,r))}},forcePatchProp:(e,t)=>"value"===t},mr);let Ir,Mr=!1;const Pr=(...e)=>{const t=(Ir=Mr?Ir:vo(Ar),Mr=!0,Ir).createApp(...e),{mount:n}=t;return t.mount=e=>{const t=function(e){if(P(e)){return document.querySelector(e)}return e}(e);if(t)return n(t,!0,t instanceof SVGElement)},t};const Rr="undefined"!=typeof window;function jr(e,t){const n=function(e,t){t.sort(((e,t)=>{const n=t.split("/").length-e.split("/").length;return 0!==n?n:t.length-e.length}));for(const n of t)if(e.startsWith(n))return n}(t,Object.keys(e));return n?e[n]:void 0}function Fr(e,t){t=function(e,t){if(!Rr)return t;const n=e.base,o=n.endsWith("/")?n.slice(0,-1):n;return t.slice(o.length)}(e,t);const n=jr(e.locales||{},t)||{},o=jr(e.themeConfig&&e.themeConfig.locales||{},t)||{};return c(i(i({},e),n),{themeConfig:c(i(i({},e.themeConfig),o),{locales:{}}),lang:o.lang||e.lang,locales:{}})}function Nr(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function Ur(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t.endsWith("/")&&(t+="index"),Rr){const e="/";t=t.slice(e.length).replace(/\//g,"_")+".md";const n=__VP_HASH_MAP__[t.toLowerCase()];t=`${e}assets/${t}.${n}.js`}else t=`./${t.slice(1).replace(/\//g,"_")}.md.js`;return t}const Vr=Symbol();function Br(){return function(){const e=rn(Vr);if(!e)throw new Error("useRouter() is called without provider.");return e}().route}function zr(e,t,n=!1){const o=document.querySelector(".nav-bar").offsetHeight,r=e.classList.contains(".header-anchor")?e:document.querySelector(decodeURIComponent(t));if(r){const e=r.offsetTop-o-15;!n||Math.abs(e-window.scrollY)>window.innerHeight?window.scrollTo(0,e):window.scrollTo({left:0,top:e,behavior:"smooth"})}}const Dr=pn({name:"VitePressContent",setup(){const e=Br();return()=>e.component?dr(e.component):null}}),Wr=pn({setup(e,{slots:t}){const n=ut(!1);return Cn((()=>{n.value=!0})),()=>n.value&&t.default?t.default():null}});const Hr=ut((qr='{"lang":"zh-CN","title":"vue-element-plus-admin","description":"一套基于vue3、element-plus、typesScript、vite的后台集成方案","base":"/","head":[["meta",{"name":"author","content":"Archer"}],["meta",{"name":"keywords","content":"vue-element-plus-admin, vitejs, vite, element-plus, vue"}],["link",{"rel":"icon","type":"image/svg+xml","href":"/logo.svg"}],["meta",{"name":"viewport","content":"width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"}],["meta",{"name":"keywords","content":"vue-element-plus-admin-doc"}],["link",{"rel":"icon","href":"/favicon.ico"}]],"themeConfig":{"repo":"kailong321200875/vue-element-plus-adminc","docsRepo":"kailong321200875/vue-element-plus-admin-doc","logo":"/logo.png","docsBranch":"master","editLinks":true,"editLinkText":"为此页提供修改建议","nav":[{"text":"指南","link":"/guide/","items":[{"text":"指南","link":"/guide/introduction"},{"text":"深入","link":"/dep/i18n"},{"text":"v2版本重大更新","link":"/guide/version"}]},{"text":"组件","link":"/components/","items":[{"text":"介绍","link":"/components/introduction"},{"text":"全局组件","link":"/components/icon"},{"text":"功能组件","link":"/components/form"},{"text":"函数式组件","link":"/components/image-viewer"}]},{"text":"常用Hooks","link":"/hooks/useWatermark"},{"text":"常见问题","link":"/guide/fqa"},{"text":"相关链接","items":[{"text":"Github 站点预览","link":"https://element-plus-admin.cn/"},{"text":"Github 源码","link":"https://github.com/kailong321200875/vue-element-plus-admin"},{"text":"Github 文档源码","link":"https://github.com/kailong321200875/vue-element-plus-admin-doc"},{"text":"Github 更新日志","link":"https://github.com/kailong321200875/vue-element-plus-admin/blob/master/CHANGELOG.md"},{"text":"Gitee 站点预览","link":"https://kailong110120130.gitee.io/vue-element-plus-admin"},{"text":"Gitee 源码","link":"https://gitee.com/kailong110120130/vue-element-plus-admin"},{"text":"Gitee 文档源码","link":"https://gitee.com/kailong110120130/vue-element-plus-admin-doc"},{"text":"Gitee 更新日志","link":"https://gitee.com/kailong110120130/vue-element-plus-admin/blob/master/CHANGELOG.md"}]},{"text":"交流群","link":"/group/","items":[{"text":"技术交流群","link":"/group/group"}]},{"text":"捐赠","link":"/donate/","items":[{"text":"捐赠","link":"/donate/donate"}]}],"sidebar":{"/group/":[{"text":"技术交流群","link":"/group/group"}],"/donate/":[{"text":"捐赠","link":"/donate/donate"}],"/hooks/":[{"text":"useWatermark","link":"/hooks/useWatermark"},{"text":"useCrudSchemas","link":"/hooks/useCrudSchemas"},{"text":"useTagsView(2.1.0+)","link":"/hooks/useTagsView"},{"text":"useStorage(2.1.0+)","link":"/hooks/useStorage"},{"text":"useClipboard(2.4.0+)","link":"/hooks/useClipboard"},{"text":"useNetwork(2.4.0+)","link":"/hooks/useNetwork"}],"/components/":[{"text":"组件","children":[{"text":"前言","link":"/components/introduction"}]},{"text":"全局组件","children":[{"text":"Icon 图标组件","link":"/components/icon"},{"text":"Permission 权限组件(2.1.0+)","link":"/components/permission"},{"text":"BaseButton 按钮组件(2.5.1+)","link":"/components/button"}]},{"text":"功能组件","children":[{"text":"Form 表单组件","link":"/components/form"},{"text":"Table 表格组件","link":"/components/table"},{"text":"Editor 富文本组件","link":"/components/editor"},{"text":"Search 查询组件","link":"/components/search"},{"text":"Descriptions 描述组件","link":"/components/descriptions"},{"text":"Dialog 弹窗组件","link":"/components/dialog"},{"text":"Echart 图表组件","link":"/components/echart"},{"text":"CountTo 数字动画组件","link":"/components/count-to"},{"text":"Qrcode 二维码组件","link":"/components/qrcode"},{"text":"Highlight 高亮组件","link":"/components/highlight"},{"text":"Infotip 信息提示组件","link":"/components/infotip"},{"text":"Error 缺省组件","link":"/components/error"},{"text":"ContentDetailWrap 详情包裹组件","link":"/components/content-detail-wrap"},{"text":"InputPassword 密码输入框组件","link":"/components/input-password"},{"text":"Footer 页脚组件","link":"/components/footer"},{"text":"JsonEditor JSON编辑器组件(2.2.0+)","link":"/components/json-editor"},{"text":"图标选择器组件(2.3.0+)","link":"/components/icon-picker"},{"text":"瀑布流组件(2.4.0+)","link":"/components/waterfall"},{"text":"视频播放器组件(2.5.0+)","link":"/components/video-player"},{"text":"头像列表组件(2.7.0+)","link":"/components/avatars"},{"text":"我同意组件(2.7.0+)","link":"/components/i-agree"}]},{"text":"函数式组件","children":[{"text":"ImageViewer 图片预览组件","link":"/components/image-viewer"},{"text":"VideoViewer 图片预览组件(2.5.0+)","link":"/components/video-viewer"}]}],"/":[{"text":"指南","children":[{"text":"介绍","link":"/guide/introduction"},{"text":"开始","link":"/guide/"},{"text":"项目配置","link":"/guide/settings"},{"text":"路由","link":"/guide/router"},{"text":"权限","link":"/guide/auth"},{"text":"Mock&联调","link":"/guide/mock"},{"text":"组件注册","link":"/guide/component"},{"text":"样式","link":"/guide/design"},{"text":"构建&部署","link":"/guide/deploy"}]},{"text":"深入","children":[{"text":"国际化","link":"/dep/i18n"},{"text":"项目规范","link":"/dep/lint"},{"text":"黑暗主题","link":"/dep/dark"},{"text":"模版生成","link":"/dep/create-module"}]},{"text":"v2版本重大更新","children":[{"text":"介绍","link":"/guide/version"}]},{"text":"常见问题","children":[{"text":"前言","link":"/guide/fqa"}]}]}},"locales":{},"customData":{}}',tt(JSON.parse(qr))));var qr;function Gr(){return Hr}function Kr(e){const t=e||Br();return ur((()=>Fr(Hr.value,t.path)))}function Jr(e){const t=e||Br();return ur((()=>t.data))}function Yr(e,t){const n=Array.from(document.querySelectorAll("meta"));let o=!0;const r=e=>{o?o=!1:(n.forEach((e=>document.head.removeChild(e))),n.length=0,e&&e.length&&e.forEach((e=>{const t=function([e,t,n]){const o=document.createElement(e);for(const r in t)o.setAttribute(r,t[r]);n&&(o.innerHTML=n);return o}(e);document.head.appendChild(t),n.push(t)})))};var l;cn((()=>{const n=e.data,o=t.value,l=n&&n.title,s=n&&n.description,i=n&&n.frontmatter.head;var c;document.title=(l?l+" | ":"")+o.title,r([["meta",{charset:"utf-8"}],["meta",{name:"viewport",content:"width=device-width,initial-scale=1"}],["meta",{name:"description",content:s||o.description}],...o.head,...i&&(c=i,c.filter((e=>{return!("meta"===(t=e)[0]&&t[1]&&"description"===t[1].name);var t})))||[]])}),null,l)}let Xr;const Qr={};function Zr(){const e=Jr();return ur((()=>e.value.frontmatter))}Jt("data-v-1be840de");const el=Fo("p",{class:"title"},"Debug",-1),tl={class:"block"},nl={class:"block"},ol={class:"block"};Yt();pn({expose:[],setup(e){const t=ut(null),n=ut(!1);return sn(n,(e=>{!1===e&&(t.value.scrollTop=0)})),(e,o)=>(Oo(),Ao("div",{class:["debug",{open:n.value}],ref:t,onClick:o[1]||(o[1]=e=>n.value=!n.value)},[el,Fo("pre",tl,"$page "+g(e.$page),1),Fo("pre",nl,"$siteByRoute "+g(e.$siteByRoute),1),Fo("pre",ol,"$site "+g(e.$site),1)],2))}}).__scopeId="data-v-1be840de";const rl=/#.*$/,ll=/(index)?\.(md|html)$/,sl=/\/$/,il=/^[a-z]+:/i;function cl(e){return Array.isArray(e)}function al(e){return il.test(e)}function ul(e){return decodeURI(e).replace(rl,"").replace(ll,"")}function dl(e){return/^\//.test(e)?e:`/${e}`}function pl(e){return e.replace(/(index)?(\.(md|html))?$/,"")||"/"}function fl(e,t){if(function(e){return!1===e||"auto"===e||cl(e)}(e))return e;t=dl(t);for(const n in e)if(t.startsWith(dl(n)))return e[n];return"auto"}function hl(e){return e.reduce(((e,t)=>(t.link&&e.push({text:t.text,link:pl(t.link)}),function(e){return void 0!==e.children}(t)&&(e=[...e,...hl(t.children)]),e)),[])}const ml={xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink",width:"1.2em",height:"1.2em",preserveAspectRatio:"xMidYMid meet",viewBox:"0 0 24 24"},vl=Fo("path",{d:"M5.883 18.653c-.3-.2-.558-.455-.86-.816a50.32 50.32 0 0 1-.466-.579c-.463-.575-.755-.84-1.057-.949a1 1 0 0 1 .676-1.883c.752.27 1.261.735 1.947 1.588c-.094-.117.34.427.433.539c.19.227.33.365.44.438c.204.137.587.196 1.15.14c.023-.382.094-.753.202-1.095C5.38 15.31 3.7 13.396 3.7 9.64c0-1.24.37-2.356 1.058-3.292c-.218-.894-.185-1.975.302-3.192a1 1 0 0 1 .63-.582c.081-.024.127-.035.208-.047c.803-.123 1.937.17 3.415 1.096A11.731 11.731 0 0 1 12 3.315c.912 0 1.818.104 2.684.308c1.477-.933 2.613-1.226 3.422-1.096c.085.013.157.03.218.05a1 1 0 0 1 .616.58c.487 1.216.52 2.297.302 3.19c.691.936 1.058 2.045 1.058 3.293c0 3.757-1.674 5.665-4.642 6.392c.125.415.19.879.19 1.38a300.492 300.492 0 0 1-.012 2.716a1 1 0 0 1-.019 1.958c-1.139.228-1.983-.532-1.983-1.525l.002-.446l.005-.705c.005-.708.007-1.338.007-1.998c0-.697-.183-1.152-.425-1.36c-.661-.57-.326-1.655.54-1.752c2.967-.333 4.337-1.482 4.337-4.66c0-.955-.312-1.744-.913-2.404a1 1 0 0 1-.19-1.045c.166-.414.237-.957.096-1.614l-.01.003c-.491.139-1.11.44-1.858.949a1 1 0 0 1-.833.135A9.626 9.626 0 0 0 12 5.315c-.89 0-1.772.119-2.592.35a1 1 0 0 1-.83-.134c-.752-.507-1.374-.807-1.868-.947c-.144.653-.073 1.194.092 1.607a1 1 0 0 1-.189 1.045C6.016 7.89 5.7 8.694 5.7 9.64c0 3.172 1.371 4.328 4.322 4.66c.865.097 1.201 1.177.544 1.748c-.192.168-.429.732-.429 1.364v3.15c0 .986-.835 1.725-1.96 1.528a1 1 0 0 1-.04-1.962v-.99c-.91.061-1.662-.088-2.254-.485z",fill:"currentColor"},null,-1);var gl={name:"ri-github-line",render:function(e,t){return Oo(),Ao("svg",ml,[vl])}};const bl={},yl=Xt()(((e,t)=>(Oo(),Ao("a",{class:"nav-bar-title",href:e.$withBase(e.$localePath),"aria-label":`${e.$siteByRoute.title}, back to home`},[e.$themeConfig.logo?(Oo(),Ao("img",{key:0,class:"logo",src:e.$withBase(e.$themeConfig.logo),alt:"Logo"},null,8,["src"])):Bo("v-if",!0),Uo(" "+g(e.$site.title),1)],8,["href","aria-label"]))));function xl(e){const t=Br(),{withBase:n}=function(){const e=Gr();return{withBase:function(t){return Nr(e.value.base,t)}}}(),o=al(e.value.link);return{props:ur((()=>{const r=kl(`/${t.data.relativePath}`);let l=!1;if(e.value.activeMatch)l=new RegExp(e.value.activeMatch).test(r);else{const t=kl(n(e.value.link));l="/"===t?t===r:r.startsWith(t)}return{class:{active:l,isExternal:o},href:o?e.value.link:n(e.value.link),target:e.value.target||o?"_blank":null,rel:e.value.rel||o?"noopener noreferrer":null,"aria-label":e.value.ariaLabel}})),isExternal:o}}function kl(e){return e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\.(html|md)$/,"").replace(/\/index$/,"/")}bl.render=yl,bl.__scopeId="data-v-3e3fd8b1";const _l={},wl={class:"icon outbound",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"},Cl=Fo("path",{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"},null,-1),Sl=Fo("polygon",{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"},null,-1);_l.render=function(e,t){return Oo(),Ao("svg",wl,[Cl,Sl])},Jt("data-v-c272f228");const $l={class:"nav-link"};Yt();var El=pn({expose:[],props:{item:{type:null,required:!0}},setup(e){const t=mt(e),{props:n,isExternal:o}=xl(t.item);return(t,r)=>(Oo(),Ao("div",$l,[Fo("a",Ho({class:"item"},pt(n)),[Uo(g(e.item.text)+" ",1),pt(o)?(Oo(),Ao(_l,{key:0})):Bo("v-if",!0)],16)]))}});El.__scopeId="data-v-c272f228",Jt("data-v-7b16fcd4");const Ol={class:"nav-dropdown-link-item"},Ll=Fo("span",{class:"arrow"},null,-1),Tl={class:"text"},Al={class:"icon"};Yt();var Il=pn({expose:[],props:{item:{type:null,required:!0}},setup(e){const t=mt(e),{props:n,isExternal:o}=xl(t.item);return(t,r)=>(Oo(),Ao("div",Ol,[Fo("a",Ho({class:"item"},pt(n)),[Ll,Fo("span",Tl,g(e.item.text),1),Fo("span",Al,[pt(o)?(Oo(),Ao(_l,{key:0})):Bo("v-if",!0)])],16)]))}});Il.__scopeId="data-v-7b16fcd4",Jt("data-v-312de885");const Ml={class:"button-text"},Pl={class:"dialog"};Yt();var Rl=pn({expose:[],props:{item:{type:null,required:!0}},setup(e){const t=Br(),n=ut(!1);function o(){n.value=!n.value}return sn((()=>t.path),(()=>{n.value=!1})),(t,r)=>(Oo(),Ao("div",{class:["nav-dropdown-link",{open:n.value}]},[Fo("button",{class:"button","aria-label":e.item.ariaLabel,onClick:o},[Fo("span",Ml,g(e.item.text),1),Fo("span",{class:["button-arrow",n.value?"down":"right"]},null,2)],8,["aria-label"]),Fo("ul",Pl,[(Oo(!0),Ao(_o,null,qo(e.item.items,(e=>(Oo(),Ao("li",{key:e.text,class:"dialog-item"},[Fo(Il,{item:e},null,8,["item"])])))),128))])],2))}});Rl.__scopeId="data-v-312de885",Jt("data-v-1e870408");const jl={key:0,class:"nav-links"},Fl={key:1,class:"item"};Yt();var Nl=pn({expose:[],setup(e){const t=Kr(),n=function(){const e=Br(),t=Gr();return ur((()=>{const n=t.value.themeConfig.locales;if(!n)return null;const o=Object.keys(n);if(o.length<=1)return null;const r=Rr?t.value.base:"/",l=r.endsWith("/")?r.slice(0,-1):r,s=e.path.slice(l.length),i=o.find((e=>"/"!==e&&s.startsWith(e))),c=i?s.substring(i.length-1):s,a=o.map((e=>{const t=e.endsWith("/")?e.slice(0,-1):e;return{text:n[e].label,link:`${t}${c}`}})),u=i||"/";return{text:n[u].selectText?n[u].selectText:"Languages",items:a}}))}(),o=ur((()=>r.value||repo.value)),r=ur((()=>t.value.themeConfig.nav));return(e,t)=>pt(o)?(Oo(),Ao("nav",jl,[pt(r)?(Oo(!0),Ao(_o,{key:0},qo(pt(r),(e=>(Oo(),Ao("div",{key:e.text,class:"item"},[e.items?(Oo(),Ao(Rl,{key:0,item:e},null,8,["item"])):(Oo(),Ao(El,{key:1,item:e},null,8,["item"]))])))),128)):Bo("v-if",!0),pt(n)?(Oo(),Ao("div",Fl,[Fo(Rl,{item:pt(n)},null,8,["item"])])):Bo("v-if",!0),Bo('
\n \n
')])):Bo("v-if",!0)}});Nl.__scopeId="data-v-1e870408";const Ul={emits:["toggle"]},Vl=Fo("svg",{class:"icon",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",role:"img",viewBox:"0 0 448 512"},[Fo("path",{fill:"currentColor",d:"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z",class:""})],-1);Ul.render=function(e,t,n,o,r,l){return Oo(),Ao("div",{class:"sidebar-button",onClick:t[1]||(t[1]=t=>e.$emit("toggle"))},[Vl])};const Bl={xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink",width:"1.2em",height:"1.2em",preserveAspectRatio:"xMidYMid meet",viewBox:"0 0 24 24"},zl=Fo("path",{d:"M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938A7.999 7.999 0 0 0 4 12z",fill:"currentColor"},null,-1);var Dl={name:"ri-moon-line",render:function(e,t){return Oo(),Ao("svg",Bl,[zl])}};const Wl={xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink",width:"1.2em",height:"1.2em",preserveAspectRatio:"xMidYMid meet",viewBox:"0 0 24 24"},Hl=Fo("path",{d:"M12 18a6 6 0 1 1 0-12a6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8a4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636L5.636 7.05L3.515 4.93zM16.95 18.364l1.414-1.414l2.121 2.121l-1.414 1.414l-2.121-2.121zm2.121-14.85l1.414 1.415l-2.121 2.121l-1.414-1.414l2.121-2.121zM5.636 16.95l1.414 1.414l-2.121 2.121l-1.414-1.414l2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z",fill:"currentColor"},null,-1);var ql={name:"ri-sun-line",render:function(e,t){return Oo(),Ao("svg",Wl,[Hl])}}; +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */const Gl="undefined"!=typeof window,Kl=()=>{};const Jl=e=>e();function Yl(e,t,n={}){const{eventFilter:o=Jl}=n,r=function(e,t){var n={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&t.indexOf(o)<0&&(n[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var r=0;for(o=Object.getOwnPropertySymbols(e);rs.apply(this,e)),{fn:s,thisArg:this,args:e})}),r);var l,s}function Xl(e){nr()&&On(e)}const Ql=Gl?window:void 0;const Zl={boolean:{read:e=>null!=e?"true"===e:null,write:e=>String(e)},object:{read:e=>e?JSON.parse(e):null,write:e=>JSON.stringify(e)},number:{read:e=>null!=e?Number.parseFloat(e):null,write:e=>String(e)},any:{read:e=>null!=e?e:null,write:e=>String(e)},string:{read:e=>null!=e?e:null,write:e=>String(e)}};function es(e,t,n=(null==Ql?void 0:Ql.localStorage),o={}){var r;const{flush:l="pre",deep:s=!0,listenToStorageChanges:i=!0,window:c=Ql,eventFilter:a}=o,u=ut(t),d=null==t?"any":"boolean"==typeof t?"boolean":"string"==typeof t?"string":"object"==typeof t||Array.isArray(t)?"object":Number.isNaN(t)?"any":"number",p=null!==(r=o.serializer)&&void 0!==r?r:Zl[d];function f(o){if(n&&(!o||o.key===e))try{const r=o?o.newValue:n.getItem(e);null==r?(u.value=t,n.setItem(e,p.write(t))):u.value=p.read(r)}catch(r){console.warn(r)}}return f(),c&&i&&function(...e){let t,n,o,r;if("string"==typeof e[0]?([n,o,r]=e,t=Ql):[t,n,o,r]=e,!t)return Kl;let l=Kl;const s=sn((()=>pt(t)),(e=>{l(),e&&(e.addEventListener(n,o,r),l=()=>{e.removeEventListener(n,o,r),l=Kl})}),{immediate:!0,flush:"post"}),i=()=>{s(),l()};Xl(i)}(c,"storage",f),Yl(u,(()=>{if(n)try{null==u.value?n.removeItem(e):n.setItem(e,p.write(u.value))}catch(t){console.warn(t)}}),{flush:l,deep:s,eventFilter:a}),u}function ts(e){return function(e,t={}){const{window:n=Ql}=t;if(!n)return ut(!1);const o=n.matchMedia(e),r=ut(o.matches),l=e=>{r.value=e.matches};return"addEventListener"in o?o.addEventListener("change",l):o.addListener(l),Xl((()=>{"removeEventListener"in o?o.removeEventListener("change",l):o.removeListener(l)})),r}("(prefers-color-scheme: dark)",e)}var ns,os;(os=ns||(ns={})).UP="UP",os.RIGHT="RIGHT",os.DOWN="DOWN",os.LEFT="LEFT",os.NONE="NONE";const rs=function(e={}){const{selector:t="html",attribute:n="class",valueDark:o="dark",valueLight:r="",window:l=Ql,storage:s=(null==Ql?void 0:Ql.localStorage),storageKey:i="vueuse-color-scheme",listenToStorageChanges:c=!0}=e,a=ts({window:l}),u=null==i?ut("auto"):es(i,"auto",s,{window:l,listenToStorageChanges:c}),d=ur({get:()=>"auto"===u.value?a.value:"dark"===u.value,set(e){e===a.value?u.value="auto":u.value=e?"dark":"light"}}),p=e.onChanged||(e=>{const s=null==l?void 0:l.document.querySelector(t);"class"===n?(null==s||s.classList.toggle(o,e),r&&(null==s||s.classList.toggle(r,!e))):null==s||s.setAttribute(n,e?o:r)});return sn(d,p,{flush:"post"}),function(e,t=!0){nr()?Cn(e):t?e():Rt(e)}((()=>p(d.value))),d}({storageKey:"vben-admin-color-scheme",valueLight:"light"});var ls=pn({expose:[],setup(e){const t=function(e=!1){if(at(e))return()=>e.value=!e.value;{const t=ut(e),n=()=>t.value=!t.value;return[t,n]}}(rs);return(e,n)=>{const o=Dl,r=ql;return Oo(),Ao("button",{class:"nav-btn","aria-label":"Toggle Theme",onClick:n[1]||(n[1]=(...e)=>pt(t)&&pt(t)(...e))},[oo(Fo(o,null,null,512),[[Lr,pt(rs)]]),oo(Fo(r,null,null,512),[[Lr,!pt(rs)]])])}}});const ss=["GitHub","GitLab","Bitbucket"].map((e=>[e,new RegExp(e,"i")]));function is(){const e=Kr();return ur((()=>{const t=e.value.themeConfig,n=t.docsRepo||t.repo;if(!n)return null;const o=/^https?:/.test(r=n)?r:`https://github.com/${r}`;var r;return{text:function(e,t){if(t)return t;const n=e.match(/^https?:\/\/[^/]+/);if(!n)return"Source";const o=ss.find((([e,t])=>t.test(n[0])));if(o&&o[0])return o[0];return"Source"}(o,t.repoLabel),link:o}}))}Jt("data-v-47437b3e");const cs={class:"nav-bar"},as=Fo("div",{class:"flex-grow"},null,-1),us={class:"nav"},ds={class:"nav-icons"},ps={class:"item"},fs={key:0,class:"item"},hs={class:"nav-btn",href:"https://github.com/kailong321200875/vue-element-plus-admin",target:"_blank","aria-label":"View GitHub Repo"};Yt();var ms=pn({expose:[],emits:["toggle"],setup(e){const t=is();return(e,n)=>{const o=gl;return Oo(),Ao("header",cs,[Fo(Ul,{onToggle:n[1]||(n[1]=t=>e.$emit("toggle"))}),Fo(bl),as,Fo("div",us,[Fo(Nl)]),Fo("div",ds,[Fo("div",ps,[Fo(ls)]),pt(t)?(Oo(),Ao("div",fs,[Fo("a",hs,[Fo(o)])])):Bo("v-if",!0)]),Go(e.$slots,"search",{},void 0,!0)])}}});function vs(){let e=null,t=null;const n=function(e,t){let n,o=!1;return()=>{n&&clearTimeout(n),o?n=setTimeout(e,t):(e(),o=!0,setTimeout((()=>{o=!1}),t))}}(o,300);function o(){const e=function(e){return[].slice.call(document.querySelectorAll(".header-anchor")).filter((t=>e.some((e=>e.hash===t.hash))))}([].slice.call(document.querySelectorAll(".sidebar a.sidebar-link-item")));for(let t=0;t ul > li");o&&o!==t.parentElement?(e=o.querySelector("a"),e&&e.classList.add("active")):e=null}function l(e){e&&e.classList.remove("active")}Cn((()=>{o(),window.addEventListener("scroll",n)})),$n((()=>{r(decodeURIComponent(location.hash))})),On((()=>{window.removeEventListener("scroll",n)}))}function gs(e){const t=document.querySelector(".nav-bar").offsetHeight;return e.parentElement.offsetTop-t-15}function bs(e,t,n){const o=window.scrollY;return 0===e&&0===o?[!0,null]:o{if(e-1>t)return;const s={text:r,link:`#${l}`};2===e?(o=s,n.push(s)):o&&(o.children||(o.children=[])).push(s)})),n}ms.__scopeId="data-v-47437b3e";const xs=e=>{const t=Br(),n=Gr(),o=t.data.headers,r=e.item.text,l=function(e,t){if(void 0===t)return t;if(t.startsWith("#"))return t;return function(e,t){const n=e.endsWith("/"),o=t.startsWith("/");return n&&o?e.slice(0,-1)+t:n||o?e+t:`${e}/${t}`}(e,t)}(n.value.base,e.item.link),s=e.item.children,i=function(e,t){return void 0!==t&&ul(`/${e.data.relativePath}`)===ul(t)}(t,e.item.link),c=ks(i,s,o);return dr("li",{class:"sidebar-link"},[dr(l?"a":"p",{class:{"sidebar-link-item":!0,active:i},href:l},r),c])};function ks(e,t,n){return t&&t.length>0?dr("ul",{class:"sidebar-links"},t.map((e=>dr(xs,{item:e})))):e&&n?ks(!1,function(e){return _s(function(e){let t;return(e=e.map((e=>Object.assign({},e)))).forEach((e=>{2===e.level?t=e:t&&(t.children||(t.children=[])).push(e)})),e.filter((e=>2===e.level))}(e))}(n)):null}function _s(e){return e.map((e=>({text:e.title,link:`#${e.slug}`,children:e.children?_s(e.children):void 0})))}const ws={key:0,class:"sidebar-links"};var Cs=pn({expose:[],setup(e){const t=function(){const e=Br(),t=Kr();return vs(),ur((()=>{const n=e.data.headers,o=e.data.frontmatter.sidebar,r=e.data.frontmatter.sidebarDepth;if(!1===o)return[];if("auto"===o)return ys(n,r);const l=fl(t.value.themeConfig.sidebar,e.data.relativePath);return!1===l?[]:"auto"===l?ys(n,r):l}))}();return(e,n)=>pt(t).length>0?(Oo(),Ao("ul",ws,[(Oo(!0),Ao(_o,null,qo(pt(t),(e=>(Oo(),Ao(pt(xs),{key:e.text,item:e},null,8,["item"])))),128))])):Bo("v-if",!0)}}),Ss=pn({expose:[],props:{open:{type:Boolean,required:!0}},setup:e=>(t,n)=>(Oo(),Ao(_o,null,[Fo("aside",{class:["sidebar",{open:e.open}]},[Fo(Nl,{class:"nav"}),Go(t.$slots,"sidebar-top",{},void 0,!0),Fo(Cs),Go(t.$slots,"sidebar-bottom",{},void 0,!0)],2),Bo(" ")],2112))});Ss.__scopeId="data-v-4668b452";const $s=/bitbucket.org/;function Es(){const e=Kr(),t=Jr();return{url:ur((()=>{const n=null==t.value.frontmatter.editLink?e.value.themeConfig.editLinks:t.value.frontmatter.editLink;const{repo:o,docsDir:r="",docsBranch:l="master",docsRepo:s=o}=e.value.themeConfig,{relativePath:i}=t.value;return n&&i&&o?function(e,t,n,o,r){return $s.test(e)?function(e,t,n,o,r){return(al(t)?t:e).replace(sl,"")+`/src/${o}/`+(n?n.replace(sl,"")+"/":"")+r+`?mode=edit&spa=0&at=${o}&fileviewer=file-view-default`}(e,t,n,o,r):function(e,t,n,o,r){return(al(t)?t:`https://github.com/${t}`).replace(sl,"")+`/edit/${o}/`+(n?n.replace(sl,"")+"/":"")+r}(0,t,n,o,r)}(o,s,r,l,i):null})),text:ur((()=>e.value.themeConfig.editLinkText||"Edit this page"))}}Jt("data-v-045573c2");const Os={class:"edit-link"};Yt();var Ls=pn({expose:[],setup(e){const{url:t,text:n}=Es();return(e,o)=>(Oo(),Ao("div",Os,[pt(t)?(Oo(),Ao("a",{key:0,class:"link",href:pt(t),target:"_blank",rel:"noopener noreferrer"},[Uo(g(pt(n))+" ",1),Fo(_l,{class:"icon"})],8,["href"])):Bo("v-if",!0)]))}});Ls.__scopeId="data-v-045573c2",Jt("data-v-03e55a27");const Ts={key:0,class:"last-updated"},As={class:"prefix"},Is={class:"datetime"};Yt();var Ms=pn({expose:[],setup(e){const t=Kr(),n=Jr(),o=ur((()=>{const e=t.value.themeConfig.lastUpdated;return void 0!==e&&!1!==e})),r=ur((()=>{const e=t.value.themeConfig.lastUpdated;return!0===e?"Last Updated":e})),l=ut("");return Cn((()=>{l.value=new Date(n.value.lastUpdated).toLocaleString("en-US")})),(e,t)=>pt(o)?(Oo(),Ao("p",Ts,[Fo("span",As,g(pt(r))+":",1),Fo("span",Is,g(l.value),1)])):Bo("v-if",!0)}});Ms.__scopeId="data-v-03e55a27",Jt("data-v-22e60b1a");const Ps={class:"page-footer"},Rs={class:"edit"},js={class:"updated"};Yt();var Fs=pn({expose:[],setup:e=>(e,t)=>(Oo(),Ao("footer",Ps,[Fo("div",Rs,[Fo(Ls)]),Fo("div",js,[Fo(Ms)])]))});function Ns(){const e=Kr(),t=Jr(),n=ur((()=>pl(dl(t.value.relativePath)))),o=ur((()=>{const t=fl(e.value.themeConfig.sidebar,n.value);return cl(t)?hl(t):[]})),r=ur((()=>o.value.findIndex((e=>e.link===n.value)))),l=ur((()=>{if(!1!==e.value.themeConfig.nextLinks&&r.value>-1&&r.value{if(!1!==e.value.themeConfig.prevLinks&&r.value>0)return o.value[r.value-1]})),i=ur((()=>!!l.value||!!s.value));return{next:l,prev:s,hasLinks:i}}Fs.__scopeId="data-v-22e60b1a";const Us={},Vs={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},Bs=Fo("path",{d:"M19,11H7.4l5.3-5.3c0.4-0.4,0.4-1,0-1.4s-1-0.4-1.4,0l-7,7c-0.1,0.1-0.2,0.2-0.2,0.3c-0.1,0.2-0.1,0.5,0,0.8c0.1,0.1,0.1,0.2,0.2,0.3l7,7c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3c0.4-0.4,0.4-1,0-1.4L7.4,13H19c0.6,0,1-0.4,1-1S19.6,11,19,11z"},null,-1);Us.render=function(e,t){return Oo(),Ao("svg",Vs,[Bs])};const zs={},Ds={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},Ws=Fo("path",{d:"M19.9,12.4c0.1-0.2,0.1-0.5,0-0.8c-0.1-0.1-0.1-0.2-0.2-0.3l-7-7c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.3,5.3H5c-0.6,0-1,0.4-1,1s0.4,1,1,1h11.6l-5.3,5.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l7-7C19.8,12.6,19.9,12.5,19.9,12.4z"},null,-1);zs.render=function(e,t){return Oo(),Ao("svg",Ds,[Ws])},Jt("data-v-0facf926");const Hs={key:0,class:"next-and-prev-link"},qs={class:"container"},Gs={class:"prev"},Ks={class:"text"},Js={class:"next"},Ys={class:"text"};Yt();var Xs=pn({expose:[],setup(e){const{hasLinks:t,prev:n,next:o}=Ns();return(e,r)=>pt(t)?(Oo(),Ao("div",Hs,[Fo("div",qs,[Fo("div",Gs,[pt(n)?(Oo(),Ao("a",{key:0,class:"link",href:e.$withBase(pt(n).link)},[Fo(Us,{class:"icon icon-prev"}),Fo("span",Ks,g(pt(n).text),1)],8,["href"])):Bo("v-if",!0)]),Fo("div",Js,[pt(o)?(Oo(),Ao("a",{key:0,class:"link",href:e.$withBase(pt(o).link)},[Fo("span",Ys,g(pt(o).text),1),Fo(zs,{class:"icon icon-next"})],8,["href"])):Bo("v-if",!0)])])])):Bo("v-if",!0)}});Xs.__scopeId="data-v-0facf926",Jt("data-v-7abc59e6");const Qs={class:"page"},Zs={class:"container"},ei={class:"content"};Yt();var ti=pn({expose:[],setup:e=>(e,t)=>{const n=yo("Content");return Oo(),Ao("main",Qs,[Fo("div",Zs,[Go(e.$slots,"top",{},void 0,!0),Fo("div",ei,[Fo(n)]),Fo(Fs),Fo(Xs),Go(e.$slots,"bottom",{},void 0,!0)])])}});ti.__scopeId="data-v-7abc59e6";const ni={key:0,id:"ads-container"};var oi=pn({expose:[],setup(e){const t=hn((()=>function(e,t){if(!t)return e();if(void 0===Xr){const e=document.createElement("link").relList;Xr=e&&e.supports&&e.supports("modulepreload")?"modulepreload":"preload"}return Promise.all(t.map((e=>{if(e in Qr)return;Qr[e]=!0;const t=e.endsWith(".css"),n=t?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${e}"]${n}`))return;const o=document.createElement("link");return o.rel=t?"stylesheet":Xr,t||(o.as="script",o.crossOrigin=""),o.href=e,document.head.appendChild(o),t?new Promise(((e,t)=>{o.addEventListener("load",e),o.addEventListener("error",t)})):void 0}))).then((()=>e()))}((()=>import("./Home.3ecc1b19.js")),void 0))),n=()=>null,o=n,r=n,l=n,s=Br(),i=Gr(),c=Kr(),a=ur((()=>i.value.themeConfig)),u=Jr(),d=ur((()=>!!s.data.frontmatter.customLayout)),p=ur((()=>!!s.data.frontmatter.home)),f=ur((()=>{const{themeConfig:e}=c.value,{frontmatter:t}=s.data;return!1!==t.navbar&&!1!==e.navbar&&(i.value.title||e.logo||e.repo||e.nav)})),h=ut(!1),m=ur((()=>{const{frontmatter:e}=s.data;if(e.home||!1===e.sidebar)return!1;const{themeConfig:t}=c.value;return!(cl(n=fl(t.sidebar,s.data.relativePath))?0===n.length:!n);var n})),v=e=>{h.value="boolean"==typeof e?e:!h.value},g=v.bind(null,!1);sn(s,g);const b=ur((()=>[{"no-navbar":!f.value,"sidebar-open":h.value,"no-sidebar":!m.value}]));return(e,n)=>{const s=yo("Content"),i=yo("Debug");return Oo(),Ao(_o,null,[Fo("div",{class:["theme",pt(b)]},[pt(f)?(Oo(),Ao(ms,{key:0,onToggle:v},{search:Qt((()=>[Go(e.$slots,"navbar-search",{},(()=>[pt(a).algolia?(Oo(),Ao(pt(l),{options:pt(a).algolia,multilang:!!pt(a).locales,key:pt(c).lang},null,8,["options","multilang"])):Bo("v-if",!0)]))])),_:1})):Bo("v-if",!0),Fo(Ss,{open:h.value},{"sidebar-top":Qt((()=>[Go(e.$slots,"sidebar-top")])),"sidebar-bottom":Qt((()=>[Go(e.$slots,"sidebar-bottom")])),_:1},8,["open"]),Bo(" TODO: make this button accessible "),Fo("div",{class:"sidebar-mask",onClick:n[1]||(n[1]=e=>v(!1))}),pt(d)?(Oo(),Ao(s,{key:1})):pt(p)?(Oo(),Ao(pt(t),{key:2},{hero:Qt((()=>[Go(e.$slots,"home-hero")])),features:Qt((()=>[Go(e.$slots,"home-features")])),footer:Qt((()=>[Go(e.$slots,"home-footer")])),_:1})):(Oo(),Ao(ti,{key:3},{top:Qt((()=>[Go(e.$slots,"page-top-ads",{},(()=>[pt(a).carbonAds&&pt(a).carbonAds.carbon?(Oo(),Ao("div",ni,[Fo(pt(o),{key:"carbon"+pt(u).relativePath,code:pt(a).carbonAds.carbon,placement:pt(a).carbonAds.placement},null,8,["code","placement"])])):Bo("v-if",!0)])),Go(e.$slots,"page-top")])),bottom:Qt((()=>[Go(e.$slots,"page-bottom"),Go(e.$slots,"page-bottom-ads",{},(()=>[pt(a).carbonAds&&pt(a).carbonAds.custom?(Oo(),Ao(pt(r),{key:"custom"+pt(u).relativePath,code:pt(a).carbonAds.custom,placement:pt(a).carbonAds.placement},null,8,["code","placement"])):Bo("v-if",!0)]))])),_:1}))],2),Fo(i)],64)}}});const ri={class:"theme"},li=Fo("h1",null,"404",-1);var si=pn({expose:[],setup(e){const t=["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links."];return(e,n)=>(Oo(),Ao("div",ri,[li,Fo("blockquote",null,g(t[Math.floor(Math.random()*t.length)]),1),Fo("a",{href:e.$site.base,"aria-label":"go to home"},"Take me home.",8,["href"])]))}});var ii=i({},{Layout:oi,NotFound:si});const ci=new Set,ai=()=>document.createElement("link");let ui;const di=Rr&&(ui=ai())&&ui.relList&&ui.relList.supports&&ui.relList.supports("prefetch")?e=>{const t=ai();t.rel="prefetch",t.href=e,document.head.appendChild(t)}:e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};const pi=ii.NotFound||(()=>"404 Not Found"),fi={name:"VitePressApp",setup(){const e=Kr();return Cn((()=>{sn((()=>e.value.lang),(e=>{document.documentElement.lang=e}),{immediate:!0})})),function(){if(!Rr)return;if(!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const o=()=>{n&&n.disconnect(),n=new IntersectionObserver((e=>{e.forEach((e=>{if(e.isIntersecting){const t=e.target;n.unobserve(t);const{pathname:o}=t;if(!ci.has(o)){ci.add(o);const e=Ur(o);di(e)}}}))})),t((()=>{document.querySelectorAll("#app a").forEach((e=>{const{target:t,hostname:o,pathname:r}=e,l=r.match(/\.\w+$/);l&&".html"!==l[0]||"_blank"!==t&&o===location.hostname&&(r!==location.pathname?n.observe(e):ci.add(r))}))}))};Cn(o);const r=Br();sn((()=>r.path),o),On((()=>{n&&n.disconnect()}))}(),()=>dr(ii.Layout)}};function hi(){const e=function(){let e,t=Rr;return function(e,t){const n=et({path:"/",component:null,data:null});function o(e=(Rr?location.href:"/")){const t=new URL(e,"http://a.com");return t.pathname.endsWith("/")||t.pathname.endsWith(".html")||(t.pathname+=".html",e=t.pathname+t.search+t.hash),Rr&&(history.replaceState({scrollPosition:window.scrollY},document.title),history.pushState(null,"",e)),l(e)}let r=null;async function l(o,l=0){const s=new URL(o,"http://a.com"),i=r=s.pathname;try{let t=e(i);if("then"in t&&"function"==typeof t.then&&(t=await t),r===i){r=null;const{default:e,__pageData:o}=t;if(!e)throw new Error(`Invalid route component: ${e}`);n.path=i,n.component=it(e),n.data=tt(JSON.parse(o)),Rr&&Rt((()=>{if(s.hash&&!l){const e=document.querySelector(decodeURIComponent(s.hash));if(e)return void zr(e,s.hash)}window.scrollTo(0,l)}))}}catch(c){c.message.match(/fetch/)||console.error(c),r===i&&(r=null,n.path=i,n.component=t?it(t):null)}}return Rr&&(window.addEventListener("click",(e=>{const t=e.target.closest("a");if(t){const{href:n,protocol:r,hostname:l,pathname:s,hash:i,target:c}=t,a=window.location,u=s.match(/\.\w+$/);e.ctrlKey||e.shiftKey||e.altKey||e.metaKey||"_blank"===c||r!==a.protocol||l!==a.hostname||u&&".html"!==u[0]||(e.preventDefault(),s===a.pathname?i&&i!==a.hash&&(history.pushState(null,"",i),zr(t,i,t.classList.contains("header-anchor"))):o(n))}}),{capture:!0}),window.addEventListener("popstate",(e=>{l(location.href,e.state&&e.state.scrollPosition||0)})),window.addEventListener("hashchange",(e=>{e.preventDefault()}))),{route:n,go:o}}((n=>{let o=Ur(n);return t&&(e=o),(t||e===o)&&(o=o.replace(/\.js$/,".lean.js")),Rr?(t=!1,import(o)):require(o)}),pi)}(),t=Pr(fi);t.provide(Vr,e);const n=Kr(e.route),o=Jr(e.route);return Rr&&Yr(e.route,n),function(e,t,n,o){Object.defineProperties(e.config.globalProperties,{$site:{get:()=>t.value},$siteByRoute:{get:()=>n.value},$themeConfig:{get:()=>n.value.themeConfig},$page:{get:()=>o.value},$frontmatter:{get:()=>o.value.frontmatter},$lang:{get:()=>n.value.lang},$localePath:{get(){const{locales:e}=t.value,{lang:o}=n.value,r=Object.keys(e).find((t=>e[t].lang===o));return e&&r||"/"}},$title:{get:()=>o.value.title?o.value.title+" | "+n.value.title:n.value.title},$description:{get:()=>o.value.description||n.value.description},$withBase:{value:e=>Nr(t.value.base,e)}})}(t,Hr,n,o),function(e){e.component("Content",Dr),e.component("ClientOnly",Wr),e.component("Debug",(()=>null))}(t),ii.enhanceApp&&ii.enhanceApp({app:t,router:e,siteData:Hr}),{app:t,router:e}}if(Rr){const{app:e,router:t}=hi();t.go().then((()=>{e.mount("#app")}))}export{_o as F,El as _,Vo as a,Fo as b,Ao as c,hi as createApp,Uo as d,Bo as e,pn as f,Zr as g,ur as h,pt as i,Yt as j,yo as k,Go as l,Oo as o,Jt as p,qo as r,g as t,Kr as u,Xt as w}; diff --git a/assets/components_avatars.md.c10b1c02.js b/assets/components_avatars.md.c10b1c02.js new file mode 100644 index 00000000..4ed27fa8 --- /dev/null +++ b/assets/components_avatars.md.c10b1c02.js @@ -0,0 +1 @@ +import{o as a,c as n,a as s}from"./app.7e863e47.js";const t='{"title":"Avatars 头像列表","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Avatars 属性","slug":"avatars-属性"},{"level":3,"title":"data","slug":"data"}],"relativePath":"components/avatars.md","lastUpdated":1718353615647}',p={},o=s('

Avatars 头像列表

展示多个头像集合

Avatars 组件位于 src/components/Avatars

用法

<script lang="ts" setup>\nimport { Avatars } from '@/components/Avatars'\n\nconst data = ref<AvatarItem[]>([\n  {\n    name: 'Lily',\n    url: 'https://avatars.githubusercontent.com/u/3459374?v=4'\n  },\n  {\n    name: 'Amanda',\n    url: 'https://avatars.githubusercontent.com/u/3459375?v=4'\n  },\n  {\n    name: 'Daisy',\n    url: 'https://avatars.githubusercontent.com/u/3459376?v=4'\n  },\n  {\n    name: 'Olivia',\n    url: 'https://avatars.githubusercontent.com/u/3459377?v=4'\n  },\n  {\n    name: 'Tina',\n    url: 'https://avatars.githubusercontent.com/u/3459378?v=4'\n  },\n  {\n    name: 'Kitty',\n    url: 'https://avatars.githubusercontent.com/u/3459323?v=4'\n  },\n  {\n    name: 'Helen',\n    url: 'https://avatars.githubusercontent.com/u/3459324?v=4'\n  },\n  {\n    name: 'Sophia',\n    url: 'https://avatars.githubusercontent.com/u/3459325?v=4'\n  },\n  {\n    name: 'Wendy',\n    url: 'https://avatars.githubusercontent.com/u/3459326?v=4'\n  }\n])\n</script>\n\n<template>\n  <Avatars :data="data" />\n</template>\n\n

Avatars 属性

属性说明类型可选值默认值
size头像尺寸ComponentSize、number--
max最大展示个数number-5
data头像数据,详见AvatarItem[]--
showTooltip是否展示名称tipboolean-true

data

属性说明类型可选值默认值
url头像图片地址string--
name名称,非必填string--
',9);p.render=function(s,t,p,e,c,r){return a(),n("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_avatars.md.c10b1c02.lean.js b/assets/components_avatars.md.c10b1c02.lean.js new file mode 100644 index 00000000..82a7a71a --- /dev/null +++ b/assets/components_avatars.md.c10b1c02.lean.js @@ -0,0 +1 @@ +import{o as a,c as n,a as s}from"./app.7e863e47.js";const t='{"title":"Avatars 头像列表","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Avatars 属性","slug":"avatars-属性"},{"level":3,"title":"data","slug":"data"}],"relativePath":"components/avatars.md","lastUpdated":1718353615647}',p={},o=s('',9);p.render=function(s,t,p,e,c,r){return a(),n("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_button.md.4b3c0c5f.js b/assets/components_button.md.4b3c0c5f.js new file mode 100644 index 00000000..dbc84274 --- /dev/null +++ b/assets/components_button.md.4b3c0c5f.js @@ -0,0 +1 @@ +import{o as a,c as t,a as n}from"./app.7e863e47.js";const s='{"title":"BaseButton 按钮组件","description":"","frontmatter":{},"headers":[{"level":3,"title":"基本用法","slug":"基本用法"},{"level":2,"title":"BaseButton 属性","slug":"basebutton-属性"}],"relativePath":"components/button.md","lastUpdated":1718353615647}',e={},o=n('

BaseButton 按钮组件

二次封装 ElButton ,支持修改主题色

BaseButton 组件位于 src/components/Button

BaseButton 已经全局引入,无需在手动引入

基本用法

<template>\n  <BaseButton type="primary"> Add </BaseButton>\n</template>\n\n

BaseButton 属性

支持 ElButton 的所有属性

',8);e.render=function(n,s,e,p,u,c){return a(),t("div",null,[o])};export default e;export{s as __pageData}; diff --git a/assets/components_button.md.4b3c0c5f.lean.js b/assets/components_button.md.4b3c0c5f.lean.js new file mode 100644 index 00000000..4f4780e1 --- /dev/null +++ b/assets/components_button.md.4b3c0c5f.lean.js @@ -0,0 +1 @@ +import{o as a,c as t,a as n}from"./app.7e863e47.js";const s='{"title":"BaseButton 按钮组件","description":"","frontmatter":{},"headers":[{"level":3,"title":"基本用法","slug":"基本用法"},{"level":2,"title":"BaseButton 属性","slug":"basebutton-属性"}],"relativePath":"components/button.md","lastUpdated":1718353615647}',e={},o=n('',8);e.render=function(n,s,e,p,u,c){return a(),t("div",null,[o])};export default e;export{s as __pageData}; diff --git a/assets/components_content-detail-wrap.md.dfda79ff.js b/assets/components_content-detail-wrap.md.dfda79ff.js new file mode 100644 index 00000000..235f3667 --- /dev/null +++ b/assets/components_content-detail-wrap.md.dfda79ff.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.7e863e47.js";const s='{"title":"ContentDetailWrap 详情包裹组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"ContentDetailWrap 属性","slug":"contentdetailwrap-属性"},{"level":2,"title":"ContentDetailWrap 事件","slug":"contentdetailwrap-事件"},{"level":2,"title":"ContentDetailWrap 插槽","slug":"contentdetailwrap-插槽"}],"relativePath":"components/content-detail-wrap.md","lastUpdated":1718353615647}',e={},p=n('

ContentDetailWrap 详情包裹组件

1.2.4 新增

用于展示详情,自带返回按钮。

ContentDetailWrap 组件位于 src/components/ContentDetailWrap

用法

<script setup lang="ts">\nimport { ContentDetailWrap } from '@/components/ContentDetailWrap'\n</script>\n\n<template>\n  <ContentDetailWrap title="详情" @back="push('/example/example-page')">\n    Details\n  </ContentDetailWrap>\n</template>\n\n

ContentDetailWrap 属性

属性说明类型可选值默认值
title标题string--

ContentDetailWrap 事件

方法名说明回调参数
back返回事件-

ContentDetailWrap 插槽

插槽名说明子标签
-默认展示内容-
title自定义标题内容-
right自定义右侧内容-
',12);e.render=function(n,s,e,o,l,c){return t(),a("div",null,[p])};export default e;export{s as __pageData}; diff --git a/assets/components_content-detail-wrap.md.dfda79ff.lean.js b/assets/components_content-detail-wrap.md.dfda79ff.lean.js new file mode 100644 index 00000000..9c793c8f --- /dev/null +++ b/assets/components_content-detail-wrap.md.dfda79ff.lean.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.7e863e47.js";const s='{"title":"ContentDetailWrap 详情包裹组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"ContentDetailWrap 属性","slug":"contentdetailwrap-属性"},{"level":2,"title":"ContentDetailWrap 事件","slug":"contentdetailwrap-事件"},{"level":2,"title":"ContentDetailWrap 插槽","slug":"contentdetailwrap-插槽"}],"relativePath":"components/content-detail-wrap.md","lastUpdated":1718353615647}',e={},p=n('',12);e.render=function(n,s,e,o,l,c){return t(),a("div",null,[p])};export default e;export{s as __pageData}; diff --git a/assets/components_count-to.md.cbfc4417.js b/assets/components_count-to.md.cbfc4417.js new file mode 100644 index 00000000..15c4b763 --- /dev/null +++ b/assets/components_count-to.md.cbfc4417.js @@ -0,0 +1 @@ +import{o as t,c as n,a}from"./app.7e863e47.js";const s='{"title":"CountTo 数字动画组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"CountTo 属性","slug":"countto-属性"}],"relativePath":"components/count-to.md","lastUpdated":1718353615647}',e={},o=a('

CountTo 数字动画组件

基于 vue-count-to 改造

CountTo 组件位于 src/components/CountTo

用法

更复杂点的例子,请在线预览

<script setup lang="ts">\nimport { CountTo } from '@/components/CountTo'\n</script>\n\n<template>\n  <CountTo :start-val="0" :end-val="35225" />\n</template>\n\n

CountTo 属性

属性说明类型可选值默认值
startVal初始值number-0
endVal最后展示的值number-2021
duration动画时间number-3000
autoplay是否自动播放boolean-true
decimals小位数number-0
decimal小位数分割符号string-.
separator分割符号string-,
prefix前缀string--
suffix后缀string--
useEasing过渡动画boolean-true
easingFn自定义动画效果(t: number, b: number, c: number, d: number) => number--
',8);e.render=function(a,s,e,d,p,c){return t(),n("div",null,[o])};export default e;export{s as __pageData}; diff --git a/assets/components_count-to.md.cbfc4417.lean.js b/assets/components_count-to.md.cbfc4417.lean.js new file mode 100644 index 00000000..01de3f79 --- /dev/null +++ b/assets/components_count-to.md.cbfc4417.lean.js @@ -0,0 +1 @@ +import{o as t,c as n,a}from"./app.7e863e47.js";const s='{"title":"CountTo 数字动画组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"CountTo 属性","slug":"countto-属性"}],"relativePath":"components/count-to.md","lastUpdated":1718353615647}',e={},o=a('',8);e.render=function(a,s,e,d,p,c){return t(),n("div",null,[o])};export default e;export{s as __pageData}; diff --git a/assets/components_descriptions.md.83f44587.js b/assets/components_descriptions.md.83f44587.js new file mode 100644 index 00000000..3d9c19eb --- /dev/null +++ b/assets/components_descriptions.md.83f44587.js @@ -0,0 +1 @@ +import{o as n,c as s,a as t}from"./app.7e863e47.js";const a='{"title":"Descriptions 描述组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Descriptions 属性","slug":"descriptions-属性"},{"level":3,"title":"Schema","slug":"schema"}],"relativePath":"components/descriptions.md","lastUpdated":1718353615647}',e={},p=t('

Descriptions 描述组件

注意

从 v2.5.3之后,Descriptions 组件不再基于 element-plusDescriptions 进行二次封装,所以可能有的属性无法使用,具体可以自行修改或者改造,或者可以提issue。

element-plusDescriptions 组件进行封装。

Descriptions 组件位于 src/components/Descriptions

注意

推荐使用 tsx 来使用 Descriptions 组件

用法

更复杂点的例子,请在线预览

<script setup lang="tsx">\nimport { Descriptions, DescriptionsSchema } from '@/components/Descriptions'\nimport { reactive } from 'vue'\n\nconst data = reactive({\n  username: 'chenkl',\n  nickName: '梦似花落。',\n  age: 26,\n  phone: '13655971xxxx',\n  email: '502431556@qq.com',\n  addr: '这是一个很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的地址',\n  sex: '男',\n  certy: '3505831994xxxxxxxx'\n})\n\nconst schema = reactive<DescriptionsSchema[]>([\n  {\n    field: 'username',\n    label: 'username'\n  },\n  {\n    field: 'nickName',\n    label: 'nickName'\n  },\n  {\n    field: 'phone',\n    label: 'phone'\n  },\n  {\n    field: 'email',\n    label: 'email'\n  },\n  {\n    field: 'addr',\n    label: 'addr',\n    span: 24\n  }\n])\n</script>\n\n<template>\n  <Descriptions\n    title="descriptions"\n    message="message"\n    :data="data"\n    :schema="schema"\n  />\n</template>\n\n

Descriptions 属性

除以下参数外,还支持 element-plusDescriptions 所有属性,详见

属性说明类型可选值默认值
title标题string--
message提示string--
collapse是否显示展开按钮boolean-true
schema布局结构数据,详见DescriptionsSchema[]-[]
data展示的数据Recordable-{}

Schema

属性说明类型可选值默认值
span栅格占比number--
field字段名,唯一值,需要与 data 中的属性名对应string--
label列表标题string--
width列表宽度string/number--
minWidth列表最小宽度string/number--
align内容对齐方式stringleft/center/rightleft
labelAlign标题对齐方式stringleft/center/rightleft
className自定义内容标签类名string--
labelClassName自定义标题标签类名string--
slots插槽对象object--
',13);e.render=function(t,a,e,o,c,d){return n(),s("div",null,[p])};export default e;export{a as __pageData}; diff --git a/assets/components_descriptions.md.83f44587.lean.js b/assets/components_descriptions.md.83f44587.lean.js new file mode 100644 index 00000000..21e723f1 --- /dev/null +++ b/assets/components_descriptions.md.83f44587.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a as t}from"./app.7e863e47.js";const a='{"title":"Descriptions 描述组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Descriptions 属性","slug":"descriptions-属性"},{"level":3,"title":"Schema","slug":"schema"}],"relativePath":"components/descriptions.md","lastUpdated":1718353615647}',e={},p=t('',13);e.render=function(t,a,e,o,c,d){return n(),s("div",null,[p])};export default e;export{a as __pageData}; diff --git a/assets/components_dialog.md.660e8880.js b/assets/components_dialog.md.660e8880.js new file mode 100644 index 00000000..cb443cd5 --- /dev/null +++ b/assets/components_dialog.md.660e8880.js @@ -0,0 +1 @@ +import{o as a,c as n,a as t}from"./app.7e863e47.js";const s='{"title":"Dialog 弹窗组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Dialog 属性","slug":"dialog-属性"},{"level":2,"title":"Dialog 插槽","slug":"dialog-插槽"}],"relativePath":"components/dialog.md","lastUpdated":1718353615647}',p={},o=t('

Dialog 弹窗组件

element-plusDialog 组件进行封装。

Dialog 组件位于 src/components/Dialog

用法

<script setup lang="ts">\nimport { Dialog } from '@/components/Dialog'\nimport { ElButton } from 'element-plus'\nimport { ref } from 'vue'\n\nconst dialogVisible = ref(false)\n</script>\n\n<template>\n  <ElButton type="primary" @click="dialogVisible = !dialogVisible">\n    open\n  </ElButton>\n  <Dialog v-model="dialogVisible" title="dialog">\n    <div v-for="v in 10000" :key="v">{{ v }}</div>\n    <template #footer>\n      <el-button @click="dialogVisible = false">close</el-button>\n    </template>\n  </Dialog>\n</template>\n\n

Dialog 属性

除以下参数外,还支持 element-plusDialog 所有属性,详见

属性说明类型可选值默认值
modelValue是否显示弹窗,支持v-modelboolean-false
fullscreen是否显示全屏按钮boolean-true
title弹窗标题string-Dialog
maxHeight弹窗内容最大高度string/number-500px

Dialog 插槽

插槽名说明子标签
-弹窗内容-
title弹窗标题内容-
footer弹窗底部内容-
',10);p.render=function(t,s,p,e,l,c){return a(),n("div",null,[o])};export default p;export{s as __pageData}; diff --git a/assets/components_dialog.md.660e8880.lean.js b/assets/components_dialog.md.660e8880.lean.js new file mode 100644 index 00000000..be564768 --- /dev/null +++ b/assets/components_dialog.md.660e8880.lean.js @@ -0,0 +1 @@ +import{o as a,c as n,a as t}from"./app.7e863e47.js";const s='{"title":"Dialog 弹窗组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Dialog 属性","slug":"dialog-属性"},{"level":2,"title":"Dialog 插槽","slug":"dialog-插槽"}],"relativePath":"components/dialog.md","lastUpdated":1718353615647}',p={},o=t('',10);p.render=function(t,s,p,e,l,c){return a(),n("div",null,[o])};export default p;export{s as __pageData}; diff --git a/assets/components_echart.md.b419ebcf.js b/assets/components_echart.md.b419ebcf.js new file mode 100644 index 00000000..3c79d83d --- /dev/null +++ b/assets/components_echart.md.b419ebcf.js @@ -0,0 +1 @@ +import{o as t,c as a,a as e}from"./app.7e863e47.js";const n='{"title":"Echart 图表组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Echart 属性","slug":"echart-属性"}],"relativePath":"components/echart.md","lastUpdated":1718353615647}',s={},o=e('

Echart 图表组件

echarts 进行封装,自适应窗口大小。

Echart 组件位于 src/components/Echart

用法

只需传入对应的 optionsheight 即可展示图表。

<template>\n  <Echart :options="pieOptions" :height="300" />\n</template>\n

Echart 属性

属性说明类型可选值默认值
optionsechart 对应的配置项,详见EChartsOption-[]
width图表宽度string/number--
height图表高度string/number-500
',8);s.render=function(e,n,s,p,c,r){return t(),a("div",null,[o])};export default s;export{n as __pageData}; diff --git a/assets/components_echart.md.b419ebcf.lean.js b/assets/components_echart.md.b419ebcf.lean.js new file mode 100644 index 00000000..dae1e9bf --- /dev/null +++ b/assets/components_echart.md.b419ebcf.lean.js @@ -0,0 +1 @@ +import{o as t,c as a,a as e}from"./app.7e863e47.js";const n='{"title":"Echart 图表组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Echart 属性","slug":"echart-属性"}],"relativePath":"components/echart.md","lastUpdated":1718353615647}',s={},o=e('',8);s.render=function(e,n,s,p,c,r){return t(),a("div",null,[o])};export default s;export{n as __pageData}; diff --git a/assets/components_editor.md.d3df14db.js b/assets/components_editor.md.d3df14db.js new file mode 100644 index 00000000..da12430b --- /dev/null +++ b/assets/components_editor.md.d3df14db.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.7e863e47.js";const s='{"title":"Editor 富文本组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Editor 属性","slug":"editor-属性"},{"level":2,"title":"Editor 事件","slug":"editor-事件"},{"level":2,"title":"Editor 方法","slug":"editor-方法"}],"relativePath":"components/editor.md","lastUpdated":1718353615647}',e={},o=n('

Editor 富文本组件

基于 wangeditor 封装。

目前项目中的 editor 只是做了简单的封装,需要开发者根据实际情况,自行配置 editorConfig 属性,如,上传图片功能。

可自行阅读 wangeditor文档

Editor 组件位于 src/components/Editor

用法

<script setup lang="ts">\nimport { Editor } from '@/components/Editor'\nimport { ref} from 'vue'\n\nconst defaultHtml = ref('<p>hello <strong>world</strong></p>')\n\nconst change = (html: string) => {\n  console.log(html)\n}\n</script>\n\n<template>\n  <Editor v-model="defaultHtml" ref="editorRef" @change="change" />\n</template>\n\n

Editor 属性

属性说明类型可选值默认值
editorId富文本组件唯一值,必填项string-wangeEditor-1
height高度string/number-500px
editorConfigwangeditor 组件的所有配置项IEditorConfig--
modelValue内容双向绑定,支持v-modelstring--

Editor 事件

方法名说明回调参数
change内容改变时,返回 editor 实例editor: IDomEditor

Editor 方法

方法名说明回调参数
getEditorRef获取 editor 实例() => Promise<IDomEditor>
',13);e.render=function(n,s,e,p,r,d){return t(),a("div",null,[o])};export default e;export{s as __pageData}; diff --git a/assets/components_editor.md.d3df14db.lean.js b/assets/components_editor.md.d3df14db.lean.js new file mode 100644 index 00000000..02d4cc7c --- /dev/null +++ b/assets/components_editor.md.d3df14db.lean.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.7e863e47.js";const s='{"title":"Editor 富文本组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Editor 属性","slug":"editor-属性"},{"level":2,"title":"Editor 事件","slug":"editor-事件"},{"level":2,"title":"Editor 方法","slug":"editor-方法"}],"relativePath":"components/editor.md","lastUpdated":1718353615647}',e={},o=n('',13);e.render=function(n,s,e,p,r,d){return t(),a("div",null,[o])};export default e;export{s as __pageData}; diff --git a/assets/components_error.md.f6256b37.js b/assets/components_error.md.f6256b37.js new file mode 100644 index 00000000..ea2a9926 --- /dev/null +++ b/assets/components_error.md.f6256b37.js @@ -0,0 +1 @@ +import{o as a,c as t,a as n}from"./app.7e863e47.js";const s='{"title":"Error 缺省组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Error 属性","slug":"error-属性"},{"level":2,"title":"Error 事件","slug":"error-事件"},{"level":2,"title":"如何扩展新类型","slug":"如何扩展新类型"}],"relativePath":"components/error.md","lastUpdated":1718353615647}',e={},r=n('

Error 缺省组件

用于各种占位图组件,如 404403500 等错误页面。

Error 组件位于 src/components/Error

用法

<script setup lang="ts">\nimport { Error } from '@/components/Error'\n</script>\n\n<template>\n  <Error />\n</template>\n\n

Error 属性

属性说明类型可选值默认值
type占位图类型string-404

Error 事件

方法名说明回调参数
errorClick点击按钮后的回调-

如何扩展新类型

目前只提供了 404403500 三种类型,如果不满足实际需求,可自行扩展。

只需在 src/components/Error/src/Error.vue 文件的 errorMap 对象扩展对应类型即可。

',12);e.render=function(n,s,e,o,p,c){return a(),t("div",null,[r])};export default e;export{s as __pageData}; diff --git a/assets/components_error.md.f6256b37.lean.js b/assets/components_error.md.f6256b37.lean.js new file mode 100644 index 00000000..3b41163e --- /dev/null +++ b/assets/components_error.md.f6256b37.lean.js @@ -0,0 +1 @@ +import{o as a,c as t,a as n}from"./app.7e863e47.js";const s='{"title":"Error 缺省组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Error 属性","slug":"error-属性"},{"level":2,"title":"Error 事件","slug":"error-事件"},{"level":2,"title":"如何扩展新类型","slug":"如何扩展新类型"}],"relativePath":"components/error.md","lastUpdated":1718353615647}',e={},r=n('',12);e.render=function(n,s,e,o,p,c){return a(),t("div",null,[r])};export default e;export{s as __pageData}; diff --git a/assets/components_footer.md.3d4ee3bd.js b/assets/components_footer.md.3d4ee3bd.js new file mode 100644 index 00000000..6fd43de0 --- /dev/null +++ b/assets/components_footer.md.3d4ee3bd.js @@ -0,0 +1 @@ +import{o as a,c as n,a as s}from"./app.7e863e47.js";const t='{"title":"Footer 页脚","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"}],"relativePath":"components/footer.md","lastUpdated":1718353615647}',e={},p=s('

Footer 页脚

为整个项目提供页脚信息,自动适应,内容高度不够时,会一直保持在最底部,内容超出则跟随在内容后面。

Footer 组件位于 src/components/Footer 内,如果需要修改页脚信息,可在组件内自定义修改。

用法

<script setup lang="ts">\nimport { Footer } from '@/components/Footer'\n</script>\n\n<template>\n  <Footer />\n</template>\n\n
',5);e.render=function(s,t,e,o,c,l){return a(),n("div",null,[p])};export default e;export{t as __pageData}; diff --git a/assets/components_footer.md.3d4ee3bd.lean.js b/assets/components_footer.md.3d4ee3bd.lean.js new file mode 100644 index 00000000..d3cb3a42 --- /dev/null +++ b/assets/components_footer.md.3d4ee3bd.lean.js @@ -0,0 +1 @@ +import{o as a,c as n,a as s}from"./app.7e863e47.js";const t='{"title":"Footer 页脚","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"}],"relativePath":"components/footer.md","lastUpdated":1718353615647}',e={},p=s('',5);e.render=function(s,t,e,o,c,l){return a(),n("div",null,[p])};export default e;export{t as __pageData}; diff --git a/assets/components_form.md.cbd4378a.js b/assets/components_form.md.cbd4378a.js new file mode 100644 index 00000000..d883a44a --- /dev/null +++ b/assets/components_form.md.cbd4378a.js @@ -0,0 +1 @@ +import{o as t,c as n,a as s}from"./app.7e863e47.js";const a='{"title":"Form 表单组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"基础用法","slug":"基础用法"},{"level":3,"title":"useForm","slug":"useform"},{"level":2,"title":"Form 属性","slug":"form-属性"},{"level":3,"title":"Schema","slug":"schema"},{"level":3,"title":"ComponentProps","slug":"componentprops"},{"level":3,"title":"FormItemProps","slug":"formitemprops"},{"level":2,"title":"Form 方法","slug":"form-方法"},{"level":2,"title":"如何新增表单子组件","slug":"如何新增表单子组件"}],"relativePath":"components/form.md","lastUpdated":1718353615647}',o={},e=s('

Form 表单组件

element-plusForm 组件进行封装,支持 element-plus 的所有表单组件,并额外扩展了一些功能。

Form 组件位于 src/components/Form

注意

推荐使用 tsx 来使用 Form 组件。

用法

目前支持的表单组件,你可以在 在线预览 中看到。

基础用法

<script setup lang="ts">\nimport { Form, FormSchema } from '@/components/Form'\nimport { reactive } from 'vue'\n\nconst schema = reactive<FormSchema[]>([\n  {\n    field: 'field1',\n    label: 'input',\n    component: 'Input'\n  }\n])\n</script>\n\n<template>\n  <Form :schema="schema" />\n</template>\n\n

useForm

对于复杂的场景,可以配合 useForm 来使用。

如果想看更复杂点的例子,请在线预览

<script setup lang="tsx">\nimport { Form, FormSchema } from '@/components/Form'\nimport { reactive } from 'vue'\nimport { useForm } from '@/hooks/web/useForm'\n\nconst schema = reactive<FormSchema[]>([\n  {\n    field: 'field1',\n    label: 'input',\n    component: 'Input'\n  }\n])\n\nconst { formRegister } = useForm()\n</script>\n\n<template>\n  <Form :schema="schema" @register="formRegister" />\n</template>\n\n

参数介绍

const { formRegister, formMethods } = useForm()\n

register

formRegister 用于注册 useForm,如果需要使用 useForm 提供的 api,必须将 formRegister 传入组件的 onRegister

formMethods

方法名说明回调参数
setValues用于设置表单值(data: Recordable) => void
setProps用于设置表单属性(props: Recordable) => void
delSchema用于删除表单结构(field: string) => void
addSchema用于新增表单结构(formSchema: FormSchema, index?: number) => void
setSchema用于编辑表单结构(schemaProps: FormSetPropsType[]) => void
getFormData用于获取表单数据<T = Recordable>() => Promise<T>
getComponentExpose用于获取表单组件实例,如 ElInput 实例(field: string) => any
getFormItemExpose用于获取 formItem 组件实例(field: string) => Promise<ComponentRef<typeof ElFormItem>>
getElFormExpose用于获取 elForm 组件实例(field: string) => Promise<ComponentRef<typeof ElForm>>
getFormExpose用于获取二次封装的 Form 组件实例() => Promise<ComponentRef<typeof Form>>

Form 属性

除以下参数外,还支持 element-plusForm 所有属性,详见

属性说明类型可选值默认值
schema生成 Form 的布局结构数组,详见FormSchema-[]
isCol是否需要栅格布局boolean-true
model表单数据对象Recordable-{}
autoSetPlaceholder是否自动设置 placeholderboolean-true
isCustom是否自定义内容boolean-false
labelWidth表单 label 宽度string/number-auto

Schema

属性说明类型可选值默认值
field唯一值,必填项string--
label标题string--
colPropselement-plus 的 col 组件属性ColProps--
componentProps表单组件子属性,详见any--
formItemPropselement-plus 的 form-item 组件属性,详见FormItemProps--
component需要渲染的表单子组件ComponentName--
value表单子组件初始值any--
hidden表单子组件是否隐藏boolean--
remove表单子组件是否隐藏,如果为true,会连同值一同删除,类似v-ifboolean--
optionApi加载 options 方法() => Promise<any>--

ComponentProps

componentProps的类型有: InputComponentProps AutocompleteComponentProps InputNumberComponentProps SelectComponentProps SelectV2ComponentProps CascaderComponentProps SwitchComponentProps RateComponentProps ColorPickerComponentProps TransferComponentProps RadioGroupComponentProps RadioButtonComponentProps DividerComponentProps DatePickerComponentProps DateTimePickerComponentProps TimePickerComponentProps InputPasswordComponentProps TreeSelectComponentProps UploadComponentProps any

基本上每个表单组件都有 slots 的插槽对象,用于自定义插槽,如 InputComponentProps :

slots?: {\n  prefix?: (...args: any[]) => JSX.Element | null\n  suffix?: (...args: any[]) => JSX.Element | null\n  prepend?: (...args: any[]) => JSX.Element | null\n  append?: (...args: any[]) => JSX.Element | null\n}\n

如果需要监听组件事件,如 change 事件,每个 ComponentProps 基本上都有 on 对象用来接收事件,如 InputComponentProps :

on?: {\n  blur?: (event: FocusEvent) => void\n  focus?: (event: FocusEvent) => void\n  change?: (value: string | number) => void\n  clear?: () => void\n  input?: (value: string | number) => void\n}\n\n

FormItemProps

除了以下属性,还支持 element-plus 中的 FormItem 的所有属性

属性说明类型可选值默认值
slotsFormItem的插槽Object--
style子表单项的样式CSSProperties--

Form 方法

方法名说明回调参数
setValues用于设置表单值(data: Recordable) => void
setProps用于设置表单属性(props: Recordable) => void
delSchema用于删除表单结构(field: string) => void
addSchema用于新增表单结构(formSchema: FormSchema, index?: number) => void
setSchema用于编辑表单结构(schemaProps: FormSetPropsType[]) => void
getComponentExpose用于获取表单子组件的实例,如 ElInput 实例(field: string) => any
getFormItemExpose用于获取 FormItem 组件的实例() => Promise<typeof FormItem>

如何新增表单子组件

当项目中内置的表单组件不满足需求时,可以自行添加组件进去。

  1. src/components/Form/src/types/index.tsComponentName 添加你组件名称。
  2. src/components/Form/src/helper/componentMap.ts 引入你自己的组件,并在 componentMap 对象中添加键值对即可。
  3. 如果想要更好的类型提示,可以把自定义组件的类型也添加到 componentProps
',37);o.render=function(s,a,o,p,c,r){return t(),n("div",null,[e])};export default o;export{a as __pageData}; diff --git a/assets/components_form.md.cbd4378a.lean.js b/assets/components_form.md.cbd4378a.lean.js new file mode 100644 index 00000000..6a23a28f --- /dev/null +++ b/assets/components_form.md.cbd4378a.lean.js @@ -0,0 +1 @@ +import{o as t,c as n,a as s}from"./app.7e863e47.js";const a='{"title":"Form 表单组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"基础用法","slug":"基础用法"},{"level":3,"title":"useForm","slug":"useform"},{"level":2,"title":"Form 属性","slug":"form-属性"},{"level":3,"title":"Schema","slug":"schema"},{"level":3,"title":"ComponentProps","slug":"componentprops"},{"level":3,"title":"FormItemProps","slug":"formitemprops"},{"level":2,"title":"Form 方法","slug":"form-方法"},{"level":2,"title":"如何新增表单子组件","slug":"如何新增表单子组件"}],"relativePath":"components/form.md","lastUpdated":1718353615647}',o={},e=s('',37);o.render=function(s,a,o,p,c,r){return t(),n("div",null,[e])};export default o;export{a as __pageData}; diff --git a/assets/components_highlight.md.38a1878a.js b/assets/components_highlight.md.38a1878a.js new file mode 100644 index 00000000..ef7dc764 --- /dev/null +++ b/assets/components_highlight.md.38a1878a.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.7e863e47.js";const s='{"title":"Highlight 高亮组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Highlight 属性","slug":"highlight-属性"},{"level":2,"title":"Highlight 事件","slug":"highlight-事件"}],"relativePath":"components/highlight.md","lastUpdated":1718353615647}',e={},p=n('

Highlight 高亮组件

Highlight 组件位于 src/components/Highlight

用法

组件只能接收纯文本。

<script setup lang="ts">\nimport { Highlight } from '@/components/Highlight'\n</script>\n\n<template>\n  <Highlight :keys="['十年前', '现在']">\n    种一棵树最好的时间是十年前,其次就是现在。\n  </Highlight>\n</template>\n\n

Highlight 属性

属性说明类型可选值默认值
tag包裹标签string-span
keys高亮的关键字string[]-[]
color高亮的颜色string-var(--el-color-primary)

Highlight 事件

方法名说明回调参数
click关键字点击事件key: string
',9);e.render=function(n,s,e,o,l,i){return t(),a("div",null,[p])};export default e;export{s as __pageData}; diff --git a/assets/components_highlight.md.38a1878a.lean.js b/assets/components_highlight.md.38a1878a.lean.js new file mode 100644 index 00000000..f0b69273 --- /dev/null +++ b/assets/components_highlight.md.38a1878a.lean.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.7e863e47.js";const s='{"title":"Highlight 高亮组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Highlight 属性","slug":"highlight-属性"},{"level":2,"title":"Highlight 事件","slug":"highlight-事件"}],"relativePath":"components/highlight.md","lastUpdated":1718353615647}',e={},p=n('',9);e.render=function(n,s,e,o,l,i){return t(),a("div",null,[p])};export default e;export{s as __pageData}; diff --git a/assets/components_i-agree.md.2db699a9.js b/assets/components_i-agree.md.2db699a9.js new file mode 100644 index 00000000..b9f1afda --- /dev/null +++ b/assets/components_i-agree.md.2db699a9.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.7e863e47.js";const s='{"title":"IAgree 我同意","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Avatars 属性","slug":"avatars-属性"},{"level":3,"title":"link","slug":"link"}],"relativePath":"components/i-agree.md","lastUpdated":1718353615647}',e={},d=n('

IAgree 我同意

用于同意协议选项

IAgree 组件位于 src/components/IAgree

用法

<template>\n  <IAgree\n    :link="[\n      {\n        text: '《隐私政策》',\n        url: 'https://www.baidu.com'\n      }\n    ]"\n    text="我同意《隐私政策》"\n  />\n</template>\n\n

Avatars 属性

属性说明类型可选值默认值
text文案string--
link需要跳转的高亮数据,详见LinkItem[]--
属性说明类型可选值默认值
url跳转地址,非必填string--
text高亮文案string--
onClick点击高亮文案执行的方法,非必填() => void--
',9);e.render=function(n,s,e,p,o,r){return t(),a("div",null,[d])};export default e;export{s as __pageData}; diff --git a/assets/components_i-agree.md.2db699a9.lean.js b/assets/components_i-agree.md.2db699a9.lean.js new file mode 100644 index 00000000..dd8306be --- /dev/null +++ b/assets/components_i-agree.md.2db699a9.lean.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.7e863e47.js";const s='{"title":"IAgree 我同意","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Avatars 属性","slug":"avatars-属性"},{"level":3,"title":"link","slug":"link"}],"relativePath":"components/i-agree.md","lastUpdated":1718353615647}',e={},d=n('',9);e.render=function(n,s,e,p,o,r){return t(),a("div",null,[d])};export default e;export{s as __pageData}; diff --git a/assets/components_icon-picker.md.a9185916.js b/assets/components_icon-picker.md.a9185916.js new file mode 100644 index 00000000..3f00793d --- /dev/null +++ b/assets/components_icon-picker.md.a9185916.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.7e863e47.js";const t='{"title":"IconPicker 图标选择器组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"如何添加其他开源项目的图标","slug":"如何添加其他开源项目的图标"},{"level":2,"title":"Icon 属性","slug":"icon-属性"}],"relativePath":"components/icon-picker.md","lastUpdated":1718353615647}',e={},p=s('

IconPicker 图标选择器组件

用于快速选择 Iconify 图标。

IconPicker 组件位于 src/components/IconPicker

TIP

目前只集成了 Ant Design Icons 、Element Plus、TDesign Icons 三个开源项目图标

用法

<script lang="ts" setup>\nimport { IconPicker } from '@/components/IconPicker'\n\nconst currentIcon = ref('tdesign:book-open')\n</script>\n\n<template>\n  <IconPicker v-model="currentIcon" />\n</template>\n\n

如何添加其他开源项目的图标

可以执行 pnpm run icon 然后选择你想要的图标集

之后,在 IconPicker.vue 导入,并添加到 icons 中即可。

Icon 属性

属性说明类型可选值默认值
modelValue选中项绑定值,支持v-modelstring--
',11);e.render=function(s,t,e,o,c,l){return n(),a("div",null,[p])};export default e;export{t as __pageData}; diff --git a/assets/components_icon-picker.md.a9185916.lean.js b/assets/components_icon-picker.md.a9185916.lean.js new file mode 100644 index 00000000..03ac54a8 --- /dev/null +++ b/assets/components_icon-picker.md.a9185916.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.7e863e47.js";const t='{"title":"IconPicker 图标选择器组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"如何添加其他开源项目的图标","slug":"如何添加其他开源项目的图标"},{"level":2,"title":"Icon 属性","slug":"icon-属性"}],"relativePath":"components/icon-picker.md","lastUpdated":1718353615647}',e={},p=s('',11);e.render=function(s,t,e,o,c,l){return n(),a("div",null,[p])};export default e;export{t as __pageData}; diff --git a/assets/components_icon.md.53245e29.js b/assets/components_icon.md.53245e29.js new file mode 100644 index 00000000..5f64f8a5 --- /dev/null +++ b/assets/components_icon.md.53245e29.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.7e863e47.js";const t='{"title":"Icon 图标组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"基本用法","slug":"基本用法"},{"level":3,"title":"useIcon","slug":"useicon"},{"level":2,"title":"Icon 属性","slug":"icon-属性"}],"relativePath":"components/icon.md","lastUpdated":1718353615647}',o={},p=s('

Icon 图标组件

用于项目内组件的展示,基本支持所有图标库(支持按需加载,只打包所用到的图标),支持使用本地 svg 和 Iconify 图标。

Icon 组件位于 src/components/Icon

TIP

Iconify 上,你可以查询到你想要的所有图标并使用,不管是不是 element-plus 的图标库。

用法

基本用法

如果以svg-icon: 开头,则会在本地中找到该 svg 图标,否则,会加载 Iconify 图标。

<template>\n  <!-- 加载本地 svg -->\n  <Icon icon="svg-icon:peoples" />\n\n  <!-- 加载 Iconify -->\n  <Icon icon="ep:aim" />\n</template>\n\n

useIcon

如果需要在其他组件中如 ElButton 传入 icon 属性,可以使用 useIcon

<script setup lang="ts">\nimport { useIcon } from '@/hooks/web/useIcon'\nimport { ElButton } from 'element-plus'\n\nconst icon = useIcon({ icon: 'svg-icon:save' })\n</script>\n\n<template>\n  <ElButton :icon="icon"> button </ElButton>\n</template>\n

参数介绍

const icon = useIcon(props)\n

props

详见

Icon 属性

属性说明类型可选值默认值
icon图标名string--
color图标颜色string--
size图标大小number-16
hoverColorhover颜色string--
',17);o.render=function(s,t,o,e,c,l){return n(),a("div",null,[p])};export default o;export{t as __pageData}; diff --git a/assets/components_icon.md.53245e29.lean.js b/assets/components_icon.md.53245e29.lean.js new file mode 100644 index 00000000..71477893 --- /dev/null +++ b/assets/components_icon.md.53245e29.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.7e863e47.js";const t='{"title":"Icon 图标组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"基本用法","slug":"基本用法"},{"level":3,"title":"useIcon","slug":"useicon"},{"level":2,"title":"Icon 属性","slug":"icon-属性"}],"relativePath":"components/icon.md","lastUpdated":1718353615647}',o={},p=s('',17);o.render=function(s,t,o,e,c,l){return n(),a("div",null,[p])};export default o;export{t as __pageData}; diff --git a/assets/components_image-viewer.md.01b938ca.js b/assets/components_image-viewer.md.01b938ca.js new file mode 100644 index 00000000..fadb0ded --- /dev/null +++ b/assets/components_image-viewer.md.01b938ca.js @@ -0,0 +1 @@ +import{o as a,c as t,a as n}from"./app.7e863e47.js";const s='{"title":"ImageViewer 图片预览组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"createImageViewer","slug":"createimageviewer"},{"level":3,"title":"参数","slug":"参数"}],"relativePath":"components/image-viewer.md","lastUpdated":1718353615647}',e={},p=n('

ImageViewer 图片预览组件

element-plusImageViewer 组件函数化,通过函数方便创建组件。

ImageViewer 组件位于 src/components/ImageViewer

用法

<script setup lang="ts">\nimport { createImageViewer } from '@/components/ImageViewer'\nimport { ElButton } from 'element-plus'\n\nconst open = () => {\n  createImageViewer({\n    urlList: [\n      'https://img1.baidu.com/it/u=657828739,1486746195&fm=26&fmt=auto&gp=0.jpg',\n      'https://img0.baidu.com/it/u=3114228356,677481409&fm=26&fmt=auto&gp=0.jpg',\n      'https://img1.baidu.com/it/u=508846955,3814747122&fm=26&fmt=auto&gp=0.jpg',\n      'https://img1.baidu.com/it/u=3536647690,3616605490&fm=26&fmt=auto&gp=0.jpg',\n      'https://img1.baidu.com/it/u=4087287201,1148061266&fm=26&fmt=auto&gp=0.jpg',\n      'https://img2.baidu.com/it/u=3429163260,2974496379&fm=26&fmt=auto&gp=0.jpg'\n    ]\n  })\n}\n</script>\n\n<template>\n  <ElButton type="primary" @click="open">预览</ElButton>\n</template>\n\n

createImageViewer

参数

属性说明类型可选值默认值
urlList图片列表string[]--
zIndex层级number--
initialIndex默认展示第几张number-1
infinite是否可以循环切换boolean-true
hideOnClickModal点击蒙版是否关闭boolean-false
appendToBody是否添加到 body 中boolean-false
show是否显示图片预览boolean-false
',8);e.render=function(n,s,e,o,c,l){return a(),t("div",null,[p])};export default e;export{s as __pageData}; diff --git a/assets/components_image-viewer.md.01b938ca.lean.js b/assets/components_image-viewer.md.01b938ca.lean.js new file mode 100644 index 00000000..cc1bb3c5 --- /dev/null +++ b/assets/components_image-viewer.md.01b938ca.lean.js @@ -0,0 +1 @@ +import{o as a,c as t,a as n}from"./app.7e863e47.js";const s='{"title":"ImageViewer 图片预览组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"createImageViewer","slug":"createimageviewer"},{"level":3,"title":"参数","slug":"参数"}],"relativePath":"components/image-viewer.md","lastUpdated":1718353615647}',e={},p=n('',8);e.render=function(n,s,e,o,c,l){return a(),t("div",null,[p])};export default e;export{s as __pageData}; diff --git a/assets/components_infotip.md.9d96a7af.js b/assets/components_infotip.md.9d96a7af.js new file mode 100644 index 00000000..61c67434 --- /dev/null +++ b/assets/components_infotip.md.9d96a7af.js @@ -0,0 +1 @@ +import{o as t,c as n,a}from"./app.7e863e47.js";const s='{"title":"Infotip 信息提示组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Infotip 属性","slug":"infotip-属性"},{"level":2,"title":"Infotip 事件","slug":"infotip-事件"}],"relativePath":"components/infotip.md","lastUpdated":1718353615647}',p={},e=a('

Infotip 信息提示组件

基于 Highlight 组件封装。

Infotip 组件位于 src/components/Infotip

用法

<script setup lang="ts">\nimport { Infotip } from '@/components/Infotip'\n</script>\n\n<template>\n  <Infotip\n    title="推荐使用Iconify组件"\n    :schema="[\n      {\n        label: 'Iconify组件基本包含所有的图标,你可以查询到你想要的任何图标。并且打包只会打包所用到的图标。',\n        keys: ['Iconify']\n      },\n      {\n        label: '访问地址',\n        keys: ['访问地址']\n      }\n    ]"\n  />\n</template>\n\n

Infotip 属性

属性说明类型可选值默认值
title标题string--
schema展示的数据内容string[]/TipSchema[]-[]
showIndex显示序号boolean-true
highlightColor高亮颜色string-var(--el-color-primary)

Infotip 事件

方法名说明回调参数
click关键字点击事件key: string
',9);p.render=function(a,s,p,o,c,l){return t(),n("div",null,[e])};export default p;export{s as __pageData}; diff --git a/assets/components_infotip.md.9d96a7af.lean.js b/assets/components_infotip.md.9d96a7af.lean.js new file mode 100644 index 00000000..4c4733dd --- /dev/null +++ b/assets/components_infotip.md.9d96a7af.lean.js @@ -0,0 +1 @@ +import{o as t,c as n,a}from"./app.7e863e47.js";const s='{"title":"Infotip 信息提示组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Infotip 属性","slug":"infotip-属性"},{"level":2,"title":"Infotip 事件","slug":"infotip-事件"}],"relativePath":"components/infotip.md","lastUpdated":1718353615647}',p={},e=a('',9);p.render=function(a,s,p,o,c,l){return t(),n("div",null,[e])};export default p;export{s as __pageData}; diff --git a/assets/components_input-password.md.7526a1ed.js b/assets/components_input-password.md.7526a1ed.js new file mode 100644 index 00000000..e6404594 --- /dev/null +++ b/assets/components_input-password.md.7526a1ed.js @@ -0,0 +1 @@ +import{o as s,c as a,a as n}from"./app.7e863e47.js";const t='{"title":"InputPassword 密码输入框","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"InputPassword 属性","slug":"inputpassword-属性"}],"relativePath":"components/input-password.md","lastUpdated":1718353615647}',p={},e=n('

InputPassword 密码输入框

element-plusInput 组件进行封装。

InputPassword 组件位于 src/components/InputPassword

用法

<script setup lang="ts">\nimport { InputPassword } from '@/components/InputPassword'\nimport { ref } from 'vue'\n\nconst password = ref('')\n</script>\n\n<template>\n  <InputPassword v-model="password" strength />\n</template>\n\n

InputPassword 属性

除以下参数外,还支持 element-plusInput 所有属性,详见

属性说明类型可选值默认值
strength是否显示强度校验boolean-false
modelValue选中项绑定值,支持v-modelstring--
',8);p.render=function(n,t,p,o,c,r){return s(),a("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/components_input-password.md.7526a1ed.lean.js b/assets/components_input-password.md.7526a1ed.lean.js new file mode 100644 index 00000000..a7937b1c --- /dev/null +++ b/assets/components_input-password.md.7526a1ed.lean.js @@ -0,0 +1 @@ +import{o as s,c as a,a as n}from"./app.7e863e47.js";const t='{"title":"InputPassword 密码输入框","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"InputPassword 属性","slug":"inputpassword-属性"}],"relativePath":"components/input-password.md","lastUpdated":1718353615647}',p={},e=n('',8);p.render=function(n,t,p,o,c,r){return s(),a("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/components_introduction.md.61a9c470.js b/assets/components_introduction.md.61a9c470.js new file mode 100644 index 00000000..17d360c8 --- /dev/null +++ b/assets/components_introduction.md.61a9c470.js @@ -0,0 +1 @@ +import{o as e,c as n,b as t,d as r}from"./app.7e863e47.js";const o='{"title":"前言","description":"","frontmatter":{},"relativePath":"components/introduction.md","lastUpdated":1718353615647}',a={},l=t("h1",{id:"前言"},[t("a",{class:"header-anchor",href:"#前言","aria-hidden":"true"},"#"),r(" 前言")],-1),u=t("p",null,"本项目中集成了很多常用的功能组件,方便开发者使用。如果你开发项目中自带的组件不满足你的需求,可以不使用或者自行改造,这都是可以的。",-1),i=t("p",null,[r("目前本项目中基本上都是采用按需引入的方式,只有 "),t("code",null,"Icon"),r(" 和 "),t("code",null,"Permission"),r(" 进行了全局注册。")],-1),p=t("p",null,[r("如果不喜欢按需引入,可以自行去集成 "),t("a",{href:"https://github.com/antfu/unplugin-auto-import",target:"_blank",rel:"noopener noreferrer"},[t("code",null,"unplugin-auto-import")])],-1);a.render=function(t,r,o,a,d,s){return e(),n("div",null,[l,u,i,p])};export default a;export{o as __pageData}; diff --git a/assets/components_introduction.md.61a9c470.lean.js b/assets/components_introduction.md.61a9c470.lean.js new file mode 100644 index 00000000..17d360c8 --- /dev/null +++ b/assets/components_introduction.md.61a9c470.lean.js @@ -0,0 +1 @@ +import{o as e,c as n,b as t,d as r}from"./app.7e863e47.js";const o='{"title":"前言","description":"","frontmatter":{},"relativePath":"components/introduction.md","lastUpdated":1718353615647}',a={},l=t("h1",{id:"前言"},[t("a",{class:"header-anchor",href:"#前言","aria-hidden":"true"},"#"),r(" 前言")],-1),u=t("p",null,"本项目中集成了很多常用的功能组件,方便开发者使用。如果你开发项目中自带的组件不满足你的需求,可以不使用或者自行改造,这都是可以的。",-1),i=t("p",null,[r("目前本项目中基本上都是采用按需引入的方式,只有 "),t("code",null,"Icon"),r(" 和 "),t("code",null,"Permission"),r(" 进行了全局注册。")],-1),p=t("p",null,[r("如果不喜欢按需引入,可以自行去集成 "),t("a",{href:"https://github.com/antfu/unplugin-auto-import",target:"_blank",rel:"noopener noreferrer"},[t("code",null,"unplugin-auto-import")])],-1);a.render=function(t,r,o,a,d,s){return e(),n("div",null,[l,u,i,p])};export default a;export{o as __pageData}; diff --git a/assets/components_json-editor.md.0577e01f.js b/assets/components_json-editor.md.0577e01f.js new file mode 100644 index 00000000..6011d4a3 --- /dev/null +++ b/assets/components_json-editor.md.0577e01f.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"JsonEditor JSON编辑器组件(2.2.0+)","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"JsonEditor 属性","slug":"jsoneditor-属性"},{"level":2,"title":"Editor 事件","slug":"editor-事件"}],"relativePath":"components/json-editor.md","lastUpdated":1718353615647}',p={},o=a('

JsonEditor JSON编辑器组件(2.2.0+)

基于 vue-json-pretty 封装。

可自行阅读 vue-json-pretty文档

JsonEditor 组件位于 src/components/JsonEditor

用法

<script setup lang="ts">\n<script setup lang="ts">\nimport { ContentWrap } from '@/components/ContentWrap'\nimport { JsonEditor } from '@/components/JsonEditor'\nimport { useI18n } from '@/hooks/web/useI18n'\nimport { ref, watch } from 'vue'\n\nconst { t } = useI18n()\n\nconst defaultData = ref({\n  title: '标题',\n  content: '内容'\n})\n\nwatch(\n  () => defaultData.value,\n  (val) => {\n    console.log(val)\n  },\n  {\n    deep: true\n  }\n)\n\nsetTimeout(() => {\n  defaultData.value = {\n    title: '异步标题',\n    content: '异步内容'\n  }\n}, 4000)\n</script>\n\n<template>\n  <ContentWrap :title="t('richText.jsonEditor')" :message="t('richText.jsonEditorDes')">\n    <JsonEditor v-model="defaultData" />\n  </ContentWrap>\n</template>\n\n

JsonEditor 属性

可查看 vue-json-pretty文档

Editor 事件

可查看 vue-json-pretty文档

',10);p.render=function(a,t,p,e,c,l){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_json-editor.md.0577e01f.lean.js b/assets/components_json-editor.md.0577e01f.lean.js new file mode 100644 index 00000000..df13b8f7 --- /dev/null +++ b/assets/components_json-editor.md.0577e01f.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"JsonEditor JSON编辑器组件(2.2.0+)","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"JsonEditor 属性","slug":"jsoneditor-属性"},{"level":2,"title":"Editor 事件","slug":"editor-事件"}],"relativePath":"components/json-editor.md","lastUpdated":1718353615647}',p={},o=a('',10);p.render=function(a,t,p,e,c,l){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_permission.md.caa7a342.js b/assets/components_permission.md.caa7a342.js new file mode 100644 index 00000000..a41bb993 --- /dev/null +++ b/assets/components_permission.md.caa7a342.js @@ -0,0 +1 @@ +import{o as a,c as n,a as s}from"./app.7e863e47.js";const t='{"title":"Permission 权限组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"在线例子","slug":"在线例子"},{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"基本用法","slug":"基本用法"},{"level":3,"title":"指令形式","slug":"指令形式"},{"level":3,"title":"函数形式","slug":"函数形式"},{"level":2,"title":"Permission 属性","slug":"permission-属性"}],"relativePath":"components/permission.md","lastUpdated":1718353615647}',e={},p=s('

Permission 权限组件

用于颗粒级别的按钮权限组件

Permission 组件位于 src/components/Permission

在线例子

在线例子

用法

由于项目中的颗粒级别的权限,是放在路由表中,所以会判断在当前路由 meta.permission 是否包含传入的权限值,有的话则展示。

如果权限实现不一致的话,可以自行改造下。

基本用法

<template>\n  <Permission permission="add">\n    <ElButton type="primary"> Add </ElButton>\n  </Permission>\n</template>\n\n

指令形式

权限控制目前还提供了指令的使用方式,并且已经全局注册,所以可以在任意组件中使用 v-hasPermi

<ElButton v-hasPermi="'add'" type="primary"> Add </ElButton>\n\n

函数形式

除了以上两种,还可以使用函数的形式进行控制

import { hasPermi } from '@/components/Permission'\n\n
<ElButton v-if="hasPermi('add')" type="primary"> Add </ElButton>\n\n

Permission 属性

属性说明类型可选值默认值
permission权限值string--
',19);e.render=function(s,t,e,o,c,l){return a(),n("div",null,[p])};export default e;export{t as __pageData}; diff --git a/assets/components_permission.md.caa7a342.lean.js b/assets/components_permission.md.caa7a342.lean.js new file mode 100644 index 00000000..8983b101 --- /dev/null +++ b/assets/components_permission.md.caa7a342.lean.js @@ -0,0 +1 @@ +import{o as a,c as n,a as s}from"./app.7e863e47.js";const t='{"title":"Permission 权限组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"在线例子","slug":"在线例子"},{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"基本用法","slug":"基本用法"},{"level":3,"title":"指令形式","slug":"指令形式"},{"level":3,"title":"函数形式","slug":"函数形式"},{"level":2,"title":"Permission 属性","slug":"permission-属性"}],"relativePath":"components/permission.md","lastUpdated":1718353615647}',e={},p=s('',19);e.render=function(s,t,e,o,c,l){return a(),n("div",null,[p])};export default e;export{t as __pageData}; diff --git a/assets/components_qrcode.md.fc1263fa.js b/assets/components_qrcode.md.fc1263fa.js new file mode 100644 index 00000000..db20729e --- /dev/null +++ b/assets/components_qrcode.md.fc1263fa.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.7e863e47.js";const s='{"title":"Qrcode 二维码组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Qrcode 属性","slug":"qrcode-属性"},{"level":2,"title":"Qrcode 事件","slug":"qrcode-事件"}],"relativePath":"components/qrcode.md","lastUpdated":1718353615647}',e={},d=n('

Qrcode 二维码组件

基于 qrcode 封装。

Qrcode 组件位于 src/components/Qrcode

用法

更复杂点的例子,请在线预览

<script setup lang="ts">\nimport { Qrcode } from '@/components/Qrcode'\n</script>\n\n<template>\n  <Qrcode text="vue-element-plus-admin" />\n</template>\n\n

Qrcode 属性

属性说明类型可选值默认值
tag以什么标签生成二维码stringcanvas/imgcanvas
text二维码内容string/Array--
optionsqrcode.js 配置项QRCodeRenderersOptions-{}
width二维码宽度number-200
logo二维码 logoQrcodeLogo/string--
disabled二维码是否过期boolean-false
disabledText二维码过期提示内容string--

Qrcode 事件

方法名说明回调参数
done生成二维码后的回调-
click二维码点击事件-
disabled-click二维码过期后点击事件-
',10);e.render=function(n,s,e,o,c,p){return t(),a("div",null,[d])};export default e;export{s as __pageData}; diff --git a/assets/components_qrcode.md.fc1263fa.lean.js b/assets/components_qrcode.md.fc1263fa.lean.js new file mode 100644 index 00000000..3e7a4df2 --- /dev/null +++ b/assets/components_qrcode.md.fc1263fa.lean.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.7e863e47.js";const s='{"title":"Qrcode 二维码组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Qrcode 属性","slug":"qrcode-属性"},{"level":2,"title":"Qrcode 事件","slug":"qrcode-事件"}],"relativePath":"components/qrcode.md","lastUpdated":1718353615647}',e={},d=n('',10);e.render=function(n,s,e,o,c,p){return t(),a("div",null,[d])};export default e;export{s as __pageData}; diff --git a/assets/components_search.md.9af6faeb.js b/assets/components_search.md.9af6faeb.js new file mode 100644 index 00000000..d1710373 --- /dev/null +++ b/assets/components_search.md.9af6faeb.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"Search 查询组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"基础用法","slug":"基础用法"},{"level":3,"title":"useSearch","slug":"usesearch"},{"level":2,"title":"Search 属性","slug":"search-属性"},{"level":2,"title":"Search 事件","slug":"search-事件"},{"level":2,"title":"Search 方法","slug":"search-方法"}],"relativePath":"components/search.md","lastUpdated":1718353615647}',p={},o=a('

Search 查询组件

基于 Form 组件封装,支持收缩展开。

Search 组件位于 src/components/Search

注意

推荐使用 tsx 来使用 Search 组件

用法

基础用法

更复杂例子,请在线预览

<script setup lang="ts">\nimport { Search } from '@/components/Search'\nimport { FormSchema } from '@/components/Form'\nimport { reactive } from 'vue'\n\nconst schema = reactive<FormSchema[]>([\n  {\n    field: 'field1',\n    label: 'input',\n    component: 'Input'\n  }\n])\n</script>\n\n<template>\n  <Search :schema="schema" />\n</template>\n\n

useSearch

对于复杂的场景,可以配合 useSearch 来使用。

<script setup lang="ts">\nimport { ContentWrap } from '@/components/ContentWrap'\nimport { useI18n } from '@/hooks/web/useI18n'\nimport { Search } from '@/components/Search'\nimport { reactive, ref, unref } from 'vue'\nimport { ElButton } from 'element-plus'\nimport { getDictOneApi } from '@/api/common'\nimport { FormSchema } from '@/components/Form'\nimport { useSearch } from '@/hooks/web/useSearch'\n\nconst { t } = useI18n()\n\nconst { searchRegister, searchMethods } = useSearch()\nconst { setSchema, setProps, setValues } = searchMethods\n\nconst schema = reactive<FormSchema[]>([\n  {\n    field: 'field1',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field2',\n    label: t('formDemo.select'),\n    component: 'Select',\n    componentProps: {\n      options: [\n        {\n          label: 'option1',\n          value: '1'\n        },\n        {\n          label: 'option2',\n          value: '2'\n        }\n      ],\n      on: {\n        change: (value: string) => {\n          console.log(value)\n        }\n      }\n    }\n  },\n  {\n    field: 'field3',\n    label: t('formDemo.radio'),\n    component: 'RadioGroup',\n    componentProps: {\n      options: [\n        {\n          label: 'option-1',\n          value: '1'\n        },\n        {\n          label: 'option-2',\n          value: '2'\n        }\n      ]\n    }\n  },\n  {\n    field: 'field5',\n    component: 'DatePicker',\n    label: t('formDemo.datePicker'),\n    componentProps: {\n      type: 'date'\n    }\n  },\n  {\n    field: 'field6',\n    component: 'TimeSelect',\n    label: t('formDemo.timeSelect')\n  },\n  {\n    field: 'field8',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field9',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field10',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field11',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field12',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field13',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field14',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field15',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field16',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field17',\n    label: t('formDemo.input'),\n    component: 'Input'\n  },\n  {\n    field: 'field18',\n    label: t('formDemo.input'),\n    component: 'Input'\n  }\n])\n\nconst isGrid = ref(false)\n\nconst changeGrid = (grid: boolean) => {\n  setProps({\n    isCol: grid\n  })\n  // isGrid.value = grid\n}\n\nconst layout = ref('inline')\n\nconst changeLayout = () => {\n  layout.value = unref(layout) === 'inline' ? 'bottom' : 'inline'\n}\n\nconst buttonPosition = ref('left')\n\nconst changePosition = (position: string) => {\n  layout.value = 'bottom'\n  buttonPosition.value = position\n}\n\nconst getDictOne = async () => {\n  const res = await getDictOneApi()\n  if (res) {\n    setSchema([\n      {\n        field: 'field2',\n        path: 'componentProps.options',\n        value: res.data\n      }\n    ])\n  }\n}\n\nconst handleSearch = (data: any) => {\n  console.log(data)\n}\n\nconst delRadio = () => {\n  setSchema([\n    {\n      field: 'field3',\n      path: 'remove',\n      value: true\n    }\n  ])\n}\n\nconst restoreRadio = () => {\n  setSchema([\n    {\n      field: 'field3',\n      path: 'remove',\n      value: false\n    }\n  ])\n}\n\nconst setValue = () => {\n  setValues({\n    field1: 'Joy'\n  })\n}\n\nconst searchLoading = ref(false)\nconst changeSearchLoading = () => {\n  searchLoading.value = true\n  setTimeout(() => {\n    searchLoading.value = false\n  }, 2000)\n}\n\nconst resetLoading = ref(false)\nconst changeResetLoading = () => {\n  resetLoading.value = true\n  setTimeout(() => {\n    resetLoading.value = false\n  }, 2000)\n}\n</script>\n\n<template>\n  <ContentWrap\n    :title="`${t('searchDemo.search')} ${t('searchDemo.operate')}`"\n    style="margin-bottom: 20px"\n  >\n    <ElButton @click="changeGrid(true)">{{ t('searchDemo.grid') }}</ElButton>\n    <ElButton @click="changeGrid(false)">\n      {{ t('searchDemo.restore') }} {{ t('searchDemo.grid') }}\n    </ElButton>\n\n    <ElButton @click="changeLayout">\n      {{ t('searchDemo.button') }} {{ t('searchDemo.position') }}\n    </ElButton>\n\n    <ElButton @click="changePosition('left')">\n      {{ t('searchDemo.bottom') }} {{ t('searchDemo.position') }}-{{ t('searchDemo.left') }}\n    </ElButton>\n    <ElButton @click="changePosition('center')">\n      {{ t('searchDemo.bottom') }} {{ t('searchDemo.position') }}-{{ t('searchDemo.center') }}\n    </ElButton>\n    <ElButton @click="changePosition('right')">\n      {{ t('searchDemo.bottom') }} {{ t('searchDemo.position') }}-{{ t('searchDemo.right') }}\n    </ElButton>\n    <ElButton @click="getDictOne">\n      {{ t('formDemo.select') }} {{ t('searchDemo.dynamicOptions') }}\n    </ElButton>\n    <ElButton @click="delRadio">{{ t('searchDemo.deleteRadio') }}</ElButton>\n    <ElButton @click="restoreRadio">{{ t('searchDemo.restoreRadio') }}</ElButton>\n    <ElButton @click="setValue">{{ t('formDemo.setValue') }}</ElButton>\n\n    <ElButton @click="changeSearchLoading">\n      {{ t('searchDemo.search') }} {{ t('searchDemo.loading') }}\n    </ElButton>\n    <ElButton @click="changeResetLoading">\n      {{ t('searchDemo.reset') }} {{ t('searchDemo.loading') }}\n    </ElButton>\n  </ContentWrap>\n\n  <ContentWrap :title="t('searchDemo.search')" :message="t('searchDemo.searchDes')">\n    <Search\n      :schema="schema"\n      :is-col="isGrid"\n      :layout="layout"\n      :button-position="buttonPosition"\n      :search-loading="searchLoading"\n      :reset-loading="resetLoading"\n      show-expand\n      expand-field="field6"\n      @search="handleSearch"\n      @reset="handleSearch"\n      @register="searchRegister"\n    />\n  </ContentWrap>\n</template>\n\n

参数介绍

const { searchRegister, searchMethods } = useSearch()\n

register

searchRegister 用于注册 useSearch,如果需要使用 useSearch 提供的 api,必须将 searchRegister 传入组件的 onRegister

formMethods

方法名说明回调参数
setValues用于设置表单值(data: Recordable) => void
setProps用于设置表单属性(props: Recordable) => void
delSchema用于删除表单结构(field: string) => void
addSchema用于新增表单结构(formSchema: FormSchema, index?: number) => void
setSchema用于编辑表单结构(schemaProps: FormSetPropsType[]) => void
getFormData用于获取表单数据<T = Recordable>() => Promise<T>

Search 属性

属性说明类型可选值默认值
schema生成 Search 的布局结构数组,详见FormSchema-[]
isCol是否需要栅格布局boolean-true
labelWidth表单 label 宽度string/number-auto
layout操作按钮风格位置stringinline/bottominline
buttonPosition底部操作按钮的对齐方式stringleft/center/rightcenter
showSearch是否显示查询按钮boolean-true
showReset是否显示重置按钮boolean-true
expand是否显示伸缩按钮boolean-false
expandField伸缩的界限字段string--
inline是否是行内boolean-true
removeNoValueItem是否自动去除空值boolean-true
model初始化数据object--
searchLoading查询按钮加载状态boolean-false
resetLoading重置按钮加载状态boolean-false

Search 事件

方法名说明回调参数
search查询后的回调data: Recordable
reset重置后的回调data: Recordable

Search 方法

方法名说明回调参数
setValues用于设置表单值(data: Recordable) => void
setProps用于设置表单属性(props: Recordable) => void
delSchema用于删除表单结构(field: string) => void
addSchema用于新增表单结构(formSchema: FormSchema, index?: number) => void
setSchema用于编辑表单结构(schemaProps: FormSetPropsType[]) => void
getElFormExpose用于获取 Form 组件的实例() => Promise<typeof ElForm>
',23);p.render=function(a,t,p,e,c,l){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_search.md.9af6faeb.lean.js b/assets/components_search.md.9af6faeb.lean.js new file mode 100644 index 00000000..262a246e --- /dev/null +++ b/assets/components_search.md.9af6faeb.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"Search 查询组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"基础用法","slug":"基础用法"},{"level":3,"title":"useSearch","slug":"usesearch"},{"level":2,"title":"Search 属性","slug":"search-属性"},{"level":2,"title":"Search 事件","slug":"search-事件"},{"level":2,"title":"Search 方法","slug":"search-方法"}],"relativePath":"components/search.md","lastUpdated":1718353615647}',p={},o=a('',23);p.render=function(a,t,p,e,c,l){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_sticky.md.0dbd9241.js b/assets/components_sticky.md.0dbd9241.js new file mode 100644 index 00000000..4a3a8f39 --- /dev/null +++ b/assets/components_sticky.md.0dbd9241.js @@ -0,0 +1 @@ +import{o as t,c as a,a as s}from"./app.7e863e47.js";const n='{"title":"Sticky 黏性组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Sticky 属性","slug":"sticky-属性"}],"relativePath":"components/sticky.md","lastUpdated":1718353615647}',p={},e=s('

Sticky 黏性组件

1.2.4 新增

Sticky 组件位于 src/components/Sticky

用法

<script setup lang="ts">\nimport { Sticky } from '@/components/Sticky'\n</script>\n\n<template>\n  <Sticky :offset="90">\n    <div style="padding: 10px; background-color: lightblue"> Sticky 距离顶部90px </div>\n  </Sticky>\n</template>\n\n

Sticky 属性

属性说明类型可选值默认值
offset距离顶部或者底部的距离number-0
zIndex设置元素的堆叠顺序number-999
className设置指定的classstring/number--
position定位方式,默认为(top),表示距离顶部位置,可以设置为top或者bottomstringtop/bottomtop
',7);p.render=function(s,n,p,o,c,l){return t(),a("div",null,[e])};export default p;export{n as __pageData}; diff --git a/assets/components_sticky.md.0dbd9241.lean.js b/assets/components_sticky.md.0dbd9241.lean.js new file mode 100644 index 00000000..ab705b3e --- /dev/null +++ b/assets/components_sticky.md.0dbd9241.lean.js @@ -0,0 +1 @@ +import{o as t,c as a,a as s}from"./app.7e863e47.js";const n='{"title":"Sticky 黏性组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Sticky 属性","slug":"sticky-属性"}],"relativePath":"components/sticky.md","lastUpdated":1718353615647}',p={},e=s('',7);p.render=function(s,n,p,o,c,l){return t(),a("div",null,[e])};export default p;export{n as __pageData}; diff --git a/assets/components_table.md.c485d0cd.js b/assets/components_table.md.c485d0cd.js new file mode 100644 index 00000000..c5a3b14b --- /dev/null +++ b/assets/components_table.md.c485d0cd.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.7e863e47.js";const t='{"title":"Table 表格组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"基础用法","slug":"基础用法"},{"level":3,"title":"useTable","slug":"usetable"},{"level":2,"title":"Table 属性","slug":"table-属性"},{"level":3,"title":"Columns","slug":"columns"},{"level":3,"title":"Pagination","slug":"pagination"},{"level":2,"title":"Table 方法","slug":"table-方法"}],"relativePath":"components/table.md","lastUpdated":1718353615647}',p={},o=s('

Table 表格组件

element-plusTable 组件进行封装,只需传入 columnsdata 参数,即可渲染出响应的表格出来。

Table 组件位于 src/components/Table

注意

推荐使用 tsx 来使用 Table 组件。

用法

基础用法

<script setup lang="ts">\nimport { reactive } from 'vue'\nimport { Table, TableColumn } from '@/components/Table'\n\nconst columns = reactive<TableColumn[]>([\n  {\n    field: 'title',\n    label: 'title'\n  },\n  {\n    field: 'author',\n    label: 'author'\n  }\n])\n\nconst data = reactive([\n  {\n    title: 'title1',\n    author: 'author1'\n  },\n  {\n    title: 'title2',\n    author: 'author2'\n  },\n  {\n    title: 'title3',\n    author: 'author3'\n  }\n])\n</script>\n\n<template>\n  <Table :columns="columns" :data="data" />\n</template>\n\n

useTable

推荐配合 useTable 来使用

复杂点的例子,请在线预览

<script setup lang="tsx">\nimport { ContentWrap } from '@/components/ContentWrap'\nimport { useI18n } from '@/hooks/web/useI18n'\nimport { Table, TableColumn, TableSlotDefault } from '@/components/Table'\nimport { getTreeTableListApi } from '@/api/table'\nimport { reactive, unref } from 'vue'\nimport { ElTag, ElButton } from 'element-plus'\nimport { useTable } from '@/hooks/web/useTable'\n\nconst { tableRegister, tableState } = useTable({\n  fetchDataApi: async () => {\n    const { currentPage, pageSize } = tableState\n    const res = await getTreeTableListApi({\n      pageIndex: unref(currentPage),\n      pageSize: unref(pageSize)\n    })\n    return {\n      list: res.data.list,\n      total: res.data.total\n    }\n  }\n})\nconst { loading, dataList, total, currentPage, pageSize } = tableState\n\nconst { t } = useI18n()\n\nconst columns = reactive<TableColumn[]>([\n  {\n    field: 'selection',\n    type: 'selection'\n  },\n  {\n    field: 'index',\n    label: t('tableDemo.index'),\n    type: 'index'\n  },\n  {\n    field: 'content',\n    label: t('tableDemo.header'),\n    children: [\n      {\n        field: 'title',\n        label: t('tableDemo.title')\n      },\n      {\n        field: 'author',\n        label: t('tableDemo.author')\n      },\n      {\n        field: 'display_time',\n        label: t('tableDemo.displayTime')\n      },\n      {\n        field: 'importance',\n        label: t('tableDemo.importance'),\n        formatter: (_: Recordable, __: TableColumn, cellValue: number) => {\n          return (\n            <ElTag type={cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'}>\n              {cellValue === 1\n                ? t('tableDemo.important')\n                : cellValue === 2\n                ? t('tableDemo.good')\n                : t('tableDemo.commonly')}\n            </ElTag>\n          )\n        }\n      },\n      {\n        field: 'pageviews',\n        label: t('tableDemo.pageviews')\n      }\n    ]\n  },\n  {\n    field: 'action',\n    label: t('tableDemo.action'),\n    slots: {\n      default: (data) => {\n        return (\n          <ElButton type="primary" onClick={() => actionFn(data)}>\n            {t('tableDemo.action')}\n          </ElButton>\n        )\n      }\n    }\n  }\n])\n\nconst actionFn = (data: TableSlotDefault) => {\n  console.log(data)\n}\n</script>\n\n<template>\n  <ContentWrap :title="`${t('router.treeTable')} ${t('tableDemo.example')}`">\n    <Table\n      v-model:pageSize="pageSize"\n      v-model:currentPage="currentPage"\n      :columns="columns"\n      :data="dataList"\n      row-key="id"\n      :loading="loading"\n      sortable\n      :pagination="{\n        total: total\n      }"\n      @register="tableRegister"\n    />\n  </ContentWrap>\n</template>\n\n</script>\n\n<template>\n  <Table\n    v-model:pageSize="tableObject.pageSize"\n    v-model:currentPage="tableObject.currentPage"\n    :data="tableObject.tableList"\n    :loading="tableObject.loading"\n    :pagination="{\n      total: tableObject.total\n    }"\n    @register="register"\n  />\n</template>\n\n

参数介绍

const { tableRegister, tableState, tableMethods } = useTable(props: UseTableConfig)\n

props

在使用 useTable 的时候,需要传入 fetchDataApi,为了保证可定制,需要自行在 fetchDataApi 中完成请求逻辑,之后返回结果 { list: Array, total?: number },后续分页,就可以自动请求数据。

如果需要删除,同样需要传入 fetchDelApi ,返回一个 Boolean 来判断是否删除完成,后续 useTable 将自行刷新表格。

tableRegister

tableRegister 用于注册 useTable,如果需要使用 useTable 提供的 api,必须将 tableRegister 传入组件的 onRegister

tableState

表格状态

属性说明类型可选值默认值
pageSize每页显示多少条number-10
currentPage当前页number-1
total总条数number--
dataList表格数据any[]-[]
loading表格是否加载中boolean-false

tableMethods

方法名说明回调参数
setProps用于表格组件属性(props: Recordable) => void
getList获取表格数据() => Promise<void>
setColumn设置表头结构(columnProps: TableSetProps[]) => void
addColumn新增表头结构(tableColumn: TableColumn, index?: number) => void
delColumn删除表头结构(field: string) => void
getElTableExpose获取 ElTable 实例() => Promise<typeof ElTable>
refresh刷新表格() => void
delList删除数据(idsLength: number) => Promise<void>

Table 属性

除以下参数外,还支持 element-plusTable 所有属性,详见

属性说明类型可选值默认值
pageSize每页显示多少条,支持 v-model 双向绑定number-10
currentPage当前页,支持 v-model 双向绑定number-1
selection是否多选boolean-true
showOverflowTooltip是否所有的超出隐藏,优先级低于 schema 中的 showOverflowTooltipboolean-true
columns表头结构,详见TableColumn[]-[]
expand是否显示展开行boolean-false
pagination是否展示分页,详见Pagination/undefined--
reserveSelection仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)boolean-false
loading加载状态boolean-false
reserveIndex是否叠加索引boolean-false
align内容对齐方式stringleft/center/rightleft
headerAlign表头对齐方式stringleft/center/rightleft
data表格数据Recordable[]-[]
showAction是否显示表格操作boolean-false
imagePreview需要展示图片的字段string[]--
videoPreview需要展示视频的字段string[]--
customContent是否自定义内容boolean-false
cardBodyStyle卡片内容样式CSSProperties--
cardBodyClass卡片内容类名string--
cardWrapStyle卡片容器样式CSSProperties--
cardWrapClass卡片容器类名string--

Columns

除了以下属性,还支持 element-plusTableColumn 属性。

属性说明类型可选值默认值
field唯一值,如需展示正确的数据,需要与 data 中的属性名对应string--
label表头名称string--
hidden是否隐藏boolean--
slots插槽对象object--
children子项,用于多级表头TableColumn[]--

Pagination

支持 element-plusPagination 所有属性,详见

Table 方法

方法名说明回调参数
setProps用于设置表格属性(props: Recordable) => void
setColumn用于修改表头结构(columnProps: TableSetPropsType[]) => void
addColumn新增表头结构(tableColumn: TableColumn, index?: number) => void
delColumn删除表头结构(field: string) => void
',33);p.render=function(s,t,p,e,c,l){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_table.md.c485d0cd.lean.js b/assets/components_table.md.c485d0cd.lean.js new file mode 100644 index 00000000..977f4039 --- /dev/null +++ b/assets/components_table.md.c485d0cd.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.7e863e47.js";const t='{"title":"Table 表格组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"基础用法","slug":"基础用法"},{"level":3,"title":"useTable","slug":"usetable"},{"level":2,"title":"Table 属性","slug":"table-属性"},{"level":3,"title":"Columns","slug":"columns"},{"level":3,"title":"Pagination","slug":"pagination"},{"level":2,"title":"Table 方法","slug":"table-方法"}],"relativePath":"components/table.md","lastUpdated":1718353615647}',p={},o=s('',33);p.render=function(s,t,p,e,c,l){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_video-player.md.93fe127a.js b/assets/components_video-player.md.93fe127a.js new file mode 100644 index 00000000..3d1f166f --- /dev/null +++ b/assets/components_video-player.md.93fe127a.js @@ -0,0 +1 @@ +import{o as a,c as t,a as n}from"./app.7e863e47.js";const s='{"title":"VideoPlayer 视频播放器组件(2.5.0+)","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"VideoPlayer 属性","slug":"videoplayer-属性"}],"relativePath":"components/video-player.md","lastUpdated":1718353615647}',e={},p=n('

VideoPlayer 视频播放器组件(2.5.0+)

基于 xgplayer 二次封装的视频播放器

VideoPlayer 组件位于 src/components/VideoPlayer

用法

<script lang="ts" setup>\nimport { VideoPlayer } from '@/components/VideoPlayer'\n</script>\n\n<template>\n  <VideoPlayer\n    url="//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-720p.mp4"\n    poster="//lf3-static.bytednsdoc.com/obj/eden-cn/nupenuvpxnuvo/xgplayer_doc/poster.jpg"\n  />\n</template>\n\n

VideoPlayer 属性

属性说明类型可选值默认值
url视频的地址string--
poster视频的封面string--
',7);e.render=function(n,s,e,o,l,c){return a(),t("div",null,[p])};export default e;export{s as __pageData}; diff --git a/assets/components_video-player.md.93fe127a.lean.js b/assets/components_video-player.md.93fe127a.lean.js new file mode 100644 index 00000000..80a67a49 --- /dev/null +++ b/assets/components_video-player.md.93fe127a.lean.js @@ -0,0 +1 @@ +import{o as a,c as t,a as n}from"./app.7e863e47.js";const s='{"title":"VideoPlayer 视频播放器组件(2.5.0+)","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"VideoPlayer 属性","slug":"videoplayer-属性"}],"relativePath":"components/video-player.md","lastUpdated":1718353615647}',e={},p=n('',7);e.render=function(n,s,e,o,l,c){return a(),t("div",null,[p])};export default e;export{s as __pageData}; diff --git a/assets/components_video-viewer.md.78c5e19a.js b/assets/components_video-viewer.md.78c5e19a.js new file mode 100644 index 00000000..bbc7f7bd --- /dev/null +++ b/assets/components_video-viewer.md.78c5e19a.js @@ -0,0 +1 @@ +import{o as a,c as n,a as s}from"./app.7e863e47.js";const t='{"title":"VideoViewer 图片预览组件(2.5.0+)","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"VideoViewer","slug":"videoviewer"},{"level":3,"title":"参数","slug":"参数"}],"relativePath":"components/video-viewer.md","lastUpdated":1718353615647}',e={},p=s('

VideoViewer 图片预览组件(2.5.0+)

VideoPlayer 组件函数化,通过函数方便创建组件。

VideoViewer 组件位于 src/components/VideoViewer

用法

<script setup lang="ts">\nimport { createVideoViewer } from '@/components/VideoPlayer'\n\nconst open = () => {\n  createVideoViewer({\n    url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-720p.mp4',\n    poster: '//lf3-static.bytednsdoc.com/obj/eden-cn/nupenuvpxnuvo/xgplayer_doc/poster.jpg'\n  })\n}\n</script>\n\n<template>\n  <ElButton type="primary" @click="open">预览</ElButton>\n</template>\n\n

VideoViewer

参数

属性说明类型可选值默认值
url视频的地址string--
poster视频的封面string--
',8);e.render=function(s,t,e,o,c,l){return a(),n("div",null,[p])};export default e;export{t as __pageData}; diff --git a/assets/components_video-viewer.md.78c5e19a.lean.js b/assets/components_video-viewer.md.78c5e19a.lean.js new file mode 100644 index 00000000..73039c04 --- /dev/null +++ b/assets/components_video-viewer.md.78c5e19a.lean.js @@ -0,0 +1 @@ +import{o as a,c as n,a as s}from"./app.7e863e47.js";const t='{"title":"VideoViewer 图片预览组件(2.5.0+)","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"VideoViewer","slug":"videoviewer"},{"level":3,"title":"参数","slug":"参数"}],"relativePath":"components/video-viewer.md","lastUpdated":1718353615647}',e={},p=s('',8);e.render=function(s,t,e,o,c,l){return a(),n("div",null,[p])};export default e;export{t as __pageData}; diff --git a/assets/components_waterfall.md.bebda31b.js b/assets/components_waterfall.md.bebda31b.js new file mode 100644 index 00000000..4b296cc6 --- /dev/null +++ b/assets/components_waterfall.md.bebda31b.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.7e863e47.js";const t='{"title":"Waterfall 瀑布流组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Waterfall 属性","slug":"waterfall-属性"},{"level":2,"title":"Waterfall 事件","slug":"waterfall-事件"}],"relativePath":"components/waterfall.md","lastUpdated":1718353615647}',p={},o=s('

Waterfall 瀑布流组件

瀑布流组件

Waterfall 组件位于 src/components/Waterfall

TIP

data 数据必须带有高度字段,用于确保计算出正确的位置

用法

<script lang="ts" setup>\nimport { Waterfall } from '@/components/Waterfall'\nimport Mock from 'mockjs'\nimport { ref, unref } from 'vue'\nimport { toAnyString } from '@/utils'\n\nconst data = ref<any>([])\n\nconst getList = () => {\n  const list: any = []\n  for (let i = 0; i < 20; i++) {\n    // 随机 100, 500 之间的整数\n    const height = Mock.Random.integer(100, 500)\n    const width = Mock.Random.integer(100, 500)\n    list.push(\n      Mock.mock({\n        width,\n        height,\n        id: toAnyString(),\n        image_uri: Mock.Random.image(`${width}x${height}`)\n      })\n    )\n  }\n  data.value = [...unref(data), ...list]\n  if (unref(data).length >= 60) {\n    end.value = true\n  }\n}\ngetList()\n\nconst loading = ref(false)\n\nconst end = ref(false)\n\nconst loadMore = () => {\n  loading.value = true\n  setTimeout(() => {\n    getList()\n    loading.value = false\n  }, 1000)\n}\n</script>\n\n<template>\n  <Waterfall\n    :data="data"\n    :loading="loading"\n    :end="end"\n    :props="{\n      src: 'image_uri',\n      height: 'height'\n    }"\n    @load-more="loadMore"\n  />\n</template>\n\n

Waterfall 属性

属性说明类型可选值默认值
data要展示的数据Array--
reset窗口变化是否重新布局booleantrue/falsetrue
width每个项的宽度number-200
gap每个项的间距number-20
loadingText加载中文字string-加载中...
loading是否加载中boolean-false
end是否加载结束boolean-false
endText是否加载结束文字string-没有更多了
props字段别名object-{ src: 'src', height: 'height' }

Waterfall 事件

方法名说明回调参数
loadMore加载更多事件-
',10);p.render=function(s,t,p,e,c,l){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_waterfall.md.bebda31b.lean.js b/assets/components_waterfall.md.bebda31b.lean.js new file mode 100644 index 00000000..4e97283a --- /dev/null +++ b/assets/components_waterfall.md.bebda31b.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.7e863e47.js";const t='{"title":"Waterfall 瀑布流组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":2,"title":"Waterfall 属性","slug":"waterfall-属性"},{"level":2,"title":"Waterfall 事件","slug":"waterfall-事件"}],"relativePath":"components/waterfall.md","lastUpdated":1718353615647}',p={},o=s('',10);p.render=function(s,t,p,e,c,l){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/dep_create-module.md.ac3e139c.js b/assets/dep_create-module.md.ac3e139c.js new file mode 100644 index 00000000..816b9351 --- /dev/null +++ b/assets/dep_create-module.md.ac3e139c.js @@ -0,0 +1 @@ +import{o as e,c as o,a as d}from"./app.7e863e47.js";const a='{"title":"模版生成","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"生成组件","slug":"生成组件"},{"level":2,"title":"生成视图","slug":"生成视图"},{"level":2,"title":"如何扩展","slug":"如何扩展"}],"relativePath":"dep/create-module.md","lastUpdated":1718353615647}',c={},r=d('

模版生成

介绍

为了方便开发者快速生成 组件视图 文件,本项目提供了 plop 为开发者生成统一的文件模版。

生成组件

运行

npm run p\n

选择 component 之后,输入组件名,如 newCom,既可在 src/components 目录下创建对应的组件。

组件名开头如果是小写,会自动转换为大写。

生成视图

运行

npm run p\n

选择 view 之后,输入路径,默认为 views,接着输入模块名,如 newView,既可在 src/${views} 目录下创建对应的视图文件。

如何扩展

如果需要扩展额外的视图模版,可以在根目录 plopfile.js 文件中,添加初始模版,然后到根目录 plop 文件夹中,添加对应的模块代码。具体可以参考 component 下的代码。

更多的 plop 配置,则可以查阅 文档

',15);c.render=function(d,a,c,p,n,l){return e(),o("div",null,[r])};export default c;export{a as __pageData}; diff --git a/assets/dep_create-module.md.ac3e139c.lean.js b/assets/dep_create-module.md.ac3e139c.lean.js new file mode 100644 index 00000000..6f6b40d6 --- /dev/null +++ b/assets/dep_create-module.md.ac3e139c.lean.js @@ -0,0 +1 @@ +import{o as e,c as o,a as d}from"./app.7e863e47.js";const a='{"title":"模版生成","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"生成组件","slug":"生成组件"},{"level":2,"title":"生成视图","slug":"生成视图"},{"level":2,"title":"如何扩展","slug":"如何扩展"}],"relativePath":"dep/create-module.md","lastUpdated":1718353615647}',c={},r=d('',15);c.render=function(d,a,c,p,n,l){return e(),o("div",null,[r])};export default c;export{a as __pageData}; diff --git a/assets/dep_dark.md.5137d629.js b/assets/dep_dark.md.5137d629.js new file mode 100644 index 00000000..faa57b18 --- /dev/null +++ b/assets/dep_dark.md.5137d629.js @@ -0,0 +1 @@ +import{o as e,c as a,a as r}from"./app.7e863e47.js";const d='{"title":"黑暗主题","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"切换主题","slug":"切换主题"}],"relativePath":"dep/dark.md","lastUpdated":1718353615647}',t={},h=r('

黑暗主题

介绍

默认第一次进入系统,会检测浏览器默认的主题

切换主题

如果需要切换 明亮 或者 暗黑 ,可以执行 appStore.setIsDark(val) 进行主题的切换。

具体例子可以查看登录页的右上角主题切换。

',6);t.render=function(r,d,t,o,i,l){return e(),a("div",null,[h])};export default t;export{d as __pageData}; diff --git a/assets/dep_dark.md.5137d629.lean.js b/assets/dep_dark.md.5137d629.lean.js new file mode 100644 index 00000000..12441028 --- /dev/null +++ b/assets/dep_dark.md.5137d629.lean.js @@ -0,0 +1 @@ +import{o as e,c as a,a as r}from"./app.7e863e47.js";const d='{"title":"黑暗主题","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"切换主题","slug":"切换主题"}],"relativePath":"dep/dark.md","lastUpdated":1718353615647}',t={},h=r('',6);t.render=function(r,d,t,o,i,l){return e(),a("div",null,[h])};export default t;export{d as __pageData}; diff --git a/assets/dep_i18n.md.ea190651.js b/assets/dep_i18n.md.ea190651.js new file mode 100644 index 00000000..2080a488 --- /dev/null +++ b/assets/dep_i18n.md.ea190651.js @@ -0,0 +1 @@ +import{o as n,c as a,b as s,d as t}from"./app.7e863e47.js";const e='{"title":"国际化","description":"","frontmatter":{},"headers":[{"level":2,"title":"I18n-ally 插件","slug":"i18n-ally-插件"},{"level":2,"title":"配置默认语言","slug":"配置默认语言"},{"level":2,"title":"语言文件","slug":"语言文件"},{"level":2,"title":"语言导入逻辑说明","slug":"语言导入逻辑说明"},{"level":2,"title":"使用","slug":"使用"},{"level":2,"title":"切换语言","slug":"切换语言"},{"level":2,"title":"新增","slug":"新增"},{"level":3,"title":"语言文件","slug":"语言文件-1"},{"level":3,"title":"新增语言","slug":"新增语言"},{"level":2,"title":"远程读取语言数据","slug":"远程读取语言数据"},{"level":3,"title":"useLocale","slug":"uselocale"}],"relativePath":"dep/i18n.md","lastUpdated":1718353615651}',o={},l=s("h1",{id:"国际化"},[s("a",{class:"header-anchor",href:"#国际化","aria-hidden":"true"},"#"),t(" 国际化")],-1),c=s("p",null,[t("如果你使用的 vscode 开发工具,则推荐安装 "),s("a",{href:"https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally",target:"_blank",rel:"noopener noreferrer"},"I18n-ally"),t(" 这个插件")],-1),p=s("h2",{id:"i18n-ally-插件"},[s("a",{class:"header-anchor",href:"#i18n-ally-插件","aria-hidden":"true"},"#"),t(" I18n-ally 插件")],-1),u=s("p",null,"安装了该插件后,你的代码内可以实时看到对应的语言内容",-1),r=s("p",null,[s("img",{src:"/images/i18n.png",alt:""})],-1),i=s("h2",{id:"配置默认语言"},[s("a",{class:"header-anchor",href:"#配置默认语言","aria-hidden":"true"},"#"),t(" 配置默认语言")],-1),k=s("p",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/config/locale.ts",target:"_blank",rel:"noopener noreferrer"},"src/config/locale.ts"),t(" 内配置 "),s("code",null,"currentLocale"),t(" 为其他语言。")],-1),d=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"import"),t(),s("span",{class:"token punctuation"},"{"),t(" useCache "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token keyword"},"from"),t(),s("span",{class:"token string"},"'@/hooks/web/useCache'"),t("\n"),s("span",{class:"token keyword"},"import"),t(" zhCn "),s("span",{class:"token keyword"},"from"),t(),s("span",{class:"token string"},"'element-plus/lib/locale/lang/zh-cn'"),t("\n"),s("span",{class:"token keyword"},"import"),t(" en "),s("span",{class:"token keyword"},"from"),t(),s("span",{class:"token string"},"'element-plus/lib/locale/lang/en'"),t("\n\n"),s("span",{class:"token keyword"},"const"),t(),s("span",{class:"token punctuation"},"{"),t(" wsCache "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token operator"},"="),t(),s("span",{class:"token function"},"useCache"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),t("\n\n"),s("span",{class:"token keyword"},"export"),t(),s("span",{class:"token keyword"},"const"),t(" elLocaleMap "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token punctuation"},"{"),t("\n "),s("span",{class:"token string"},"'zh-CN'"),s("span",{class:"token operator"},":"),t(" zhCn"),s("span",{class:"token punctuation"},","),t("\n en"),s("span",{class:"token operator"},":"),t(" en\n"),s("span",{class:"token punctuation"},"}"),t("\n"),s("span",{class:"token keyword"},"export"),t(),s("span",{class:"token keyword"},"interface"),t(),s("span",{class:"token class-name"},"LocaleState"),t(),s("span",{class:"token punctuation"},"{"),t("\n currentLocale"),s("span",{class:"token operator"},":"),t(" LocaleDropdownType\n localeMap"),s("span",{class:"token operator"},":"),t(" LocaleDropdownType"),s("span",{class:"token punctuation"},"["),s("span",{class:"token punctuation"},"]"),t("\n"),s("span",{class:"token punctuation"},"}"),t("\n\n"),s("span",{class:"token keyword"},"export"),t(),s("span",{class:"token keyword"},"const"),t(" localeModules"),s("span",{class:"token operator"},":"),t(" LocaleState "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token punctuation"},"{"),t("\n currentLocale"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token punctuation"},"{"),t("\n lang"),s("span",{class:"token operator"},":"),t(" wsCache"),s("span",{class:"token punctuation"},"."),s("span",{class:"token function"},"get"),s("span",{class:"token punctuation"},"("),s("span",{class:"token string"},"'lang'"),s("span",{class:"token punctuation"},")"),t(),s("span",{class:"token operator"},"||"),t(),s("span",{class:"token string"},"'zh-CN'"),s("span",{class:"token punctuation"},","),t("\n elLocale"),s("span",{class:"token operator"},":"),t(" elLocaleMap"),s("span",{class:"token punctuation"},"["),t("wsCache"),s("span",{class:"token punctuation"},"."),s("span",{class:"token function"},"get"),s("span",{class:"token punctuation"},"("),s("span",{class:"token string"},"'lang'"),s("span",{class:"token punctuation"},")"),t(),s("span",{class:"token operator"},"||"),t(),s("span",{class:"token string"},"'zh-CN'"),s("span",{class:"token punctuation"},"]"),t("\n "),s("span",{class:"token punctuation"},"}"),s("span",{class:"token punctuation"},","),t("\n "),s("span",{class:"token comment"},"// 多语言"),t("\n localeMap"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token punctuation"},"["),t("\n "),s("span",{class:"token punctuation"},"{"),t("\n lang"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token string"},"'zh-CN'"),s("span",{class:"token punctuation"},","),t("\n name"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token string"},"'简体中文'"),t("\n "),s("span",{class:"token punctuation"},"}"),s("span",{class:"token punctuation"},","),t("\n "),s("span",{class:"token punctuation"},"{"),t("\n lang"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token string"},"'en'"),s("span",{class:"token punctuation"},","),t("\n name"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token string"},"'English'"),t("\n "),s("span",{class:"token punctuation"},"}"),t("\n "),s("span",{class:"token punctuation"},"]"),t("\n"),s("span",{class:"token punctuation"},"}"),t("\n\n")])])],-1),g=s("h2",{id:"语言文件"},[s("a",{class:"header-anchor",href:"#语言文件","aria-hidden":"true"},"#"),t(" 语言文件")],-1),h=s("p",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/tree/master/src/locales",target:"_blank",rel:"noopener noreferrer"},"src/locales"),t(" 可以配置具体的语言,目前项目中的语言都是没有拆分的,全部放一起,后续会考虑拆分出来,比较好维护。")],-1),m=s("h2",{id:"语言导入逻辑说明"},[s("a",{class:"header-anchor",href:"#语言导入逻辑说明","aria-hidden":"true"},"#"),t(" 语言导入逻辑说明")],-1),f=s("p",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/plugins/vueI18n/index.ts",target:"_blank",rel:"noopener noreferrer"},"src/plugins/vueI18n/index.ts"),t(" 内可以看到")],-1),y=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"const"),t(" defaultLocal "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token keyword"},"await"),t(),s("span",{class:"token keyword"},"import"),s("span",{class:"token punctuation"},"("),s("span",{class:"token template-string"},[s("span",{class:"token template-punctuation string"},"`"),s("span",{class:"token string"},"../../locales/"),s("span",{class:"token interpolation"},[s("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token interpolation-punctuation punctuation"},"}")]),s("span",{class:"token string"},".ts"),s("span",{class:"token template-punctuation string"},"`")]),s("span",{class:"token punctuation"},")"),t("\n")])])],-1),w=s("p",null,[t("这会导入 "),s("code",null,"src/locales"),t(" 文件语言包。")],-1),b=s("h2",{id:"使用"},[s("a",{class:"header-anchor",href:"#使用","aria-hidden":"true"},"#"),t(" 使用")],-1),L=s("p",null,[t("引入项目自带的 "),s("code",null,"useI18n"),t(),s("strong",null,"注意不要引入 vue-i18n 的 useI18n")],-1),v=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"import"),t(),s("span",{class:"token punctuation"},"{"),t(" useI18n "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token keyword"},"from"),t(),s("span",{class:"token string"},"'/@/hooks/web/useI18n'"),t("\n\n"),s("span",{class:"token keyword"},"const"),t(),s("span",{class:"token punctuation"},"{"),t(" t "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token operator"},"="),t(),s("span",{class:"token function"},"useI18n"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),t("\n\n"),s("span",{class:"token keyword"},"const"),t(" title "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token function"},"t"),s("span",{class:"token punctuation"},"("),s("span",{class:"token string"},"'common.menu'"),s("span",{class:"token punctuation"},")"),t("\n")])])],-1),I=s("h2",{id:"切换语言"},[s("a",{class:"header-anchor",href:"#切换语言","aria-hidden":"true"},"#"),t(" 切换语言")],-1),C=s("p",null,[t("切换语言需要使用 "),s("a",{href:"https://github.com/anncwb/vue-vben-admin/tree/main/src/locales/useLocale.ts",target:"_blank",rel:"noopener noreferrer"},"src/locales/useLocale.ts")],-1),M=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"import"),t(),s("span",{class:"token punctuation"},"{"),t(" useLocale "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token keyword"},"from"),t(),s("span",{class:"token string"},"'@/hooks/web/useLocale'"),t("\n"),s("span",{class:"token keyword"},"const"),t(),s("span",{class:"token punctuation"},"{"),t(" changeLocale "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token operator"},"="),t(),s("span",{class:"token function"},"useLocale"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),t("\n\n"),s("span",{class:"token function"},"changeLocale"),s("span",{class:"token punctuation"},"("),s("span",{class:"token string"},"'en'"),s("span",{class:"token punctuation"},")"),t("\n")])])],-1),_=s("h2",{id:"新增"},[s("a",{class:"header-anchor",href:"#新增","aria-hidden":"true"},"#"),t(" 新增")],-1),x=s("h3",{id:"语言文件-1"},[s("a",{class:"header-anchor",href:"#语言文件-1","aria-hidden":"true"},"#"),t(" 语言文件")],-1),z=s("p",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/tree/master/src/locales",target:"_blank",rel:"noopener noreferrer"},"src/locales"),t(" 增加对应语言的文件即可")],-1),S=s("h3",{id:"新增语言"},[s("a",{class:"header-anchor",href:"#新增语言","aria-hidden":"true"},"#"),t(" 新增语言")],-1),N=s("p",null,[t("目前项目自带的语言只有 "),s("code",null,"zh_CN"),t(" 和 "),s("code",null,"en"),t(" 两种")],-1),T=s("p",null,"如果需要新增,按以下操作即可",-1),O=s("ol",null,[s("li",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/tree/master/src/locales",target:"_blank",rel:"noopener noreferrer"},"src/locales"),t(" 下语言文件")]),s("li",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/tree/master/types/global.d.ts",target:"_blank",rel:"noopener noreferrer"},"types/global.d.ts"),t(" 给 "),s("code",null,"LocaleType"),t(" 添加对应的类型")]),s("li",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/config/locale.ts",target:"_blank",rel:"noopener noreferrer"},"src/config/locale.ts"),t(),s("code",null,"localeMap"),t(" 中添加对应语言")])],-1),W=s("h2",{id:"远程读取语言数据"},[s("a",{class:"header-anchor",href:"#远程读取语言数据","aria-hidden":"true"},"#"),t(" 远程读取语言数据")],-1),P=s("p",null,[t("目前项目会在 "),s("code",null,"src/main.ts"),t(" 内等待 "),s("code",null,"setupI18n"),t(" 这个函数执行完之后才会渲染界面,所以只需在 setupI18n 内的 "),s("code",null,"createI18nOptions"),t(" 发送 ajax 请求,将对应的数据设置到 i18n 实例上即可。")],-1),$=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"const"),t(" createI18nOptions "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token keyword"},"async"),t(),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token builtin"},"Promise"),s("span",{class:"token operator"},"<"),t("I18nOptions"),s("span",{class:"token operator"},">"),t(),s("span",{class:"token operator"},"=>"),t(),s("span",{class:"token punctuation"},"{"),t("\n "),s("span",{class:"token keyword"},"const"),t(" localeStore "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token function"},"useLocaleStoreWithOut"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),t("\n "),s("span",{class:"token keyword"},"const"),t(" locale "),s("span",{class:"token operator"},"="),t(" localeStore"),s("span",{class:"token punctuation"},"."),t("getCurrentLocale\n "),s("span",{class:"token keyword"},"const"),t(" localeMap "),s("span",{class:"token operator"},"="),t(" localeStore"),s("span",{class:"token punctuation"},"."),t("getLocaleMap\n "),s("span",{class:"token comment"},"// 这里改为远程请求即可。"),t("\n "),s("span",{class:"token keyword"},"const"),t(" defaultLocal "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token keyword"},"await"),t(),s("span",{class:"token keyword"},"import"),s("span",{class:"token punctuation"},"("),s("span",{class:"token template-string"},[s("span",{class:"token template-punctuation string"},"`"),s("span",{class:"token string"},"../../locales/"),s("span",{class:"token interpolation"},[s("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token interpolation-punctuation punctuation"},"}")]),s("span",{class:"token string"},".ts"),s("span",{class:"token template-punctuation string"},"`")]),s("span",{class:"token punctuation"},")"),t("\n "),s("span",{class:"token keyword"},"const"),t(" message "),s("span",{class:"token operator"},"="),t(" defaultLocal"),s("span",{class:"token punctuation"},"."),t("default "),s("span",{class:"token operator"},"??"),t(),s("span",{class:"token punctuation"},"{"),s("span",{class:"token punctuation"},"}"),t("\n\n "),s("span",{class:"token function"},"setHtmlPageLang"),s("span",{class:"token punctuation"},"("),t("locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token punctuation"},")"),t("\n\n localeStore"),s("span",{class:"token punctuation"},"."),s("span",{class:"token function"},"setCurrentLocale"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},"{"),t("\n lang"),s("span",{class:"token operator"},":"),t(" locale"),s("span",{class:"token punctuation"},"."),t("lang\n "),s("span",{class:"token comment"},"// elLocale: elLocal"),t("\n "),s("span",{class:"token punctuation"},"}"),s("span",{class:"token punctuation"},")"),t("\n\n "),s("span",{class:"token keyword"},"return"),t(),s("span",{class:"token punctuation"},"{"),t("\n legacy"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token boolean"},"false"),s("span",{class:"token punctuation"},","),t("\n locale"),s("span",{class:"token operator"},":"),t(" locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token punctuation"},","),t("\n fallbackLocale"),s("span",{class:"token operator"},":"),t(" locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token punctuation"},","),t("\n messages"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token punctuation"},"{"),t("\n "),s("span",{class:"token punctuation"},"["),t("locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token punctuation"},"]"),s("span",{class:"token operator"},":"),t(" message\n "),s("span",{class:"token punctuation"},"}"),s("span",{class:"token punctuation"},","),t("\n availableLocales"),s("span",{class:"token operator"},":"),t(" localeMap"),s("span",{class:"token punctuation"},"."),s("span",{class:"token function"},"map"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},"("),t("v"),s("span",{class:"token punctuation"},")"),t(),s("span",{class:"token operator"},"=>"),t(" v"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token punctuation"},")"),s("span",{class:"token punctuation"},","),t("\n sync"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token boolean"},"true"),s("span",{class:"token punctuation"},","),t("\n silentTranslationWarn"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token boolean"},"true"),s("span",{class:"token punctuation"},","),t("\n missingWarn"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token boolean"},"false"),s("span",{class:"token punctuation"},","),t("\n silentFallbackWarn"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token boolean"},"true"),t("\n "),s("span",{class:"token punctuation"},"}"),t("\n"),s("span",{class:"token punctuation"},"}"),t("\n")])])],-1),j=s("h3",{id:"uselocale"},[s("a",{class:"header-anchor",href:"#uselocale","aria-hidden":"true"},"#"),t(" useLocale")],-1),D=s("p",null,[t("代码: "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/hooks/web/useLocale.ts",target:"_blank",rel:"noopener noreferrer"},"src/hooks/web/useLocale/")],-1),A=s("p",null,[t("当手动切换语言的时候会触发 "),s("code",null,"useLocale"),t(" 函数,useLocale 也是异步函数,只需等待接口返回响应的数据后,再进行设置即可")],-1),E=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"export"),t(),s("span",{class:"token keyword"},"const"),t(),s("span",{class:"token function-variable function"},"useLocale"),t(),s("span",{class:"token operator"},"="),t(),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),t(),s("span",{class:"token operator"},"=>"),t(),s("span",{class:"token punctuation"},"{"),t("\n "),s("span",{class:"token comment"},"// Switching the language will change the locale of useI18n"),t("\n "),s("span",{class:"token comment"},"// And submit to configuration modification"),t("\n "),s("span",{class:"token keyword"},"const"),t(),s("span",{class:"token function-variable function"},"changeLocale"),t(),s("span",{class:"token operator"},"="),t(),s("span",{class:"token keyword"},"async"),t(),s("span",{class:"token punctuation"},"("),t("locale"),s("span",{class:"token operator"},":"),t(" LocaleType"),s("span",{class:"token punctuation"},")"),t(),s("span",{class:"token operator"},"=>"),t(),s("span",{class:"token punctuation"},"{"),t("\n "),s("span",{class:"token keyword"},"const"),t(" globalI18n "),s("span",{class:"token operator"},"="),t(" i18n"),s("span",{class:"token punctuation"},"."),t("global\n \n "),s("span",{class:"token comment"},"// 改为远程获取"),t("\n "),s("span",{class:"token keyword"},"const"),t(" langModule "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token keyword"},"await"),t(),s("span",{class:"token keyword"},"import"),s("span",{class:"token punctuation"},"("),s("span",{class:"token template-string"},[s("span",{class:"token template-punctuation string"},"`"),s("span",{class:"token string"},"../../locales/"),s("span",{class:"token interpolation"},[s("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),s("span",{class:"token interpolation-punctuation punctuation"},"}")]),s("span",{class:"token string"},".ts"),s("span",{class:"token template-punctuation string"},"`")]),s("span",{class:"token punctuation"},")"),t("\n\n globalI18n"),s("span",{class:"token punctuation"},"."),s("span",{class:"token function"},"setLocaleMessage"),s("span",{class:"token punctuation"},"("),t("locale"),s("span",{class:"token punctuation"},","),t(" langModule"),s("span",{class:"token punctuation"},"."),t("default"),s("span",{class:"token punctuation"},")"),t("\n\n "),s("span",{class:"token function"},"setI18nLanguage"),s("span",{class:"token punctuation"},"("),t("locale"),s("span",{class:"token punctuation"},")"),t("\n "),s("span",{class:"token punctuation"},"}"),t("\n\n "),s("span",{class:"token keyword"},"return"),t(),s("span",{class:"token punctuation"},"{"),t("\n changeLocale\n "),s("span",{class:"token punctuation"},"}"),t("\n"),s("span",{class:"token punctuation"},"}"),t("\n")])])],-1);o.render=function(s,t,e,o,F,H){return n(),a("div",null,[l,c,p,u,r,i,k,d,g,h,m,f,y,w,b,L,v,I,C,M,_,x,z,S,N,T,O,W,P,$,j,D,A,E])};export default o;export{e as __pageData}; diff --git a/assets/dep_i18n.md.ea190651.lean.js b/assets/dep_i18n.md.ea190651.lean.js new file mode 100644 index 00000000..2080a488 --- /dev/null +++ b/assets/dep_i18n.md.ea190651.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,b as s,d as t}from"./app.7e863e47.js";const e='{"title":"国际化","description":"","frontmatter":{},"headers":[{"level":2,"title":"I18n-ally 插件","slug":"i18n-ally-插件"},{"level":2,"title":"配置默认语言","slug":"配置默认语言"},{"level":2,"title":"语言文件","slug":"语言文件"},{"level":2,"title":"语言导入逻辑说明","slug":"语言导入逻辑说明"},{"level":2,"title":"使用","slug":"使用"},{"level":2,"title":"切换语言","slug":"切换语言"},{"level":2,"title":"新增","slug":"新增"},{"level":3,"title":"语言文件","slug":"语言文件-1"},{"level":3,"title":"新增语言","slug":"新增语言"},{"level":2,"title":"远程读取语言数据","slug":"远程读取语言数据"},{"level":3,"title":"useLocale","slug":"uselocale"}],"relativePath":"dep/i18n.md","lastUpdated":1718353615651}',o={},l=s("h1",{id:"国际化"},[s("a",{class:"header-anchor",href:"#国际化","aria-hidden":"true"},"#"),t(" 国际化")],-1),c=s("p",null,[t("如果你使用的 vscode 开发工具,则推荐安装 "),s("a",{href:"https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally",target:"_blank",rel:"noopener noreferrer"},"I18n-ally"),t(" 这个插件")],-1),p=s("h2",{id:"i18n-ally-插件"},[s("a",{class:"header-anchor",href:"#i18n-ally-插件","aria-hidden":"true"},"#"),t(" I18n-ally 插件")],-1),u=s("p",null,"安装了该插件后,你的代码内可以实时看到对应的语言内容",-1),r=s("p",null,[s("img",{src:"/images/i18n.png",alt:""})],-1),i=s("h2",{id:"配置默认语言"},[s("a",{class:"header-anchor",href:"#配置默认语言","aria-hidden":"true"},"#"),t(" 配置默认语言")],-1),k=s("p",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/config/locale.ts",target:"_blank",rel:"noopener noreferrer"},"src/config/locale.ts"),t(" 内配置 "),s("code",null,"currentLocale"),t(" 为其他语言。")],-1),d=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"import"),t(),s("span",{class:"token punctuation"},"{"),t(" useCache "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token keyword"},"from"),t(),s("span",{class:"token string"},"'@/hooks/web/useCache'"),t("\n"),s("span",{class:"token keyword"},"import"),t(" zhCn "),s("span",{class:"token keyword"},"from"),t(),s("span",{class:"token string"},"'element-plus/lib/locale/lang/zh-cn'"),t("\n"),s("span",{class:"token keyword"},"import"),t(" en "),s("span",{class:"token keyword"},"from"),t(),s("span",{class:"token string"},"'element-plus/lib/locale/lang/en'"),t("\n\n"),s("span",{class:"token keyword"},"const"),t(),s("span",{class:"token punctuation"},"{"),t(" wsCache "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token operator"},"="),t(),s("span",{class:"token function"},"useCache"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),t("\n\n"),s("span",{class:"token keyword"},"export"),t(),s("span",{class:"token keyword"},"const"),t(" elLocaleMap "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token punctuation"},"{"),t("\n "),s("span",{class:"token string"},"'zh-CN'"),s("span",{class:"token operator"},":"),t(" zhCn"),s("span",{class:"token punctuation"},","),t("\n en"),s("span",{class:"token operator"},":"),t(" en\n"),s("span",{class:"token punctuation"},"}"),t("\n"),s("span",{class:"token keyword"},"export"),t(),s("span",{class:"token keyword"},"interface"),t(),s("span",{class:"token class-name"},"LocaleState"),t(),s("span",{class:"token punctuation"},"{"),t("\n currentLocale"),s("span",{class:"token operator"},":"),t(" LocaleDropdownType\n localeMap"),s("span",{class:"token operator"},":"),t(" LocaleDropdownType"),s("span",{class:"token punctuation"},"["),s("span",{class:"token punctuation"},"]"),t("\n"),s("span",{class:"token punctuation"},"}"),t("\n\n"),s("span",{class:"token keyword"},"export"),t(),s("span",{class:"token keyword"},"const"),t(" localeModules"),s("span",{class:"token operator"},":"),t(" LocaleState "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token punctuation"},"{"),t("\n currentLocale"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token punctuation"},"{"),t("\n lang"),s("span",{class:"token operator"},":"),t(" wsCache"),s("span",{class:"token punctuation"},"."),s("span",{class:"token function"},"get"),s("span",{class:"token punctuation"},"("),s("span",{class:"token string"},"'lang'"),s("span",{class:"token punctuation"},")"),t(),s("span",{class:"token operator"},"||"),t(),s("span",{class:"token string"},"'zh-CN'"),s("span",{class:"token punctuation"},","),t("\n elLocale"),s("span",{class:"token operator"},":"),t(" elLocaleMap"),s("span",{class:"token punctuation"},"["),t("wsCache"),s("span",{class:"token punctuation"},"."),s("span",{class:"token function"},"get"),s("span",{class:"token punctuation"},"("),s("span",{class:"token string"},"'lang'"),s("span",{class:"token punctuation"},")"),t(),s("span",{class:"token operator"},"||"),t(),s("span",{class:"token string"},"'zh-CN'"),s("span",{class:"token punctuation"},"]"),t("\n "),s("span",{class:"token punctuation"},"}"),s("span",{class:"token punctuation"},","),t("\n "),s("span",{class:"token comment"},"// 多语言"),t("\n localeMap"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token punctuation"},"["),t("\n "),s("span",{class:"token punctuation"},"{"),t("\n lang"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token string"},"'zh-CN'"),s("span",{class:"token punctuation"},","),t("\n name"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token string"},"'简体中文'"),t("\n "),s("span",{class:"token punctuation"},"}"),s("span",{class:"token punctuation"},","),t("\n "),s("span",{class:"token punctuation"},"{"),t("\n lang"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token string"},"'en'"),s("span",{class:"token punctuation"},","),t("\n name"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token string"},"'English'"),t("\n "),s("span",{class:"token punctuation"},"}"),t("\n "),s("span",{class:"token punctuation"},"]"),t("\n"),s("span",{class:"token punctuation"},"}"),t("\n\n")])])],-1),g=s("h2",{id:"语言文件"},[s("a",{class:"header-anchor",href:"#语言文件","aria-hidden":"true"},"#"),t(" 语言文件")],-1),h=s("p",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/tree/master/src/locales",target:"_blank",rel:"noopener noreferrer"},"src/locales"),t(" 可以配置具体的语言,目前项目中的语言都是没有拆分的,全部放一起,后续会考虑拆分出来,比较好维护。")],-1),m=s("h2",{id:"语言导入逻辑说明"},[s("a",{class:"header-anchor",href:"#语言导入逻辑说明","aria-hidden":"true"},"#"),t(" 语言导入逻辑说明")],-1),f=s("p",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/plugins/vueI18n/index.ts",target:"_blank",rel:"noopener noreferrer"},"src/plugins/vueI18n/index.ts"),t(" 内可以看到")],-1),y=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"const"),t(" defaultLocal "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token keyword"},"await"),t(),s("span",{class:"token keyword"},"import"),s("span",{class:"token punctuation"},"("),s("span",{class:"token template-string"},[s("span",{class:"token template-punctuation string"},"`"),s("span",{class:"token string"},"../../locales/"),s("span",{class:"token interpolation"},[s("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token interpolation-punctuation punctuation"},"}")]),s("span",{class:"token string"},".ts"),s("span",{class:"token template-punctuation string"},"`")]),s("span",{class:"token punctuation"},")"),t("\n")])])],-1),w=s("p",null,[t("这会导入 "),s("code",null,"src/locales"),t(" 文件语言包。")],-1),b=s("h2",{id:"使用"},[s("a",{class:"header-anchor",href:"#使用","aria-hidden":"true"},"#"),t(" 使用")],-1),L=s("p",null,[t("引入项目自带的 "),s("code",null,"useI18n"),t(),s("strong",null,"注意不要引入 vue-i18n 的 useI18n")],-1),v=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"import"),t(),s("span",{class:"token punctuation"},"{"),t(" useI18n "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token keyword"},"from"),t(),s("span",{class:"token string"},"'/@/hooks/web/useI18n'"),t("\n\n"),s("span",{class:"token keyword"},"const"),t(),s("span",{class:"token punctuation"},"{"),t(" t "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token operator"},"="),t(),s("span",{class:"token function"},"useI18n"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),t("\n\n"),s("span",{class:"token keyword"},"const"),t(" title "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token function"},"t"),s("span",{class:"token punctuation"},"("),s("span",{class:"token string"},"'common.menu'"),s("span",{class:"token punctuation"},")"),t("\n")])])],-1),I=s("h2",{id:"切换语言"},[s("a",{class:"header-anchor",href:"#切换语言","aria-hidden":"true"},"#"),t(" 切换语言")],-1),C=s("p",null,[t("切换语言需要使用 "),s("a",{href:"https://github.com/anncwb/vue-vben-admin/tree/main/src/locales/useLocale.ts",target:"_blank",rel:"noopener noreferrer"},"src/locales/useLocale.ts")],-1),M=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"import"),t(),s("span",{class:"token punctuation"},"{"),t(" useLocale "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token keyword"},"from"),t(),s("span",{class:"token string"},"'@/hooks/web/useLocale'"),t("\n"),s("span",{class:"token keyword"},"const"),t(),s("span",{class:"token punctuation"},"{"),t(" changeLocale "),s("span",{class:"token punctuation"},"}"),t(),s("span",{class:"token operator"},"="),t(),s("span",{class:"token function"},"useLocale"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),t("\n\n"),s("span",{class:"token function"},"changeLocale"),s("span",{class:"token punctuation"},"("),s("span",{class:"token string"},"'en'"),s("span",{class:"token punctuation"},")"),t("\n")])])],-1),_=s("h2",{id:"新增"},[s("a",{class:"header-anchor",href:"#新增","aria-hidden":"true"},"#"),t(" 新增")],-1),x=s("h3",{id:"语言文件-1"},[s("a",{class:"header-anchor",href:"#语言文件-1","aria-hidden":"true"},"#"),t(" 语言文件")],-1),z=s("p",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/tree/master/src/locales",target:"_blank",rel:"noopener noreferrer"},"src/locales"),t(" 增加对应语言的文件即可")],-1),S=s("h3",{id:"新增语言"},[s("a",{class:"header-anchor",href:"#新增语言","aria-hidden":"true"},"#"),t(" 新增语言")],-1),N=s("p",null,[t("目前项目自带的语言只有 "),s("code",null,"zh_CN"),t(" 和 "),s("code",null,"en"),t(" 两种")],-1),T=s("p",null,"如果需要新增,按以下操作即可",-1),O=s("ol",null,[s("li",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/tree/master/src/locales",target:"_blank",rel:"noopener noreferrer"},"src/locales"),t(" 下语言文件")]),s("li",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/tree/master/types/global.d.ts",target:"_blank",rel:"noopener noreferrer"},"types/global.d.ts"),t(" 给 "),s("code",null,"LocaleType"),t(" 添加对应的类型")]),s("li",null,[t("在 "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/config/locale.ts",target:"_blank",rel:"noopener noreferrer"},"src/config/locale.ts"),t(),s("code",null,"localeMap"),t(" 中添加对应语言")])],-1),W=s("h2",{id:"远程读取语言数据"},[s("a",{class:"header-anchor",href:"#远程读取语言数据","aria-hidden":"true"},"#"),t(" 远程读取语言数据")],-1),P=s("p",null,[t("目前项目会在 "),s("code",null,"src/main.ts"),t(" 内等待 "),s("code",null,"setupI18n"),t(" 这个函数执行完之后才会渲染界面,所以只需在 setupI18n 内的 "),s("code",null,"createI18nOptions"),t(" 发送 ajax 请求,将对应的数据设置到 i18n 实例上即可。")],-1),$=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"const"),t(" createI18nOptions "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token keyword"},"async"),t(),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token builtin"},"Promise"),s("span",{class:"token operator"},"<"),t("I18nOptions"),s("span",{class:"token operator"},">"),t(),s("span",{class:"token operator"},"=>"),t(),s("span",{class:"token punctuation"},"{"),t("\n "),s("span",{class:"token keyword"},"const"),t(" localeStore "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token function"},"useLocaleStoreWithOut"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),t("\n "),s("span",{class:"token keyword"},"const"),t(" locale "),s("span",{class:"token operator"},"="),t(" localeStore"),s("span",{class:"token punctuation"},"."),t("getCurrentLocale\n "),s("span",{class:"token keyword"},"const"),t(" localeMap "),s("span",{class:"token operator"},"="),t(" localeStore"),s("span",{class:"token punctuation"},"."),t("getLocaleMap\n "),s("span",{class:"token comment"},"// 这里改为远程请求即可。"),t("\n "),s("span",{class:"token keyword"},"const"),t(" defaultLocal "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token keyword"},"await"),t(),s("span",{class:"token keyword"},"import"),s("span",{class:"token punctuation"},"("),s("span",{class:"token template-string"},[s("span",{class:"token template-punctuation string"},"`"),s("span",{class:"token string"},"../../locales/"),s("span",{class:"token interpolation"},[s("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token interpolation-punctuation punctuation"},"}")]),s("span",{class:"token string"},".ts"),s("span",{class:"token template-punctuation string"},"`")]),s("span",{class:"token punctuation"},")"),t("\n "),s("span",{class:"token keyword"},"const"),t(" message "),s("span",{class:"token operator"},"="),t(" defaultLocal"),s("span",{class:"token punctuation"},"."),t("default "),s("span",{class:"token operator"},"??"),t(),s("span",{class:"token punctuation"},"{"),s("span",{class:"token punctuation"},"}"),t("\n\n "),s("span",{class:"token function"},"setHtmlPageLang"),s("span",{class:"token punctuation"},"("),t("locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token punctuation"},")"),t("\n\n localeStore"),s("span",{class:"token punctuation"},"."),s("span",{class:"token function"},"setCurrentLocale"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},"{"),t("\n lang"),s("span",{class:"token operator"},":"),t(" locale"),s("span",{class:"token punctuation"},"."),t("lang\n "),s("span",{class:"token comment"},"// elLocale: elLocal"),t("\n "),s("span",{class:"token punctuation"},"}"),s("span",{class:"token punctuation"},")"),t("\n\n "),s("span",{class:"token keyword"},"return"),t(),s("span",{class:"token punctuation"},"{"),t("\n legacy"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token boolean"},"false"),s("span",{class:"token punctuation"},","),t("\n locale"),s("span",{class:"token operator"},":"),t(" locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token punctuation"},","),t("\n fallbackLocale"),s("span",{class:"token operator"},":"),t(" locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token punctuation"},","),t("\n messages"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token punctuation"},"{"),t("\n "),s("span",{class:"token punctuation"},"["),t("locale"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token punctuation"},"]"),s("span",{class:"token operator"},":"),t(" message\n "),s("span",{class:"token punctuation"},"}"),s("span",{class:"token punctuation"},","),t("\n availableLocales"),s("span",{class:"token operator"},":"),t(" localeMap"),s("span",{class:"token punctuation"},"."),s("span",{class:"token function"},"map"),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},"("),t("v"),s("span",{class:"token punctuation"},")"),t(),s("span",{class:"token operator"},"=>"),t(" v"),s("span",{class:"token punctuation"},"."),t("lang"),s("span",{class:"token punctuation"},")"),s("span",{class:"token punctuation"},","),t("\n sync"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token boolean"},"true"),s("span",{class:"token punctuation"},","),t("\n silentTranslationWarn"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token boolean"},"true"),s("span",{class:"token punctuation"},","),t("\n missingWarn"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token boolean"},"false"),s("span",{class:"token punctuation"},","),t("\n silentFallbackWarn"),s("span",{class:"token operator"},":"),t(),s("span",{class:"token boolean"},"true"),t("\n "),s("span",{class:"token punctuation"},"}"),t("\n"),s("span",{class:"token punctuation"},"}"),t("\n")])])],-1),j=s("h3",{id:"uselocale"},[s("a",{class:"header-anchor",href:"#uselocale","aria-hidden":"true"},"#"),t(" useLocale")],-1),D=s("p",null,[t("代码: "),s("a",{href:"https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/hooks/web/useLocale.ts",target:"_blank",rel:"noopener noreferrer"},"src/hooks/web/useLocale/")],-1),A=s("p",null,[t("当手动切换语言的时候会触发 "),s("code",null,"useLocale"),t(" 函数,useLocale 也是异步函数,只需等待接口返回响应的数据后,再进行设置即可")],-1),E=s("div",{class:"language-ts"},[s("pre",null,[s("code",null,[s("span",{class:"token keyword"},"export"),t(),s("span",{class:"token keyword"},"const"),t(),s("span",{class:"token function-variable function"},"useLocale"),t(),s("span",{class:"token operator"},"="),t(),s("span",{class:"token punctuation"},"("),s("span",{class:"token punctuation"},")"),t(),s("span",{class:"token operator"},"=>"),t(),s("span",{class:"token punctuation"},"{"),t("\n "),s("span",{class:"token comment"},"// Switching the language will change the locale of useI18n"),t("\n "),s("span",{class:"token comment"},"// And submit to configuration modification"),t("\n "),s("span",{class:"token keyword"},"const"),t(),s("span",{class:"token function-variable function"},"changeLocale"),t(),s("span",{class:"token operator"},"="),t(),s("span",{class:"token keyword"},"async"),t(),s("span",{class:"token punctuation"},"("),t("locale"),s("span",{class:"token operator"},":"),t(" LocaleType"),s("span",{class:"token punctuation"},")"),t(),s("span",{class:"token operator"},"=>"),t(),s("span",{class:"token punctuation"},"{"),t("\n "),s("span",{class:"token keyword"},"const"),t(" globalI18n "),s("span",{class:"token operator"},"="),t(" i18n"),s("span",{class:"token punctuation"},"."),t("global\n \n "),s("span",{class:"token comment"},"// 改为远程获取"),t("\n "),s("span",{class:"token keyword"},"const"),t(" langModule "),s("span",{class:"token operator"},"="),t(),s("span",{class:"token keyword"},"await"),t(),s("span",{class:"token keyword"},"import"),s("span",{class:"token punctuation"},"("),s("span",{class:"token template-string"},[s("span",{class:"token template-punctuation string"},"`"),s("span",{class:"token string"},"../../locales/"),s("span",{class:"token interpolation"},[s("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),s("span",{class:"token interpolation-punctuation punctuation"},"}")]),s("span",{class:"token string"},".ts"),s("span",{class:"token template-punctuation string"},"`")]),s("span",{class:"token punctuation"},")"),t("\n\n globalI18n"),s("span",{class:"token punctuation"},"."),s("span",{class:"token function"},"setLocaleMessage"),s("span",{class:"token punctuation"},"("),t("locale"),s("span",{class:"token punctuation"},","),t(" langModule"),s("span",{class:"token punctuation"},"."),t("default"),s("span",{class:"token punctuation"},")"),t("\n\n "),s("span",{class:"token function"},"setI18nLanguage"),s("span",{class:"token punctuation"},"("),t("locale"),s("span",{class:"token punctuation"},")"),t("\n "),s("span",{class:"token punctuation"},"}"),t("\n\n "),s("span",{class:"token keyword"},"return"),t(),s("span",{class:"token punctuation"},"{"),t("\n changeLocale\n "),s("span",{class:"token punctuation"},"}"),t("\n"),s("span",{class:"token punctuation"},"}"),t("\n")])])],-1);o.render=function(s,t,e,o,F,H){return n(),a("div",null,[l,c,p,u,r,i,k,d,g,h,m,f,y,w,b,L,v,I,C,M,_,x,z,S,N,T,O,W,P,$,j,D,A,E])};export default o;export{e as __pageData}; diff --git a/assets/dep_lint.md.13a9d801.js b/assets/dep_lint.md.13a9d801.js new file mode 100644 index 00000000..b9182194 --- /dev/null +++ b/assets/dep_lint.md.13a9d801.js @@ -0,0 +1 @@ +import{o as s,c as n,a as t}from"./app.7e863e47.js";const e='{"title":"Lint","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"ESLint","slug":"eslint"},{"level":3,"title":"手动校验代码","slug":"手动校验代码"},{"level":3,"title":"配置项","slug":"配置项"},{"level":2,"title":"CommitLint","slug":"commitlint"},{"level":3,"title":"配置","slug":"配置"},{"level":3,"title":"Git 提交规范","slug":"git-提交规范"},{"level":3,"title":"如何关闭","slug":"如何关闭"},{"level":3,"title":"示例","slug":"示例"},{"level":2,"title":"Stylelint","slug":"stylelint"},{"level":3,"title":"配置","slug":"配置-1"},{"level":3,"title":"编辑器配合","slug":"编辑器配合"},{"level":2,"title":"Prettier","slug":"prettier"},{"level":3,"title":"配置","slug":"配置-2"},{"level":3,"title":"编辑器配合","slug":"编辑器配合-1"},{"level":2,"title":"Git Hook","slug":"git-hook"},{"level":3,"title":"husky","slug":"husky"},{"level":3,"title":"如何跳过某一个检查","slug":"如何跳过某一个检查"},{"level":3,"title":"lint-staged","slug":"lint-staged"}],"relativePath":"dep/lint.md","lastUpdated":1718353615651}',a={},i=t('

Lint

介绍

使用 lint 的好处

具备基本工程素养的同学都会注重编码规范,而代码风格检查(Code Linting,简称 Lint)是保障代码规范一致性的重要手段。

遵循相应的代码规范有以下好处

  • 较少 bug 错误率
  • 高效的开发效率
  • 更高的可读性

项目内集成了以下几种代码校验方式

  1. eslint 用于校验代码格式规范
  2. commitlint 用于校验 git 提交信息规范
  3. stylelint 用于校验 css/less 规范
  4. prettier 代码格式化

注意

lint 不是必须的,但是很有必要,一个项目做大了以后或者参与人员过多后,就会出现各种风格迥异的代码,对后续的维护造成了一定的麻烦。

ESLint

ESLint 是一个代码规范和错误检查工具,可以根据自己的团队设置符合自己团队的规范

手动校验代码

# 执行下面代码.能修复的会自动修复,不能修复的需要手动修改\npnpm run lint:eslint\n

配置项

项目的 eslint 配置位于根目录下 .eslintrc.js 内,可以根据团队自行修改代码规范

CommitLint

在一个团队中,每个人的 git 的 commit 信息都不一样,五花八门,没有一个机制很难保证规范化,如何才能规范化呢?可能你想到的是 git 的 hook 机制,去写 shell 脚本去实现。这当然可以,其实 JavaScript 有一个很好的工具可以实现这个模板,它就是 commitlint(用于校验 git 提交信息规范)。

配置

commit-lint 的配置位于项目根目录下 commitlint.config.js

Git 提交规范

  • feat 新功能
  • fix 修补 bug
  • docs 文档
  • style 格式、样式(不影响代码运行的变动)
  • refactor 重构(即不是新增功能,也不是修改 BUG 的代码)
  • perf 优化相关,比如提升性能、体验
  • test 添加测试
  • build 编译相关的修改,对项目构建或者依赖的改动
  • ci 持续集成修改
  • chore 构建过程或辅助工具的变动
  • revert 回滚到上一个版本
  • workflow 工作流改进
  • mod 不确定分类的修改
  • wip 开发中
  • types 类型

如何关闭

.husky/commit-msg 内注释以下代码即可

# npx --no-install commitlint --edit "$1"\n

示例

\ngit commit -m 'feat: add new component'\n\n

Stylelint

stylelint 用于校验项目内部 css 的风格,加上编辑器的自动修复,可以很好的统一项目内部 css 风格

配置

stylelint 配置位于根目录下 stylelint.config.js

编辑器配合

如果您使用的是 vscode 编辑器的话,只需要安装下面插件,即可在保存的时候自动格式化文件内部 css 样式

插件

StyleLint

Prettier

prettier 可以用于统一项目代码风格,统一的缩进,单双引号,尾逗号等等风格

配置

prettier 配置文件位于项目根目录下 prettier.config.js

编辑器配合

如果您使用的是 vscode 编辑器的话,只需要安装下面插件,即可在保存的时候自动格式化文件内部 js 格式

插件

Prettier

Git Hook

git hook 一般结合各种 lint,在 git 提交代码的时候进行代码风格校验,如果校验没通过,则不会进行提交。需要开发者自行修改后再次进行提交

husky

有一个问题就是校验会校验全部代码,但是我们只想校验我们自己提交的代码,这个时候就可以使用 husky。

最有效的解决方案就是将 Lint 校验放到本地,常见做法是使用 husky 或者 pre-commit 在本地提交之前先做一次 Lint 校验。

项目在 .husky 内部定义了相应的 hooks

如何跳过某一个检查

# 加上 --no-verify即可跳过git hook校验(--no-verify 简写为 -n)\ngit commit -m "xxx" --no-verify\n

lint-staged

用于自动修复提交文件风格问题

lint-staged 配置位于项目 .husky 目录下 lintstagedrc.js

module.exports = {\n  // 对指定格式文件 在提交的时候执行相应的修复命令\n  '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],\n  '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'],\n  'package.json': ['prettier --write'],\n  '*.vue': ['eslint --fix', 'stylelint --fix', 'prettier --write', 'git add .'],\n  '*.{scss,less,styl,css,html}': ['stylelint --fix', 'prettier --write', 'git add .'],\n  '*.md': ['prettier --write'],\n};\n
',51);a.render=function(t,e,a,l,o,p){return s(),n("div",null,[i])};export default a;export{e as __pageData}; diff --git a/assets/dep_lint.md.13a9d801.lean.js b/assets/dep_lint.md.13a9d801.lean.js new file mode 100644 index 00000000..81935251 --- /dev/null +++ b/assets/dep_lint.md.13a9d801.lean.js @@ -0,0 +1 @@ +import{o as s,c as n,a as t}from"./app.7e863e47.js";const e='{"title":"Lint","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"ESLint","slug":"eslint"},{"level":3,"title":"手动校验代码","slug":"手动校验代码"},{"level":3,"title":"配置项","slug":"配置项"},{"level":2,"title":"CommitLint","slug":"commitlint"},{"level":3,"title":"配置","slug":"配置"},{"level":3,"title":"Git 提交规范","slug":"git-提交规范"},{"level":3,"title":"如何关闭","slug":"如何关闭"},{"level":3,"title":"示例","slug":"示例"},{"level":2,"title":"Stylelint","slug":"stylelint"},{"level":3,"title":"配置","slug":"配置-1"},{"level":3,"title":"编辑器配合","slug":"编辑器配合"},{"level":2,"title":"Prettier","slug":"prettier"},{"level":3,"title":"配置","slug":"配置-2"},{"level":3,"title":"编辑器配合","slug":"编辑器配合-1"},{"level":2,"title":"Git Hook","slug":"git-hook"},{"level":3,"title":"husky","slug":"husky"},{"level":3,"title":"如何跳过某一个检查","slug":"如何跳过某一个检查"},{"level":3,"title":"lint-staged","slug":"lint-staged"}],"relativePath":"dep/lint.md","lastUpdated":1718353615651}',a={},i=t('',51);a.render=function(t,e,a,l,o,p){return s(),n("div",null,[i])};export default a;export{e as __pageData}; diff --git a/assets/donate_donate.md.1d9ebc1a.js b/assets/donate_donate.md.1d9ebc1a.js new file mode 100644 index 00000000..105b7eb7 --- /dev/null +++ b/assets/donate_donate.md.1d9ebc1a.js @@ -0,0 +1 @@ +import{o as a,c as t,b as e,d as r}from"./app.7e863e47.js";const n='{"title":"捐赠","description":"","frontmatter":{},"relativePath":"donate/donate.md","lastUpdated":1718353615651}',d={},i=e("h1",{id:"捐赠"},[e("a",{class:"header-anchor",href:"#捐赠","aria-hidden":"true"},"#"),r(" 捐赠")],-1),o=e("p",null,"如果你觉得这个项目有帮助,欢迎赞助以示支持~",-1),s=e("img",{src:"https://github.com/kailong321200875/my-image/raw/master/pay.jpg"},null,-1);d.render=function(e,r,n,d,l,p){return a(),t("div",null,[i,o,s])};export default d;export{n as __pageData}; diff --git a/assets/donate_donate.md.1d9ebc1a.lean.js b/assets/donate_donate.md.1d9ebc1a.lean.js new file mode 100644 index 00000000..105b7eb7 --- /dev/null +++ b/assets/donate_donate.md.1d9ebc1a.lean.js @@ -0,0 +1 @@ +import{o as a,c as t,b as e,d as r}from"./app.7e863e47.js";const n='{"title":"捐赠","description":"","frontmatter":{},"relativePath":"donate/donate.md","lastUpdated":1718353615651}',d={},i=e("h1",{id:"捐赠"},[e("a",{class:"header-anchor",href:"#捐赠","aria-hidden":"true"},"#"),r(" 捐赠")],-1),o=e("p",null,"如果你觉得这个项目有帮助,欢迎赞助以示支持~",-1),s=e("img",{src:"https://github.com/kailong321200875/my-image/raw/master/pay.jpg"},null,-1);d.render=function(e,r,n,d,l,p){return a(),t("div",null,[i,o,s])};export default d;export{n as __pageData}; diff --git a/assets/group_group.md.02eacf4b.js b/assets/group_group.md.02eacf4b.js new file mode 100644 index 00000000..7997e33e --- /dev/null +++ b/assets/group_group.md.02eacf4b.js @@ -0,0 +1 @@ +import{o as e,c as a,a as r}from"./app.7e863e47.js";const t='{"title":"技术交流群","description":"","frontmatter":{},"headers":[{"level":2,"title":"群二维码","slug":"群二维码"},{"level":2,"title":"我的二维码","slug":"我的二维码"}],"relativePath":"group/group.md","lastUpdated":1718353615651}',i={},h=r('

技术交流群

如果你想进入技术交流群讨论,请扫码入群或者添加我为好友邀请入群

群二维码

我的二维码

',6);i.render=function(r,t,i,s,d,l){return e(),a("div",null,[h])};export default i;export{t as __pageData}; diff --git a/assets/group_group.md.02eacf4b.lean.js b/assets/group_group.md.02eacf4b.lean.js new file mode 100644 index 00000000..5338091c --- /dev/null +++ b/assets/group_group.md.02eacf4b.lean.js @@ -0,0 +1 @@ +import{o as e,c as a,a as r}from"./app.7e863e47.js";const t='{"title":"技术交流群","description":"","frontmatter":{},"headers":[{"level":2,"title":"群二维码","slug":"群二维码"},{"level":2,"title":"我的二维码","slug":"我的二维码"}],"relativePath":"group/group.md","lastUpdated":1718353615651}',i={},h=r('',6);i.render=function(r,t,i,s,d,l){return e(),a("div",null,[h])};export default i;export{t as __pageData}; diff --git a/assets/guide_auth.md.3188bb51.js b/assets/guide_auth.md.3188bb51.js new file mode 100644 index 00000000..0e61a7b2 --- /dev/null +++ b/assets/guide_auth.md.3188bb51.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"权限","description":"","frontmatter":{},"headers":[{"level":2,"title":"前端控制权限","slug":"前端控制权限"},{"level":2,"title":"后台动态获取","slug":"后台动态获取"},{"level":2,"title":"实现","slug":"实现"},{"level":3,"title":"前端控制实现","slug":"前端控制实现"},{"level":3,"title":"后台动态获取","slug":"后台动态获取-1"},{"level":3,"title":"公用部分修改","slug":"公用部分修改"},{"level":2,"title":"静态路由(无权限)","slug":"静态路由(无权限)"}],"relativePath":"guide/auth.md","lastUpdated":1718353615651}',p={},o=a('

权限

项目中集成了 2 种权限处理方式:

  1. 第一种是由前端来控制菜单,即服务端只返回有权限的 keys,由前端自行去匹配
  2. 第二种是通过服务端返回的路由数据结构来动态生成路由表

目前项目中提供了测试的帐号:

admin/admin

前端控制权限

实现原理: 在前端固定写死路由的权限,指定路由有哪些权限可以查看。只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内。在登陆后或者其他方式获取对应的路由 keys 后,遍历路由表去匹配 keys,过滤生成可以访问的路由表,再通过 router.addRoutes 添加到路由实例,实现权限的过滤。

缺点: 权限相对不自由,因为路由表的控制在前端,不管是要排序还是修改,都需要前端去修改,服务端只提供有权限的路由 keys

后台动态获取

实现原理: 是通过接口动态生成路由表,且遵循一定的数据结构返回。前端根据需要处理该数据为可识别的结构,再通过 router.addRoutes 添加到路由实例,实现权限的动态生成。

优点: 所有的菜单控制都是通过服务端的接口返回,前端只负责渲染,后期维护成本降低,优先推荐此方式。

实现

  1. src/store/modules/permission.tsgenerateRoutes() 进行更改。

接收的 type 参数,目前只是针对于本项目的模拟情况,如果不需要或者不适用,可自行改动。

generateRoutes(\n  type: 'server' | 'frontEnd' | 'static',\n  routers?: AppCustomRouteRecordRaw[] | string[]\n): Promise<unknown> {\n  return new Promise<void>((resolve) => {\n    let routerMap: AppRouteRecordRaw[] = []\n    if (type === 'server') {\n      // 模拟后端过滤菜单\n      routerMap = generateRoutesByServer(routers as AppCustomRouteRecordRaw[])\n    } else if (type === 'frontEnd') {\n      // 模拟前端过滤菜单\n      routerMap = generateRoutesByFrontEnd(cloneDeep(asyncRouterMap), routers as string[])\n    } else {\n      // 直接读取静态路由表\n      routerMap = cloneDeep(asyncRouterMap)\n    }\n    // 动态路由,404一定要放到最后面\n    this.addRouters = routerMap.concat([\n      {\n        path: '/:path(.*)*',\n        redirect: '/404',\n        name: '404Page',\n        meta: {\n          hidden: true,\n          breadcrumb: false\n        }\n      }\n    ])\n    // 渲染菜单的所有路由\n    this.routers = cloneDeep(constantRouterMap).concat(routerMap)\n    resolve()\n  })\n}\n

前端控制实现

  1. src/utils/routerHelper.tsgenerateRoutesByFrontEnd () 进行更改。目前本项目的前端权限控制,是根据 path 是否相同来进行过滤演示的,如果不符合需求,需要手动更改以下判断逻辑。
// 前端控制路由生成\nexport const generateRoutesByFrontEnd  = (\n  routes: AppRouteRecordRaw[],\n  keys: string[],\n  basePath = '/'\n): AppRouteRecordRaw[] => {\n  const res: AppRouteRecordRaw[] = [];\n\n  for (const route of routes) {\n    const meta = route.meta as RouteMeta;\n    // skip some route\n    if (meta.hidden && !meta.showMainRoute) {\n      continue;\n    }\n\n    let data: Nullable<AppRouteRecordRaw> = null;\n\n    let onlyOneChild: Nullable<string> = null;\n    if (route.children && route.children.length === 1 && !meta.alwaysShow) {\n      onlyOneChild = (\n        isUrl(route.children[0].path)\n          ? route.children[0].path\n          : pathResolve(pathResolve(basePath, route.path), route.children[0].path)\n      ) as string;\n    }\n\n    // 开发者可以根据实际情况进行扩展\n    for (const item of keys) {\n      // 通过路径去匹配\n      if (isUrl(item) && (onlyOneChild === item || route.path === item)) {\n        data = Object.assign({}, route);\n      } else {\n        const routePath = pathResolve(basePath, onlyOneChild || route.path);\n        if (routePath === item || meta.followRoute === item) {\n          data = Object.assign({}, route);\n        }\n      }\n    }\n\n    // recursive child routes\n    if (route.children && data) {\n      data.children = generateRoutesByFrontEnd (route.children, keys, pathResolve(basePath, data.path));\n    }\n    if (data) {\n      res.push(data as AppRouteRecordRaw);\n    }\n  }\n  return res;\n};\n

后台动态获取

  1. src/utils/routerHelper.tsgenerateRoutesByServer () 进行更改。
// 后端控制路由生成\nexport const generateRoutesByServer  = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {\n  const res: AppRouteRecordRaw[] = [];\n\n  for (const route of routes) {\n    const data: AppRouteRecordRaw = {\n      path: route.path,\n      name: route.name,\n      redirect: route.redirect,\n      meta: route.meta,\n    };\n    if (route.component) {\n      const comModule =\n        modules[`../${route.component}.vue`] || modules[`../${route.component}.tsx`];\n      const component = route.component as string;\n      if (!comModule && !component.includes('#')) {\n        console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`);\n      } else {\n        // 动态加载路由文件,可根据实际情况进行自定义逻辑\n        data.component =\n          component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule;\n      }\n    }\n    // recursive child routes\n    if (route.children) {\n      data.children = generateRoutesByServer (route.children);\n    }\n    res.push(data as AppRouteRecordRaw);\n  }\n  return res;\n};\n

公用部分修改

  1. src/views/Login/components/LoginForm.vuegetRole() 进行更改。

需要开发者自行根据需求进行代码变更。

// 获取角色信息\nconst getRole = async () => {\n  const formData = await getFormData<UserType>()\n  const params = {\n    roleName: formData.username\n  }\n  const res =\n    appStore.getDynamicRouter && appStore.getServerDynamicRouter\n      ? await getAdminRoleApi(params)\n      : await getTestRoleApi(params)\n  if (res) {\n    const routers = res.data || []\n    setStorage('roleRouters', routers)\n    appStore.getDynamicRouter && appStore.getServerDynamicRouter\n      ? await permissionStore.generateRoutes('server', routers).catch(() => {})\n      : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})\n\n    permissionStore.getAddRouters.forEach((route) => {\n      addRoute(route as RouteRecordRaw) // 动态添加可访问路由表\n    })\n    permissionStore.setIsAddRouters(true)\n    push({ path: redirect.value || permissionStore.addRouters[0].path })\n  }\n};\n
  1. src/permission.ts,以下这种情况,是考虑到手动刷新,所以需要获取到缓存中的动态菜单重新渲染。
// 开发者可根据实际情况进行修改\nconst roleRouters = getStorage('roleRouters') || []\n\n// 是否使用动态路由\nif (appStore.getDynamicRouter) {\n  appStore.serverDynamicRouter\n    ? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])\n    : await permissionStore.generateRoutes('frontEnd', roleRouters as string[])\n  } else {\n  await permissionStore.generateRoutes('static')\n}\n\npermissionStore.getAddRouters.forEach((route) => {\n  router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表\n})\nconst redirectPath = from.query.redirect || to.path\nconst redirect = decodeURIComponent(redirectPath as string)\nconst nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }\npermissionStore.setIsAddRouters(true)\nnext(nextData)\n

静态路由(无权限)

有时候,我们并不需要动态路由,那么可以在 src/config/app.ts 中把 dynamicRouter 设置为 false,这样我们取得都是项目中的静态路由表了。

内部逻辑已经处理了静态路由的部分,所以可以无需关心其他。

',30);p.render=function(a,t,p,e,c,u){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/guide_auth.md.3188bb51.lean.js b/assets/guide_auth.md.3188bb51.lean.js new file mode 100644 index 00000000..407909f3 --- /dev/null +++ b/assets/guide_auth.md.3188bb51.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"权限","description":"","frontmatter":{},"headers":[{"level":2,"title":"前端控制权限","slug":"前端控制权限"},{"level":2,"title":"后台动态获取","slug":"后台动态获取"},{"level":2,"title":"实现","slug":"实现"},{"level":3,"title":"前端控制实现","slug":"前端控制实现"},{"level":3,"title":"后台动态获取","slug":"后台动态获取-1"},{"level":3,"title":"公用部分修改","slug":"公用部分修改"},{"level":2,"title":"静态路由(无权限)","slug":"静态路由(无权限)"}],"relativePath":"guide/auth.md","lastUpdated":1718353615651}',p={},o=a('',30);p.render=function(a,t,p,e,c,u){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/guide_component.md.e230104c.js b/assets/guide_component.md.e230104c.js new file mode 100644 index 00000000..923cdb65 --- /dev/null +++ b/assets/guide_component.md.e230104c.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"组件注册","description":"","frontmatter":{},"headers":[{"level":2,"title":"按需引入","slug":"按需引入"},{"level":3,"title":"tsx 文件注册","slug":"tsx-文件注册"},{"level":2,"title":"全局注册","slug":"全局注册"}],"relativePath":"guide/component.md","lastUpdated":1718353615651}',p={},e=a('

组件注册

按需引入

项目目前的组件注册机制是按需注册,是在需要用到的页面才引入。

<script setup lang="ts">\nimport { ElBacktop } from 'element-plus'\nimport { useDesign } from '@/hooks/web/useDesign'\n\nconst { getPrefixCls, variables } = useDesign()\n\nconst prefixCls = getPrefixCls('backtop')\n</script>\n\n<template>\n  <ElBacktop\n    :class="`${prefixCls}-backtop`"\n    :target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"\n  />\n</template>\n\n

tsx 文件注册

tsx 文件内不能使用全局注册组件,需要手动引入组件使用。

全局注册

如果觉得按需引入太麻烦,可以进行全局注册,在src/components/index.ts,添加需要注册的组件。

目前只有 Icon 组件进行了全局注册。

import type { App } from 'vue'\nimport { Icon } from './Icon'\n\nexport const setupGlobCom = (app: App<Element>): void => {\n  app.component('Icon', Icon)\n}\n\n

如果 element-plus 的组件需要全局注册,在 src/plugins/elementPlus/index.ts 添加需要注册的组件。

目前 element-plus 中只有 ElLoadingElScrollbar 进行了全局注册。

import type { App } from 'vue'\n\n// 需要全局引入一些组件,如ElScrollbar,不然一些下拉项样式有问题\nimport { ElLoading, ElScrollbar } from 'element-plus'\n\nconst plugins = [ElLoading]\n\nconst components = [ElScrollbar]\n\nexport const setupElementPlus = (app: App) => {\n  plugins.forEach((plugin) => {\n    app.use(plugin)\n  })\n\n  components.forEach((component) => {\n    app.component(component.name, component)\n  })\n}\n\n
',13);p.render=function(a,t,p,o,c,l){return n(),s("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/guide_component.md.e230104c.lean.js b/assets/guide_component.md.e230104c.lean.js new file mode 100644 index 00000000..52908064 --- /dev/null +++ b/assets/guide_component.md.e230104c.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"组件注册","description":"","frontmatter":{},"headers":[{"level":2,"title":"按需引入","slug":"按需引入"},{"level":3,"title":"tsx 文件注册","slug":"tsx-文件注册"},{"level":2,"title":"全局注册","slug":"全局注册"}],"relativePath":"guide/component.md","lastUpdated":1718353615651}',p={},e=a('',13);p.render=function(a,t,p,o,c,l){return n(),s("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/guide_deploy.md.9764347c.js b/assets/guide_deploy.md.9764347c.js new file mode 100644 index 00000000..02c865ed --- /dev/null +++ b/assets/guide_deploy.md.9764347c.js @@ -0,0 +1 @@ +import{o as e,c as s,a}from"./app.7e863e47.js";const n='{"title":"构建&部署","description":"","frontmatter":{},"headers":[{"level":2,"title":"构建","slug":"构建"},{"level":3,"title":"预览","slug":"预览"},{"level":2,"title":"部署","slug":"部署"},{"level":3,"title":"发布","slug":"发布"}],"relativePath":"guide/deploy.md","lastUpdated":1718353615651}',t={},p=a('

构建&部署

前言

由于是展示项目,所以打包后相对较大,如果项目中没有用到的插件,可以删除对应的文件或者路由,不引用即可,没有引用就不会打包。

构建

项目开发完成之后,执行以下命令进行构建

  • 开发环境 pnpm run build:dev ===> dist-dev
  • 测试环境 pnpm run build:test ===> dist-test
  • 生产环境 pnpm run build:pro ===> dist-pro

构建打包成功之后,会在根目录生成 dist-* 文件夹,里面就是构建打包好的文件。

预览

发布之前可以在本地进行预览

不能直接打开构建后的 html 文件

使用项目自定的命令进行预览(推荐)

# 先打包在进行预览\n\n# 预览开发环境\npnpm run serve:dev\n\n# 预览测试环境\npnpm run serve:test\n\n# 预览生产环境\npnpm run serve:pro\n

部署

注意

项目默认是在生产环境开启 Mock,这样做非常不好,只是为了演示环境有数据,不建议在生产环境使用 Mock,而应该使用真实的后台接口。

发布

简单的部署只需要将最终生成的静态文件,dist-* 文件夹的静态文件发布到你的 cdn 或者静态服务器即可。

部署时可能会发现资源路径不对,只需要修改对应的.env.xxx文件即可。

# 根据自己路径来配置更改\nVITE_BASE_PATH = /dist-dev/\n
',17);t.render=function(a,n,t,l,r,d){return e(),s("div",null,[p])};export default t;export{n as __pageData}; diff --git a/assets/guide_deploy.md.9764347c.lean.js b/assets/guide_deploy.md.9764347c.lean.js new file mode 100644 index 00000000..d2dcd8c2 --- /dev/null +++ b/assets/guide_deploy.md.9764347c.lean.js @@ -0,0 +1 @@ +import{o as e,c as s,a}from"./app.7e863e47.js";const n='{"title":"构建&部署","description":"","frontmatter":{},"headers":[{"level":2,"title":"构建","slug":"构建"},{"level":3,"title":"预览","slug":"预览"},{"level":2,"title":"部署","slug":"部署"},{"level":3,"title":"发布","slug":"发布"}],"relativePath":"guide/deploy.md","lastUpdated":1718353615651}',t={},p=a('',17);t.render=function(a,n,t,l,r,d){return e(),s("div",null,[p])};export default t;export{n as __pageData}; diff --git a/assets/guide_design.md.fa8e8e7e.js b/assets/guide_design.md.fa8e8e7e.js new file mode 100644 index 00000000..0b2a924f --- /dev/null +++ b/assets/guide_design.md.fa8e8e7e.js @@ -0,0 +1 @@ +import{o as s,c as a,a as e}from"./app.7e863e47.js";const n='{"title":"样式","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"unocss","slug":"unocss"}],"relativePath":"guide/design.md","lastUpdated":1718353615651}',t={},p=e('

样式

介绍

主要介绍如何在项目中使用和规划样式文件。

默认使用 less 作为预处理语言,建议在使用前或者遇到疑问时学习一下 Less 的相关特性。

项目中使用的通用样式,都存放于 src/style/ 下面。

.\n├── index.less # 入口\n├── theme.less # 主题相关\n├── var.css  # css变量\n└── variables.module.less # less变量\n\n

全局注入

variables.module.less 这个文件会被全局注入到所有文件,所以在页面内可以直接使用变量而不需要手动引入。

var.css 则是注入到根元素,所以在每个地方也都能用到。

unocss

项目中使用了 unocss,具体参见文件使用说明。

可能没有用到人会觉得用起来很不习惯,但就个人而言,用起来还是挺香的。减少了很多不必要的麻烦

语法如下:

<div class="relative w-full h-full px-4"></div>\n
',12);t.render=function(e,n,t,l,o,c){return s(),a("div",null,[p])};export default t;export{n as __pageData}; diff --git a/assets/guide_design.md.fa8e8e7e.lean.js b/assets/guide_design.md.fa8e8e7e.lean.js new file mode 100644 index 00000000..49ca2733 --- /dev/null +++ b/assets/guide_design.md.fa8e8e7e.lean.js @@ -0,0 +1 @@ +import{o as s,c as a,a as e}from"./app.7e863e47.js";const n='{"title":"样式","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"unocss","slug":"unocss"}],"relativePath":"guide/design.md","lastUpdated":1718353615651}',t={},p=e('',12);t.render=function(e,n,t,l,o,c){return s(),a("div",null,[p])};export default t;export{n as __pageData}; diff --git a/assets/guide_fqa.md.8e3be823.js b/assets/guide_fqa.md.8e3be823.js new file mode 100644 index 00000000..383c0ecb --- /dev/null +++ b/assets/guide_fqa.md.8e3be823.js @@ -0,0 +1 @@ +import{o as e,c as a,a as l}from"./app.7e863e47.js";const o='{"title":"前言","description":"","frontmatter":{},"headers":[{"level":2,"title":"关于修改了 store 的默认值,无法应用问题","slug":"关于修改了-store-的默认值,无法应用问题"},{"level":2,"title":"控制台路由警告问题","slug":"控制台路由警告问题"},{"level":2,"title":"本地启动首次加载慢","slug":"本地启动首次加载慢"},{"level":2,"title":"路由切换页面刷新的问题","slug":"路由切换页面刷新的问题"},{"level":2,"title":"依赖安装问题","slug":"依赖安装问题"},{"level":2,"title":"打包文件过大","slug":"打包文件过大"},{"level":2,"title":"部署上线运行启动慢","slug":"部署上线运行启动慢"},{"level":2,"title":"菜单定制化","slug":"菜单定制化"},{"level":2,"title":"组件使用问题","slug":"组件使用问题"},{"level":2,"title":"编辑器代码报错","slug":"编辑器代码报错"},{"level":2,"title":"添加路由之后,页面无法展示","slug":"添加路由之后,页面无法展示"},{"level":2,"title":"添加新的 vue 文件后,编辑器类型报错","slug":"添加新的-vue-文件后,编辑器类型报错"},{"level":2,"title":"如何启用非在线图标","slug":"如何启用非在线图标"}],"relativePath":"guide/fqa.md","lastUpdated":1718353615651}',r={},d=l('

前言

提示

列举了一些常见的问题。有问题可以先来这里寻找,看是否有相关解答,没有的话可以上 issue 中提问或者搜索

  • 在进行开发时,先着重看下项目指南,把大体文档都过一遍,虽然文档写的可能不能满足所有人的需要,但好歹是有,毕竟项目框架上手有难度。
  • 一些非框架内的问题,可以上百度或者谷歌上搜索。
  • 实在无法解决的,可以上交流群里面提问,相互讨论,毕竟群里面的大佬还是挺多的。
  • 如果文档有落后或者不明确的,可以提 issue ,慢慢完善,毕竟文档的积累完善并不是靠一个人能完成的。

关于修改了 store 的默认值,无法应用问题

因为项目中有的 Store 默认开始了持久化,所以不管你修没修改默认值,都会优先默认取缓存中的值,所以如果修改完默认值之后,还请手动清除下浏览器的 localStorage ,默认值就会生效了。

控制台路由警告问题

本地运行之后,会出现路由警告

[Vue Router warn]: No match found for location with path "/authorization/menu"\n

这个无需关心,是vue-router的问题,项目打包上线后是不会有次警告,所以该问题可以忽略。

本地启动首次加载慢

请自行去百度下 vite 快是怎么个快法,本地运行启动,都是按需加载,一次性加载了几十个资源,当然会比较慢,有了缓存之后非首次就会实现秒开了。

目前项目中已经对于启动时间进行了优化,本地默认加载了全部的 element-plus 的样式文件,会多多少少减少请求资源数量。

启动快慢还是得根据当前文件引用的资源数量来决定。

路由切换页面刷新的问题

这是因为你在该路由中使用了第三方模块,这个模块是没有预加载的,所以需要重新去加载这个模块,然后就会出现 page reload,极大的影响了开发体验,所以可以在 vite.config.ts 中去配置预加载列表:optimizeDeps.include,这样在服务启动的使用,会先把这些模块给预先加载打包。

依赖安装问题

  • 如果依赖安装不了或者启动报错可以先尝试 删除 pnpm-locknode_modules,然后重新运行 pnpm i
  • 可以尝试配置国内镜像安装
  • 请确保项目路径没有特殊字符如:中文、韩文、日文以及空格
  • 请确保 node.js 版本大于等于 18
  • 请确保包管理器使用的是 pnpm

打包文件过大

由于完整版引入了许多第三方模块,所以打包体积会比较大,可以自行删除不需要的第三方模块,或者使用精简版(mini分支)来进行开发。

合理的进行拆包,目前项目中对一些比较大的第三方模块进行了拆包处理。

部署上线运行启动慢

  • 请检查打包体积是否合理
  • 请确保网络正常
  • 可以开启cdn缓存
  • 可以开启http2
  • 开启gzip

菜单定制化

菜单是根据路由配置来生成,请先看下已有的路由配置是否可以满足你的需要,如果不满足,可以自行去定制化。可以查看路由相关文档

组件使用问题

在使用组件的时候,遇到问题,可以先看下对应的在线例子,看是否有对应的代码,基本上覆盖了95%的使用方式,或者查看对应的组件文档。

编辑器代码报错

项目中大部分使用了 tsx ,所以原先 template 的一些代码规范就不适用了,如 v-if 得使用 {判断条件 ? 成立 : 不成立} 来进行显示隐藏,可以查阅下相关文档。

并且请确保如果要使用 tsx 语法, script 是否声明了 lang="tsx"

添加路由之后,页面无法展示

如果是在项目中直接添加静态路由,需要确保 appStore 中的 dynamicRouterserverDynamicRouterfalse,并且手动清除下浏览器的 localStorage

添加新的 vue 文件后,编辑器类型报错

这是 Volar 插件的问题,一般重启下编辑器即可生效。

如何启用非在线图标

设置 VITE_USE_ONLINE_ICON=false ,可能在有的版本设置之后会无效,是因为有BUG,可以复制最新版本的 uno.config.tsIcon.vue 的最新代码。

',35);r.render=function(l,o,r,t,i,c){return e(),a("div",null,[d])};export default r;export{o as __pageData}; diff --git a/assets/guide_fqa.md.8e3be823.lean.js b/assets/guide_fqa.md.8e3be823.lean.js new file mode 100644 index 00000000..31b86a83 --- /dev/null +++ b/assets/guide_fqa.md.8e3be823.lean.js @@ -0,0 +1 @@ +import{o as e,c as a,a as l}from"./app.7e863e47.js";const o='{"title":"前言","description":"","frontmatter":{},"headers":[{"level":2,"title":"关于修改了 store 的默认值,无法应用问题","slug":"关于修改了-store-的默认值,无法应用问题"},{"level":2,"title":"控制台路由警告问题","slug":"控制台路由警告问题"},{"level":2,"title":"本地启动首次加载慢","slug":"本地启动首次加载慢"},{"level":2,"title":"路由切换页面刷新的问题","slug":"路由切换页面刷新的问题"},{"level":2,"title":"依赖安装问题","slug":"依赖安装问题"},{"level":2,"title":"打包文件过大","slug":"打包文件过大"},{"level":2,"title":"部署上线运行启动慢","slug":"部署上线运行启动慢"},{"level":2,"title":"菜单定制化","slug":"菜单定制化"},{"level":2,"title":"组件使用问题","slug":"组件使用问题"},{"level":2,"title":"编辑器代码报错","slug":"编辑器代码报错"},{"level":2,"title":"添加路由之后,页面无法展示","slug":"添加路由之后,页面无法展示"},{"level":2,"title":"添加新的 vue 文件后,编辑器类型报错","slug":"添加新的-vue-文件后,编辑器类型报错"},{"level":2,"title":"如何启用非在线图标","slug":"如何启用非在线图标"}],"relativePath":"guide/fqa.md","lastUpdated":1718353615651}',r={},d=l('',35);r.render=function(l,o,r,t,i,c){return e(),a("div",null,[d])};export default r;export{o as __pageData}; diff --git a/assets/guide_index.md.eef690d2.js b/assets/guide_index.md.eef690d2.js new file mode 100644 index 00000000..b4c42886 --- /dev/null +++ b/assets/guide_index.md.eef690d2.js @@ -0,0 +1 @@ +import{o as n,c as e,a as s}from"./app.7e863e47.js";const a='{"title":"开始","description":"","frontmatter":{},"headers":[{"level":2,"title":"环境准备","slug":"环境准备"},{"level":2,"title":"工具配置","slug":"工具配置"},{"level":2,"title":"代码获取","slug":"代码获取"},{"level":3,"title":"从 GitHub 获取代码","slug":"从-github-获取代码"},{"level":3,"title":"从 Gitee 获取代码","slug":"从-gitee-获取代码"},{"level":2,"title":"安装","slug":"安装"},{"level":3,"title":"安装 Node.js","slug":"安装-node-js"},{"level":3,"title":"安装依赖","slug":"安装依赖"},{"level":2,"title":"npm script","slug":"npm-script"}],"relativePath":"guide/index.md","lastUpdated":1718353615651}',t={},o=s('

开始

本文将快速的帮助你从头运行并启动项目。

环境准备

本地环境需要安装 PnpmNode.jsGit

为什么使用 Pnpm,而不是用其他包管理器,大家可以搜索一下,这里就不做过多的阐述了。

注意

  • Node.js 版本要求14.x以上,这里推荐 16.x 及以上。

工具配置

如果你使用的 IDE 是vscode的话,可以安装以下工具来提高开发效率及代码格式化:

代码获取

注意

注意存放代码的目录及所有父级目录不能存在中文、韩文、日文以及空格,否则安装依赖后启动会出错。

从 GitHub 获取代码

# clone 代码\ngit clone https://github.com/kailong321200875/vue-element-plus-admin.git\n\n

从 Gitee 获取代码

git clone https://gitee.com/kailong110120130/vue-element-plus-admin.git\n

代码同步

不用担心 Gitee 代码库和 Github 代码库不同步,每次版本提交发布,都会及时同步到 Gitee 上。

安装

安装 Node.js

如果您电脑未安装Node.js,请安装它,推荐 18.x 及以上

验证

# 验证 npm 是否安装成功\nnpm -v\n\n# 验证 node 是否安装成功\nnode -v\n

如果你需要同时存在多个 node 版本,可以使用 Nvm 或者其他工具进行 Node.js 进行版本管理。

安装依赖

Pnpm 安装

推荐使用 Pnpm进行依赖安装(若其他包管理器安装不了需要自行处理)。

如果未安装 Pnpm,可以用下面命令来进行全局安装

# 全局安装 pnpm\nnpm i -g pnpm\n\n# 验证\npnpm -v\n

安装依赖

在项目根目录下,打开命令窗口执行,耐心等待安装完成即可

# 安装依赖\npnpm i\n

安装依赖时 husky 安装失败

请查看你的源码是否从 Github 或者 Gitee 直接下载的,直接下载是没有 .git 文件夹的,而 husky 需要依赖 git 才能安装。此时需使用 git init 初始化项目,再尝试重新安装即可。

当依赖安装完成后,执行以下命令即可启动项目:

pnpm run dev\n

npm script

"scripts": {\n  # 安装依赖\n  "i": "pnpm install",\n  # 本地开发环境运行\n  "dev": "vite --mode base",\n  # typeScript 检测\n  "ts:check": "vue-tsc --noEmit",\n  # 打包生产环境\n  "build:pro": "vite build --mode pro",\n  # 打包开发环境\n  "build:dev": "npm run ts:check && vite build --mode dev",\n  # 打包测试环境\n  "build:test": "npm run ts:check && vite build --mode test",\n  # 本地预览 已打包的生产环境项目包\n  "serve:pro": "vite preview --mode pro",\n  # 本地预览 已打包的开发环境项目包\n  "serve:dev": "vite preview --mode dev",\n  # 本地预览 已打包的测试环境项目包\n  "serve:test": "vite preview --mode test",\n  # 检测可更新依赖\n  "npm:check": "npx npm-check-updates",\n  # 删除 node_modules\n  "clean": "npx rimraf node_modules",\n  # 删除 缓存\n  "clean:cache": "npx rimraf node_modules/.cache",\n  # eslint 检测\n  "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",\n  # eslint 格式化\n  "lint:format": "prettier --write --loglevel warn \\"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\\"",\n  # stylelint 格式化\n  "lint:style": "stylelint --fix \\"**/*.{vue,less,postcss,css,scss}\\" --cache --cache-location node_modules/.cache/stylelint/",\n  "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",\n  "lint:pretty": "pretty-quick --staged",\n  "postinstall": "husky install",\n  # 快速生成统一规范的模块\n  "p": "plop"\n},\n
',35);t.render=function(s,a,t,l,p,r){return n(),e("div",null,[o])};export default t;export{a as __pageData}; diff --git a/assets/guide_index.md.eef690d2.lean.js b/assets/guide_index.md.eef690d2.lean.js new file mode 100644 index 00000000..576b2db5 --- /dev/null +++ b/assets/guide_index.md.eef690d2.lean.js @@ -0,0 +1 @@ +import{o as n,c as e,a as s}from"./app.7e863e47.js";const a='{"title":"开始","description":"","frontmatter":{},"headers":[{"level":2,"title":"环境准备","slug":"环境准备"},{"level":2,"title":"工具配置","slug":"工具配置"},{"level":2,"title":"代码获取","slug":"代码获取"},{"level":3,"title":"从 GitHub 获取代码","slug":"从-github-获取代码"},{"level":3,"title":"从 Gitee 获取代码","slug":"从-gitee-获取代码"},{"level":2,"title":"安装","slug":"安装"},{"level":3,"title":"安装 Node.js","slug":"安装-node-js"},{"level":3,"title":"安装依赖","slug":"安装依赖"},{"level":2,"title":"npm script","slug":"npm-script"}],"relativePath":"guide/index.md","lastUpdated":1718353615651}',t={},o=s('',35);t.render=function(s,a,t,l,p,r){return n(),e("div",null,[o])};export default t;export{a as __pageData}; diff --git a/assets/guide_introduction.md.ccf49502.js b/assets/guide_introduction.md.ccf49502.js new file mode 100644 index 00000000..098b49ee --- /dev/null +++ b/assets/guide_introduction.md.ccf49502.js @@ -0,0 +1 @@ +import{o as e,c as r,e as t,a as n}from"./app.7e863e47.js";const a='{"title":"介绍","description":"","frontmatter":{},"headers":[{"level":2,"title":"简介","slug":"简介"},{"level":2,"title":"需要掌握的基础知识","slug":"需要掌握的基础知识"},{"level":2,"title":"目录结构","slug":"目录结构"},{"level":2,"title":"浏览器支持","slug":"浏览器支持"},{"level":2,"title":"IDE推荐","slug":"ide推荐"}],"relativePath":"guide/introduction.md","lastUpdated":1718353615651}',o={},s=n('

介绍

注意

  • 如果需要 v1 版本的文档,请到 v1 分支进行 clone ,目前文档仅支持 v2 版本

简介

vue-element-plus-admin 是一个基于 element-plus 免费开源的中后台模版。使用了最新的 Vue3ViteTypescript等主流技术开发,开箱即用的中后台前端解决方案,可以用来作为项目的启动模版,也可用于学习参考。并且时刻关注着最新技术动向,尽可能的第一时间更新。

vue-element-plus-admin 的定位是后台集成方案,因为集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。

',5),i=n('

如需要基础模版,请切换到 mini 分支,mini 只简单集成了一些如:布局、动态菜单等常用布局功能,更适合开发者进行二次开发。

需要掌握的基础知识

本项目需要一定前端基础知识,请确保掌握 Vue 的基础知识,以便能处理一些常见的问题。

为了能快速上手本项目,请先大致浏览一遍文档及在线示例。

建议在开发前先学一下以下内容,提前了解和学习这些知识,会对项目理解非常有帮助:

目录结构

.\n├── .github # github workflows 相关\n├── .husky # husky 配置\n├── .vscode # vscode 配置\n├── mock # 自定义 mock 数据及配置\n├── public # 静态资源\n├── src # 项目代码\n│   ├── api # api接口管理\n|   |── axios # axios配置\n│   ├── assets # 静态资源\n│   ├── components # 公用组件\n│   ├── constants # 存放常量\n│   ├── hooks # 常用hooks\n│   ├── layout # 布局组件\n│   ├── locales # 语言文件\n│   ├── plugins # 外部插件\n│   ├── router # 路由配置\n│   ├── store # 状态管理\n│   ├── styles # 全局样式\n│   ├── utils # 全局工具类\n│   ├── views # 路由页面\n│   ├── App.vue # 入口vue文件\n│   ├── main.ts # 主入口文件\n│   └── permission.ts # 路由拦截\n├── types # 全局类型\n├── .env.base # 本地开发环境 环境变量配置\n├── .env.dev # 打包到开发环境 环境变量配置\n├── .env.gitee # 针对 gitee 的环境变量 可忽略\n├── .env.pro # 打包到生产环境 环境变量配置\n├── .env.test # 打包到测试环境 环境变量配置\n├── .eslintignore # eslint 跳过检测配置\n├── .eslintrc.js # eslint 配置\n├── .gitignore # git 跳过配置\n├── .prettierignore # prettier 跳过检测配置\n├── .stylelintignore # stylelint 跳过检测配置\n├── .versionrc 自动生成版本号及更新记录配置\n├── CHANGELOG.md # 更新记录\n├── commitlint.config.js # git commit 提交规范配置\n├── index.html # 入口页面\n├── package.json\n├── .postcssrc.js # postcss 配置\n├── prettier.config.js # prettier 配置\n├── README.md # 英文 README\n├── README.zh-CN.md # 中文 README\n├── stylelint.config.js # stylelint 配置\n├── tsconfig.json # typescript 配置\n├── vite.config.ts # vite 配置\n└── uno.config.ts # unocss 配置\n

浏览器支持

本地开发推荐使用Chrome 最新版浏览器。

由于 Vue 3 不再支持 IE11,本项目也不支持 IE。

IEIE EdgeEdgeFirefoxFirefoxChromeChromeSafariSafari
not supportlast 2 versionslast 2 versionslast 2 versionslast 2 versions

IDE推荐

',14);o.render=function(n,a,o,l,p,h){return e(),r("div",null,[s,t(" ::: warning 注意\n\n- 由于精力有限,[template](https://github.com/kailong321200875/vue-element-plus-admin/tree/template) 分支将不再维护,如果需要精简版,请自行删除不需要的文件及代码。\n\n::: "),i])};export default o;export{a as __pageData}; diff --git a/assets/guide_introduction.md.ccf49502.lean.js b/assets/guide_introduction.md.ccf49502.lean.js new file mode 100644 index 00000000..e8c62273 --- /dev/null +++ b/assets/guide_introduction.md.ccf49502.lean.js @@ -0,0 +1 @@ +import{o as e,c as r,e as t,a as n}from"./app.7e863e47.js";const a='{"title":"介绍","description":"","frontmatter":{},"headers":[{"level":2,"title":"简介","slug":"简介"},{"level":2,"title":"需要掌握的基础知识","slug":"需要掌握的基础知识"},{"level":2,"title":"目录结构","slug":"目录结构"},{"level":2,"title":"浏览器支持","slug":"浏览器支持"},{"level":2,"title":"IDE推荐","slug":"ide推荐"}],"relativePath":"guide/introduction.md","lastUpdated":1718353615651}',o={},s=n('',5),i=n('',14);o.render=function(n,a,o,l,p,h){return e(),r("div",null,[s,t(" ::: warning 注意\n\n- 由于精力有限,[template](https://github.com/kailong321200875/vue-element-plus-admin/tree/template) 分支将不再维护,如果需要精简版,请自行删除不需要的文件及代码。\n\n::: "),i])};export default o;export{a as __pageData}; diff --git a/assets/guide_mock.md.23e13f26.js b/assets/guide_mock.md.23e13f26.js new file mode 100644 index 00000000..4281be53 --- /dev/null +++ b/assets/guide_mock.md.23e13f26.js @@ -0,0 +1 @@ +import{o as s,c as n,a}from"./app.7e863e47.js";const t='{"title":"数据mock&联调","description":"","frontmatter":{},"headers":[{"level":2,"title":"开发环境","slug":"开发环境"},{"level":3,"title":"跨域设置","slug":"跨域设置"},{"level":2,"title":"接口请求","slug":"接口请求"},{"level":2,"title":"axios 配置","slug":"axios-配置"},{"level":3,"title":"全局 axios 配置说明","slug":"全局-axios-配置说明"},{"level":2,"title":"Mock 服务","slug":"mock-服务"},{"level":3,"title":"本地 Mock","slug":"本地-mock"},{"level":3,"title":"线上 mock","slug":"线上-mock"}],"relativePath":"guide/mock.md","lastUpdated":1718353615651}',p={},o=a('

数据mock&联调

开发环境

如果前端应用和后端接口服务器没有运行在同一个主机上,你需要在开发环境下将接口请求代理到接口服务器。

如果是同一个主机,可以直接请求具体的接口地址。

跨域设置

vite.config.ts 配置文件中,提供了 server 的 proxy 功能,用于代理 API 请求。

server: {\n  proxy: {\n    "/api":{\n      target: 'http://localhost:3000',\n      changeOrigin: true,\n      ws: true,\n      rewrite: (path) => path.replace(new RegExp(`^/api`), ''),\n    }\n  },\n},\n

配置接口前缀,可以在对应的 env 文件中,修改 VITE_API_BASE_PATH 的值

注意

该配置只能作用于 本地开发环境。

从浏览器控制台的 Network 看,请求是 http://localhost:3000/api/xxx,这是因为 proxy 配置不会改变本地请求的 url。

接口请求

在本项目中,所有的接口数据都是使用 Mock 模拟

接口统一存放于 src/api/ 下面管理

以获取列表接口为例:

src/api/ 内新建模块文件,其中参数与返回值最好定义一下类型,方便校验。虽然麻烦,但是后续维护字段很方便。

提示

类型定义文件可以抽取出去统一管理,具体参考项目

import request from '@/axios'\nimport type { TableData } from './types'\n\nexport const getTableListApi = (params: any) => {\n  return request.get({ url: '/example/list', params })\n}\n\nexport const getTreeTableListApi = (params: any) => {\n  return request.get({ url: '/example/treeList', params })\n}\n\nexport const saveTableApi = (data: Partial<TableData>): Promise<IResponse> => {\n  return request.post({ url: '/example/save', data })\n}\n\nexport const getTableDetApi = (id: string): Promise<IResponse<TableData>> => {\n  return request.get({ url: '/example/detail', params: { id } })\n}\n\nexport const delTableListApi = (ids: string[] | number[]): Promise<IResponse> => {\n  return request.post({ url: '/example/delete', data: { ids } })\n}\n\n

axios 配置

axios 请求封装存放于 src/axios 中。

全局 axios 配置说明

axios 全局配置放在 src/constants 中。

注意

更改之后,将影响所有的请求。

/**\n * 请求成功状态码\n */\nexport const SUCCESS_CODE = 0\n\n/**\n * 请求contentType\n */\nexport const CONTENT_TYPE = 'application/json'\n\n/**\n * 请求超时时间\n */\nexport const REQUEST_TIMEOUT = 60000\n

Mock 服务

Mock 数据是前端开发过程中必不可少的一环,是分离前后端开发的关键链路。通过预先跟服务器端约定好的接口,模拟请求数据甚至逻辑,能够让前端开发独立自主,不会被服务端的开发进程所阻塞。

本项目使用 vite-mock-plugin 来进行 mock 数据处理。项目内 mock 服务分本地和线上

本地 Mock

本地 mock 采用 Node.js 中间件进行参数拦截(不采用 mock.js 的原因是本地开发看不到请求参数和响应结果)。

如何新增 mock 接口

如果你想添加 mock 数据,只要在根目录下找到 mock 文件,添加对应的接口,对其进行拦截和模拟数据。

在 mock 文件夹内新建文件

TIP

文件新增后会自动更新,不需要手动重启,可以在代码控制台查看日志信息 mock 文件夹内会自动注册

TIP

mock 的值可以直接使用 mock.js 的语法。

接口有了,如何去掉 mock

可以在对应的 env 文件中设置 VITE_USE_MOCKfalse ,如果想要更彻底一点,可以在vite.config.ts中删除 viteMockServe 对应的代码。

线上 mock

由于该项目是一个展示类项目,线上也是用 mock 数据,所以在打包后同时也集成了 mock。通常项目线上一般为正式接口。

项目线上 mock 采用的是 mock.js 进行 mock 数据模拟。

',37);p.render=function(a,t,p,e,c,l){return s(),n("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/guide_mock.md.23e13f26.lean.js b/assets/guide_mock.md.23e13f26.lean.js new file mode 100644 index 00000000..13452730 --- /dev/null +++ b/assets/guide_mock.md.23e13f26.lean.js @@ -0,0 +1 @@ +import{o as s,c as n,a}from"./app.7e863e47.js";const t='{"title":"数据mock&联调","description":"","frontmatter":{},"headers":[{"level":2,"title":"开发环境","slug":"开发环境"},{"level":3,"title":"跨域设置","slug":"跨域设置"},{"level":2,"title":"接口请求","slug":"接口请求"},{"level":2,"title":"axios 配置","slug":"axios-配置"},{"level":3,"title":"全局 axios 配置说明","slug":"全局-axios-配置说明"},{"level":2,"title":"Mock 服务","slug":"mock-服务"},{"level":3,"title":"本地 Mock","slug":"本地-mock"},{"level":3,"title":"线上 mock","slug":"线上-mock"}],"relativePath":"guide/mock.md","lastUpdated":1718353615651}',p={},o=a('',37);p.render=function(a,t,p,e,c,l){return s(),n("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/guide_router.md.cc1fa32a.js b/assets/guide_router.md.cc1fa32a.js new file mode 100644 index 00000000..5c7ea5aa --- /dev/null +++ b/assets/guide_router.md.cc1fa32a.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"路由","description":"","frontmatter":{},"headers":[{"level":2,"title":"配置","slug":"配置"},{"level":3,"title":"如何添加新配置","slug":"如何添加新配置"},{"level":3,"title":"多级路由","slug":"多级路由"},{"level":3,"title":"外链","slug":"外链"},{"level":2,"title":"图标","slug":"图标"},{"level":2,"title":"多标签页","slug":"多标签页"},{"level":3,"title":"如何开启页面缓存","slug":"如何开启页面缓存"},{"level":3,"title":"如何让某个页面不缓存","slug":"如何让某个页面不缓存"},{"level":2,"title":"默认跳转地址","slug":"默认跳转地址"}],"relativePath":"guide/router.md","lastUpdated":1718353615651}',p={},o=a('

路由

项目路由配置存放于 src/router/index.ts 中。

为了方便阅读和查找,目前项目中并没有去对路由进行拆分,而是统一写在了一起,如果需要拆分,可自行更改。

因为路由是生成菜单关键,所以本项目中对路由提供了以下配置,方便开发者进行定制。

配置

/**\n* redirect: noredirect        当设置 noredirect 的时候该路由在面包屑导航中不可被点击\n* name:'router-name'          设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题\n* meta : {\n    hidden: true              当设置 true 的时候该路由不会再侧边栏出现 如404,login等页面(默认 false)\n\n    alwaysShow: true          当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式,\n                              只有一个时,会将那个子路由当做根路由显示在侧边栏,\n                              若你想不管路由下面的 children 声明的个数都显示你的根路由,\n                              你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,\n                              一直显示根路由(默认 false)\n\n    title: 'title'            设置该路由在侧边栏和面包屑中展示的名字\n\n    icon: 'svg-name'          设置该路由的图标\n\n    noCache: true             如果设置为true,则不会被 <keep-alive> 缓存(默认 false)\n\n    breadcrumb: false         如果设置为false,则不会在breadcrumb面包屑中显示(默认 true)\n\n    affix: true               如果设置为true,则会一直固定在tag项中(默认 false)\n\n    noTagsView: true          如果设置为true,则不会出现在tag中(默认 false)\n\n    activeMenu: '/dashboard'  显示高亮的路由路径\n\n    canTo: true               设置为true即使hidden为true,也依然可以进行路由跳转(默认 false)\n\n    permission: ['edit','add', 'delete']    设置该路由的权限\n  }\n**/\n

如何添加新配置

如果本项目中的路由配置项,满足不了你当前的开发工作,可以自行添加新的属性。

注意

所有的路由项配置,都必须放在 meta 中。

types/router.d.ts 中添加对应的类型,之后就可以在路由中添加你需要的配置项了。

declare module 'vue-router' {\n  interface RouteMeta extends Record<string | number | symbol, unknown> {\n    hidden?: boolean\n    alwaysShow?: boolean\n    title?: string\n    icon?: string\n    noCache?: boolean\n    breadcrumb?: boolean\n    affix?: boolean\n    activeMenu?: string\n    noTagsView?: boolean\n    canTo?: boolean\n    permission?: string[]\n\n    // 添加新的配置类型\n    ...\n  }\n}\n\n

多级路由

注意事项

  • 整个项目所有路由 name 不能重复
  • 所有的多级路由最终都会转成二级路由,所以不能内嵌子路由
  • 除了 layout 对应的 path 前面需要加 /,其余子路由都不要以/开头

示例

{\n  path: '/level',\n  component: Layout,\n  redirect: '/level/menu1/menu1-1/menu1-1-1',\n  name: 'Level',\n  meta: {\n    title: t('router.level'),\n    icon: 'carbon:skill-level-advanced'\n  },\n  children: [\n    {\n      path: 'menu1',\n      name: 'Menu1',\n      component: getParentLayout(),\n      redirect: '/level/menu1/menu1-1/menu1-1-1',\n      meta: {\n        title: t('router.menu1')\n      },\n      children: [\n        {\n          path: 'menu1-1',\n          name: 'Menu11',\n          component: getParentLayout(),\n          redirect: '/level/menu1/menu1-1/menu1-1-1',\n          meta: {\n            title: t('router.menu11'),\n            alwaysShow: true\n          },\n          children: [\n            {\n              path: 'menu1-1-1',\n              name: 'Menu111',\n              component: () => import('@/views/Level/Menu111.vue'),\n              meta: {\n                title: t('router.menu111')\n              }\n            }\n          ]\n        },\n        {\n          path: 'menu1-2',\n          name: 'Menu12',\n          component: () => import('@/views/Level/Menu12.vue'),\n          meta: {\n            title: t('router.menu12')\n          }\n        }\n      ]\n    },\n    {\n      path: 'menu2',\n      name: 'Menu2Demo',\n      component: () => import('@/views/Level/Menu2.vue'),\n      meta: {\n        title: t('router.menu2')\n      }\n    }\n  ]\n}\n\n

外链

只需要将 path 设置为需要跳转的HTTP 地址即可。

{\n  path: '/external-link',\n  component: Layout,\n  meta: {\n    name: 'ExternalLink'\n  },\n  children: [\n    {\n      path: 'https://github.com/kailong321200875/vue-element-plus-admin-doc',\n      meta: { name: 'Link', title: '文档' }\n    }\n  ]\n}\n

图标

这里的 icon 配置,会同步到 菜单(icon 的值可以查看此处)。

多标签页

标签页使用的是 keep-aliverouter-view 实现,实现切换 tab 后还能保存切换之前的状态。

如何开启页面缓存

开启缓存有 2 个条件

  1. 路由设置 name,且不能重复
  2. 路由对应的组件加上 name,与路由设置的 name 保持一致
{\n  path: 'menu2',\n  name: 'Menu2',\n  component: () => import('@/views/Level/Menu2.vue'),\n  meta: {\n    title: t('router.menu2')\n  }\n}\n\n// /@/views/Level/Menu2.vue\n<script setup lang="ts">\ndefineOptions({\n  name: 'Menu2'\n})\n</script>\n\n

注意

keep-alive 生效的前提是:需要将路由的 name 属性及对应的页面的 name 设置成一样。因为:

include - 字符串或正则表达式,只有名称匹配的组件会被缓存

如何让某个页面不缓存

可以将 noCache 配置成 true 即可关闭缓存或者组件不添加 name 属性。

{\n  path: 'workplace',\n  component: () => import('@/views/Dashboard/Workplace.vue'),\n  name: 'Workplace',\n  meta: {\n    title: t('router.workplace'),\n    noCache: true\n  }\n}\n

默认跳转地址

目前项目中,登录进来,默认是进入到当前第一个能找到的路由页面。

后续会考虑弄成一个配置项出来。

',33);p.render=function(a,t,p,e,c,l){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/guide_router.md.cc1fa32a.lean.js b/assets/guide_router.md.cc1fa32a.lean.js new file mode 100644 index 00000000..b43331bf --- /dev/null +++ b/assets/guide_router.md.cc1fa32a.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"路由","description":"","frontmatter":{},"headers":[{"level":2,"title":"配置","slug":"配置"},{"level":3,"title":"如何添加新配置","slug":"如何添加新配置"},{"level":3,"title":"多级路由","slug":"多级路由"},{"level":3,"title":"外链","slug":"外链"},{"level":2,"title":"图标","slug":"图标"},{"level":2,"title":"多标签页","slug":"多标签页"},{"level":3,"title":"如何开启页面缓存","slug":"如何开启页面缓存"},{"level":3,"title":"如何让某个页面不缓存","slug":"如何让某个页面不缓存"},{"level":2,"title":"默认跳转地址","slug":"默认跳转地址"}],"relativePath":"guide/router.md","lastUpdated":1718353615651}',p={},o=a('',33);p.render=function(a,t,p,e,c,l){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/guide_settings.md.f6473247.js b/assets/guide_settings.md.f6473247.js new file mode 100644 index 00000000..c742c9e9 --- /dev/null +++ b/assets/guide_settings.md.f6473247.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const e='{"title":"项目配置项","description":"","frontmatter":{},"headers":[{"level":2,"title":"环境变量配置","slug":"环境变量配置"},{"level":3,"title":"配置项说明","slug":"配置项说明"},{"level":3,"title":".env.base","slug":"env-base"},{"level":3,"title":".env.dev","slug":"env-dev"},{"level":3,"title":".env.test","slug":"env-test"},{"level":3,"title":".env.pro","slug":"env-pro"},{"level":2,"title":"项目及主题配置","slug":"项目及主题配置"},{"level":3,"title":"配置文件路径","slug":"配置文件路径"},{"level":3,"title":"说明","slug":"说明"},{"level":3,"title":"如何添加新属性","slug":"如何添加新属性"},{"level":2,"title":"多语言配置","slug":"多语言配置"},{"level":2,"title":"样式配置","slug":"样式配置"},{"level":3,"title":"css 前缀设置","slug":"css-前缀设置"},{"level":3,"title":"前缀使用","slug":"前缀使用"}],"relativePath":"guide/settings.md","lastUpdated":1718353615651}',p={},t=a('

项目配置项

本文将介绍一些常用的项目配置,方便开发者可以根据需求进行定制化改造。

环境变量配置

项目的环境变量配置位于项目根目录下的,这里主要配置四个个环境变量,分别为:

在开发调试的时候,会读取 .env.base 里面的数据。其他环境亦是如此,根据打包命令的不同,来读取不同的环境变量。

也许你会疑惑,为什么会有多个环境变量?

生产环境 为例,当我们执行 pnpm run build:pro 时,输出的包是用于线上环境的,所以代码都应该是压缩,我们需要删除掉代码中的 console.logdegubber,保证打包后代码的整洁度和不可见性。而其他环境,所以应该保留 console.logdegubber 用于调试,这样才能快速定位到问题所在。

所以环境变量的作用就是为了,在不同环境下有不同的表现。

提示

  • 只有以 VITE_ 开头的变量会被嵌入到项目中,你可以项目代码中这样访问它们:
console.log(import.meta.env.VITE_APP_TITLE)\n

配置项说明

.env.base

本地开发环境适用

# 环境\nNODE_ENV = development\n\n# 接口前缀\nVITE_API_BASEPATH = base\n\n# 打包路径\nVITE_BASE_PATH = /\n\n# 标题\nVITE_APP_TITLE = ElementAdmin\n

.env.dev

开发环境适用

# 环境\nNODE_ENV = production\n\n# 接口前缀\nVITE_API_BASEPATH = dev\n\n# 打包路径\nVITE_BASE_PATH = /dist-dev/\n\n# 是否删除debugger\nVITE_DROP_DEBUGGER = false\n\n# 是否删除console.log\nVITE_DROP_CONSOLE = false\n\n# 是否sourcemap\nVITE_SOURCEMAP = true\n\n# 输出路径\nVITE_OUT_DIR = dist-dev\n\n# 标题\nVITE_APP_TITLE = ElementAdmin\n\n

.env.test

测试环境适用

# 环境\nNODE_ENV = production\n\n# 接口前缀\nVITE_API_BASEPATH = test\n\n# 打包路径\nVITE_BASE_PATH = /dist-test/\n\n# 是否删除debugger\nVITE_DROP_DEBUGGER = false\n\n# 是否删除console.log\nVITE_DROP_CONSOLE = false\n\n# 是否sourcemap\nVITE_SOURCEMAP = true\n\n# 输出路径\nVITE_OUT_DIR = dist-test\n\n

.env.pro

生产环境适用

# 环境\nNODE_ENV = production\n\n# 接口前缀\nVITE_API_BASEPATH = pro\n\n# 打包路径\nVITE_BASE_PATH = /\n\n# 是否删除debugger\nVITE_DROP_DEBUGGER = true\n\n# 是否删除console.log\nVITE_DROP_CONSOLE = true\n\n# 是否sourcemap\nVITE_SOURCEMAP = false\n\n# 输出路径\nVITE_OUT_DIR = dist-pro\n\n# 标题\nVITE_APP_TITLE = ElementAdmin\n\n

项目及主题配置

提示

项目配置文件用于配置项目内展示的内容、布局、主题色等效果。

配置文件路径

src/store/modules/app.ts

说明

修改完之后,会添加到全局的状态管理中,方便其他地方使用。

export const appModules: AppState = {\n  sizeMap: ['default', 'large', 'small'],\n  mobile: false, // 是否是移动端\n  title: import.meta.env.VITE_APP_TITLE as string, // 标题\n  pageLoading: false, // 路由跳转loading\n\n  breadcrumb: true, // 面包屑\n  breadcrumbIcon: true, // 面包屑图标\n  collapse: false, // 折叠菜单\n  hamburger: true, // 折叠图标\n  screenfull: true, // 全屏图标\n  size: true, // 尺寸图标\n  locale: true, // 多语言图标\n  tagsView: true, // 标签页\n  logo: true, // logo\n  fixedHeader: true, // 固定toolheader\n  footer: true, // 显示页脚\n  greyMode: false, // 是否开始灰色模式,用于特殊悼念日\n\n  layout: wsCache.get('layout') || 'classic', // layout布局\n  isDark: wsCache.get('isDark') || false, // 是否是暗黑模式\n  currentSize: wsCache.get('default') || 'default', // 组件尺寸\n  theme: wsCache.get('theme') || {\n    // 主题色\n    elColorPrimary: '#409eff',\n    // 左侧菜单边框颜色\n    leftMenuBorderColor: 'inherit',\n    // 左侧菜单背景颜色\n    leftMenuBgColor: '#001529',\n    // 左侧菜单浅色背景颜色\n    leftMenuBgLightColor: '#0f2438',\n    // 左侧菜单选中背景颜色\n    leftMenuBgActiveColor: 'var(--el-color-primary)',\n    // 左侧菜单收起选中背景颜色\n    leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',\n    // 左侧菜单字体颜色\n    leftMenuTextColor: '#bfcbd9',\n    // 左侧菜单选中字体颜色\n    leftMenuTextActiveColor: '#fff',\n    // logo字体颜色\n    logoTitleTextColor: '#fff',\n    // logo边框颜色\n    logoBorderColor: 'inherit',\n    // 头部背景颜色\n    topHeaderBgColor: '#fff',\n    // 头部字体颜色\n    topHeaderTextColor: 'inherit',\n    // 头部悬停颜色\n    topHeaderHoverColor: '#f6f6f6',\n    // 头部边框颜色\n    topToolBorderColor: '#eee'\n  }\n}\n

如何添加新属性

如果想要添加新的全局配置属性,需要在 src/store/modules/app.tsAppState 添加对应的类型,并在 appModules 对象中,赋予新属性的默认值。

多语言配置

用于配置多语言信息

src/store/modules/locale.ts 内配置

import { useCache } from '@/hooks/web/useCache'\nimport zhCn from 'element-plus/lib/locale/lang/zh-cn'\nimport en from 'element-plus/lib/locale/lang/en'\n\nconst { wsCache } = useCache()\n\nexport const elLocaleMap = {\n  'zh-CN': zhCn,\n  en: en\n}\nexport interface LocaleState {\n  currentLocale: LocaleDropdownType\n  localeMap: LocaleDropdownType[]\n}\n\nexport const localeModules: LocaleState = {\n  currentLocale: {\n    lang: wsCache.get('lang') || 'zh-CN',\n    elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN']\n  },\n  // 多语言\n  localeMap: [\n    {\n      lang: 'zh-CN',\n      name: '简体中文'\n    },\n    {\n      lang: 'en',\n      name: 'English'\n    }\n  ]\n}\n\n

样式配置

css 前缀设置

用于修改项目内组件及 element-plus 组件的 class 前缀。

由于 element-plus 的组件还没有全部采用动态配置前缀,所以目前还是使用 el 前缀。

// 命名空间\n@namespace: v;\n// el命名空间\n@elNamespace: el;\n\n// 导出变量\n:export {\n  namespace: @namespace;\n  elNamespace: @elNamespace;\n}\n\n

前缀使用

在 css 内

<style lang="less" scoped>\n  /* namespace已经全局注入,不需要额外在引入 */\n  @prefix-cls: ~'@{namespace}-app';\n\n  .@{prefix-cls} {\n    width: 100%;\n  }\n</style>\n

在 vue/ts 内

import { useDesign } from '/@/hooks/web/useDesign'\n\nconst { prefixCls } = useDesign('app')\n\n// prefixCls => v-app\n
',47);p.render=function(a,e,p,o,c,l){return n(),s("div",null,[t])};export default p;export{e as __pageData}; diff --git a/assets/guide_settings.md.f6473247.lean.js b/assets/guide_settings.md.f6473247.lean.js new file mode 100644 index 00000000..b6ddac98 --- /dev/null +++ b/assets/guide_settings.md.f6473247.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const e='{"title":"项目配置项","description":"","frontmatter":{},"headers":[{"level":2,"title":"环境变量配置","slug":"环境变量配置"},{"level":3,"title":"配置项说明","slug":"配置项说明"},{"level":3,"title":".env.base","slug":"env-base"},{"level":3,"title":".env.dev","slug":"env-dev"},{"level":3,"title":".env.test","slug":"env-test"},{"level":3,"title":".env.pro","slug":"env-pro"},{"level":2,"title":"项目及主题配置","slug":"项目及主题配置"},{"level":3,"title":"配置文件路径","slug":"配置文件路径"},{"level":3,"title":"说明","slug":"说明"},{"level":3,"title":"如何添加新属性","slug":"如何添加新属性"},{"level":2,"title":"多语言配置","slug":"多语言配置"},{"level":2,"title":"样式配置","slug":"样式配置"},{"level":3,"title":"css 前缀设置","slug":"css-前缀设置"},{"level":3,"title":"前缀使用","slug":"前缀使用"}],"relativePath":"guide/settings.md","lastUpdated":1718353615651}',p={},t=a('',47);p.render=function(a,e,p,o,c,l){return n(),s("div",null,[t])};export default p;export{e as __pageData}; diff --git a/assets/guide_version.md.a6043d58.js b/assets/guide_version.md.a6043d58.js new file mode 100644 index 00000000..a069ed72 --- /dev/null +++ b/assets/guide_version.md.a6043d58.js @@ -0,0 +1 @@ +import{o as e,c as s,a as d}from"./app.7e863e47.js";const a='{"title":"插件","description":"","frontmatter":{},"headers":[{"level":2,"title":"windiCss 替换为 unocss","slug":"windicss-替换为-unocss"},{"level":2,"title":"布局","slug":"布局"},{"level":2,"title":"typescript 类型","slug":"typescript-类型"},{"level":2,"title":"组件","slug":"组件"},{"level":2,"title":"在线例子","slug":"在线例子"},{"level":2,"title":"v1 如何升级到 v2","slug":"v1-如何升级到-v2"}],"relativePath":"guide/version.md","lastUpdated":1718353615651}',c={},i=d('

插件

windiCss 替换为 unocss

由于 WindiCss 不再维护,所以换成了 unocss, 两者在用法上保持了大部分的一致性,但还是有些地方有特别的差异性,对于 v1 版本需要升级到 unocss 话,需要有一定的改造成本。

所以建议 v1 还是继续使用 WindiCss

布局

v2 版本还是保留了四种布局风格,只是在细节上的把控会比 v1 好,主要体现在一些边框重叠的优化上。

typescript 类型

v2 版本升级了 typescript5,在用法上基本上没有区别,只是针对了项目中的一些类型的规范进行了更改,使项目的代码更规范化。

组件

v2 版本最主要的更新,就是组件上的更新

主要体现在了 FormTableSearchDescriptions 的重构上。

在 V1 版本中,以上四个组件在使用上有许多不足的地方,灵活度不够,扩展性不强而被诟病。

所以在 v2 版本中,以上四个组件,schema 全部采用了 tsx 的书写方式,如果定制化比较多的话,tsx 会比 template 更有优势。

同时,以上四个组件支持嵌套绑定,如 Form 的数据绑定,v1 版本只支持一层嵌套,比较局限,在 v2 版本中,支持 xxx.xxx 的绑定方式。

如果用法比较简单的话,也是支持 template ,不过这里还是推荐使用 tsx ,避免之后扩展带来的负担。

在线例子

v2 版本丰富了在线例子,如果 权限管理,后续也会继续持续更新更多的例子来让各位客官可以更快速的了解和使用。

v1 如何升级到 v2

注意

如果 v1 版本已经项目落地,或者已经使用了一段时间,建议还是继续使用 v1 版本,刚开始使用的话,可以直接使用 v2 版本

由于两个版本的不兼容,这里是不推荐进行升级。

',19);c.render=function(d,a,c,r,t,o){return e(),s("div",null,[i])};export default c;export{a as __pageData}; diff --git a/assets/guide_version.md.a6043d58.lean.js b/assets/guide_version.md.a6043d58.lean.js new file mode 100644 index 00000000..90921146 --- /dev/null +++ b/assets/guide_version.md.a6043d58.lean.js @@ -0,0 +1 @@ +import{o as e,c as s,a as d}from"./app.7e863e47.js";const a='{"title":"插件","description":"","frontmatter":{},"headers":[{"level":2,"title":"windiCss 替换为 unocss","slug":"windicss-替换为-unocss"},{"level":2,"title":"布局","slug":"布局"},{"level":2,"title":"typescript 类型","slug":"typescript-类型"},{"level":2,"title":"组件","slug":"组件"},{"level":2,"title":"在线例子","slug":"在线例子"},{"level":2,"title":"v1 如何升级到 v2","slug":"v1-如何升级到-v2"}],"relativePath":"guide/version.md","lastUpdated":1718353615651}',c={},i=d('',19);c.render=function(d,a,c,r,t,o){return e(),s("div",null,[i])};export default c;export{a as __pageData}; diff --git a/assets/hooks_useClipboard.md.40fa8a97.js b/assets/hooks_useClipboard.md.40fa8a97.js new file mode 100644 index 00000000..664ef950 --- /dev/null +++ b/assets/hooks_useClipboard.md.40fa8a97.js @@ -0,0 +1 @@ +import{o as s,c as a,a as n}from"./app.7e863e47.js";const p='{"title":"useClipboard","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"}],"relativePath":"hooks/useClipboard.md","lastUpdated":1718353615651}',t={},e=n('

useClipboard

剪切板

useClipboard 位于 src/hooks/web/useClipboard.ts

用法

<script setup lang="ts">\nimport { useClipboard } from '@/hooks/web/useClipboard'\n\nconst { copy } = useClipboard()\n\ncopy('复制内容')\n</script>\n\n

参数介绍

const { copy, copied, text, isSupported } = useClipboard()\n

copy

copy 复制,参数传入一个需要复制的内容

copied

copied 是否已复制

text

text 复制的内容

isSupported

isSupported 浏览器是否支持复制

',15);t.render=function(n,p,t,o,c,r){return s(),a("div",null,[e])};export default t;export{p as __pageData}; diff --git a/assets/hooks_useClipboard.md.40fa8a97.lean.js b/assets/hooks_useClipboard.md.40fa8a97.lean.js new file mode 100644 index 00000000..abe355c7 --- /dev/null +++ b/assets/hooks_useClipboard.md.40fa8a97.lean.js @@ -0,0 +1 @@ +import{o as s,c as a,a as n}from"./app.7e863e47.js";const p='{"title":"useClipboard","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"}],"relativePath":"hooks/useClipboard.md","lastUpdated":1718353615651}',t={},e=n('',15);t.render=function(n,p,t,o,c,r){return s(),a("div",null,[e])};export default t;export{p as __pageData}; diff --git a/assets/hooks_useCrudSchemas.md.0ffc21b5.js b/assets/hooks_useCrudSchemas.md.0ffc21b5.js new file mode 100644 index 00000000..2c0ba81e --- /dev/null +++ b/assets/hooks_useCrudSchemas.md.0ffc21b5.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"useCrudSchemas","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"},{"level":2,"title":"CrudSchema","slug":"crudschema"}],"relativePath":"hooks/useCrudSchemas.md","lastUpdated":1718353615651}',p={},o=a('

useCrudSchemas

统一生成 SearchFormDescriptionsTable 组件所需要的数据结构。

由于以上四个组件都需要 Sechema 或者 columns 的字段,如果每个组件都写一遍的话,会造成大量重复代码,所以提供 useCrudSchemas 来进行统一的数据生成。

useCrudSchemas 位于 src/hooks/web/useCrudSchemas.ts

用法

TIP

如果不需要某个字段,如 formSchema 不需要 fieldindex 的字段,可以使用 form: { hidden: true } 进行过滤,其他组件同理。

Search 是基于 Form 进行二次封装的,所以 Search 支持的参数 Form 也都支持。

searchform 字段,可以传入 dictName 来获取全局的字典数据,也可以传入 api 来获取接口数据,如果使用 api ,需要主动 return 数据。

如果想看更复杂点的例子,请在线预览

<script setup lang="ts">\nimport { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'\n\nconst crudSchemas = reactive<CrudSchema[]>([\n  {\n    field: 'index',\n    label: t('tableDemo.index'),\n    type: 'index',\n    form: {\n      hidden: true\n    },\n    detail: {\n      hidden: true\n    }\n  },\n  {\n    field: 'title',\n    label: t('tableDemo.title'),\n    search: {\n      show: true\n    },\n    form: {\n      colProps: {\n        span: 24\n      }\n    },\n    detail: {\n      span: 24\n    }\n  },\n  {\n    field: 'author',\n    label: t('tableDemo.author')\n  },\n  {\n    field: 'display_time',\n    label: t('tableDemo.displayTime'),\n    form: {\n      component: 'DatePicker',\n      componentProps: {\n        type: 'datetime',\n        valueFormat: 'YYYY-MM-DD HH:mm:ss'\n      }\n    }\n  },\n  {\n    field: 'importance',\n    label: t('tableDemo.importance'),\n    formatter: (_: Recordable, __: TableColumn, cellValue: number) => {\n      return h(\n        ElTag,\n        {\n          type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'\n        },\n        () =>\n          cellValue === 1\n            ? t('tableDemo.important')\n            : cellValue === 2\n            ? t('tableDemo.good')\n            : t('tableDemo.commonly')\n      )\n    },\n    form: {\n      component: 'Select',\n      componentProps: {\n        options: [\n          {\n            label: '重要',\n            value: 3\n          },\n          {\n            label: '良好',\n            value: 2\n          },\n          {\n            label: '一般',\n            value: 1\n          }\n        ]\n      }\n    }\n  },\n  {\n    field: 'pageviews',\n    label: t('tableDemo.pageviews'),\n    form: {\n      component: 'InputNumber',\n      value: 0\n    }\n  },\n  {\n    field: 'content',\n    label: t('exampleDemo.content'),\n    table: {\n      hidden: true\n    },\n    form: {\n      component: 'Editor',\n      colProps: {\n        span: 24\n      }\n    },\n    detail: {\n      span: 24\n    }\n  },\n  {\n    field: 'action',\n    width: '260px',\n    label: t('tableDemo.action'),\n    form: {\n      hidden: true\n    },\n    detail: {\n      hidden: true\n    }\n  }\n])\n\nconst { allSchemas } = useCrudSchemas(crudSchemas)\n</script>\n\n

参数介绍

const { allSchemas } = useCrudSchemas(crudSchemas)\n

allSchemas

allSchemas 存放着四个组件所需要的数据结果

allSchemas.fromSchema

Form 组件的 Sechema

allSchemas.searchSchema

Search 组件的 Sechema

allSchemas.detailSchema

Descriptions 组件的 Sechema

allSchemas.tableColumns

Table 组件的 columns

CrudSchema

属性说明类型可选值默认值
search用于设置 searchSchemaCrudSearchParams--
table用于设置 tableColumnsCrudTableParams--
form用于设置 fromSchemaCrudFormParams--
detail用于设置 DescriptionsSchemaCrudDescriptionsParams--
children如果是 Table 组件,则可能会有多表头的情况存在CrudSchema[]--
',24);p.render=function(a,t,p,e,c,l){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/hooks_useCrudSchemas.md.0ffc21b5.lean.js b/assets/hooks_useCrudSchemas.md.0ffc21b5.lean.js new file mode 100644 index 00000000..cf81274c --- /dev/null +++ b/assets/hooks_useCrudSchemas.md.0ffc21b5.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"useCrudSchemas","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"},{"level":2,"title":"CrudSchema","slug":"crudschema"}],"relativePath":"hooks/useCrudSchemas.md","lastUpdated":1718353615651}',p={},o=a('',24);p.render=function(a,t,p,e,c,l){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/hooks_useNetwork.md.d83cb84f.js b/assets/hooks_useNetwork.md.d83cb84f.js new file mode 100644 index 00000000..14769e7f --- /dev/null +++ b/assets/hooks_useNetwork.md.d83cb84f.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"useNetwork","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"}],"relativePath":"hooks/useNetwork.md","lastUpdated":1718353615651}',e={},o=a('

useNetwork

监听网络状态

useNetwork 位于 src/hooks/web/useNetwork.ts

用法

<script setup lang="ts">\nimport { useNetwork } from '@/hooks/web/useNetwork'\n\nconst { online } = useNetwork()\n\nconsole.log(online)\n</script>\n\n

参数介绍

const { online } = useNetwork()\n

online

online 网络是否已连接

',9);e.render=function(a,t,e,p,c,l){return n(),s("div",null,[o])};export default e;export{t as __pageData}; diff --git a/assets/hooks_useNetwork.md.d83cb84f.lean.js b/assets/hooks_useNetwork.md.d83cb84f.lean.js new file mode 100644 index 00000000..3d8fd329 --- /dev/null +++ b/assets/hooks_useNetwork.md.d83cb84f.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"useNetwork","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"}],"relativePath":"hooks/useNetwork.md","lastUpdated":1718353615651}',e={},o=a('',9);e.render=function(a,t,e,p,c,l){return n(),s("div",null,[o])};export default e;export{t as __pageData}; diff --git a/assets/hooks_useStorage.md.3a714984.js b/assets/hooks_useStorage.md.3a714984.js new file mode 100644 index 00000000..5f7621ea --- /dev/null +++ b/assets/hooks_useStorage.md.3a714984.js @@ -0,0 +1 @@ +import{o as a,c as s,a as n}from"./app.7e863e47.js";const t='{"title":"useStorage(2.1.0+)","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"}],"relativePath":"hooks/useStorage.md","lastUpdated":1718353615651}',e={},o=n('

useStorage(2.1.0+)

用于操作 localStorage 和 sessionStorage

useStorage 位于 src/hooks/web/useStorage.ts

默认使用 sessionStorage,如需要使用 localStorage ,只需要传入 localStorage 即可,如:useStorage('localStorage')

支持非字符串类型存取值

用法

<script setup lang="ts">\nimport { useStorage } from '@/hooks/web/useStorage'\n\nconst { setStorage, getStorage, removeStorage, clear } = useStorage()\n\nsetStorage('key', { name: 'Jok' })\n\ngetStorage('key')\n\nremoveStorage('key')\n\nclear()\n</script>\n\n

参数介绍

const { setStorage, getStorage, removeStorage, clear } = useStorage('localStorage')\n

setStorage

setStorage 存储数据

getStorage

getStorage 获取某个存储数据

removeStorage

removeStorage 清除某个存储数据

clear

clear 清除所有缓存数据,如果需要排除某些数据,可以传入 excludes 来排除,如:clear(['key']),这样 key 就不会被清除

',17);e.render=function(n,t,e,p,c,r){return a(),s("div",null,[o])};export default e;export{t as __pageData}; diff --git a/assets/hooks_useStorage.md.3a714984.lean.js b/assets/hooks_useStorage.md.3a714984.lean.js new file mode 100644 index 00000000..9279b873 --- /dev/null +++ b/assets/hooks_useStorage.md.3a714984.lean.js @@ -0,0 +1 @@ +import{o as a,c as s,a as n}from"./app.7e863e47.js";const t='{"title":"useStorage(2.1.0+)","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"}],"relativePath":"hooks/useStorage.md","lastUpdated":1718353615651}',e={},o=n('',17);e.render=function(n,t,e,p,c,r){return a(),s("div",null,[o])};export default e;export{t as __pageData}; diff --git a/assets/hooks_useTagsView.md.08bfa096.js b/assets/hooks_useTagsView.md.08bfa096.js new file mode 100644 index 00000000..be6b863a --- /dev/null +++ b/assets/hooks_useTagsView.md.08bfa096.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"useTagsView(2.1.0+)","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"}],"relativePath":"hooks/useTagsView.md","lastUpdated":1718353615651}',p={},o=a('

useTagsView(2.1.0+)

操作标签页

useTagsView 位于 src/hooks/web/useTagsView.ts

用法

<script setup lang="ts">\n<script setup lang="ts">\nimport { ContentWrap } from '@/components/ContentWrap'\nimport { ElButton } from 'element-plus'\nimport { useTagsView } from '@/hooks/web/useTagsView'\nimport { useRouter } from 'vue-router'\n\nconst { push } = useRouter()\n\nconst { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage, setTitle } =\n  useTagsView()\n\nconst closeAllTabs = () => {\n  closeAll(() => {\n    push('/dashboard/analysis')\n  })\n}\n\nconst closeLeftTabs = () => {\n  closeLeft()\n}\n\nconst closeRightTabs = () => {\n  closeRight()\n}\n\nconst closeOtherTabs = () => {\n  closeOther()\n}\n\nconst refresh = () => {\n  refreshPage()\n}\n\nconst closeCurrentTab = () => {\n  closeCurrent(undefined, () => {\n    push('/dashboard/analysis')\n  })\n}\n\nconst setTabTitle = () => {\n  setTitle(new Date().getTime().toString())\n}\n\nconst setAnalysisTitle = () => {\n  setTitle(`分析页-${new Date().getTime().toString()}`, '/dashboard/analysis')\n}\n</script>\n\n<template>\n  <ContentWrap title="useTagsView">\n    <ElButton type="primary" @click="closeAllTabs"> 关闭所有标签页 </ElButton>\n    <ElButton type="primary" @click="closeLeftTabs"> 关闭左侧标签页 </ElButton>\n    <ElButton type="primary" @click="closeRightTabs"> 关闭右侧标签页 </ElButton>\n    <ElButton type="primary" @click="closeOtherTabs"> 关闭其他标签页 </ElButton>\n    <ElButton type="primary" @click="closeCurrentTab"> 关闭当前标签页 </ElButton>\n    <ElButton type="primary" @click="refresh"> 刷新当前标签页 </ElButton>\n    <ElButton type="primary" @click="setTabTitle"> 修改当前标题 </ElButton>\n    <ElButton type="primary" @click="setAnalysisTitle"> 修改分析页标题 </ElButton>\n  </ContentWrap>\n</template>\n\n</script>\n\n

参数介绍

const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage, setTitle } = useTagsView()\n

closeAll

closeAll 用于关闭所有标签页

closeLeft

closeLeft 用于关闭当前左侧标签页

closeRight

closeRight 用于关闭当前右侧标签页

closeOther

closeOther 用于关闭除当前标签页外的所有标签页

closeCurrent

closeCurrent 用于关闭除当前标签页

refreshPage

refreshPage 用于刷新当前标签页

setTitle

setTitle(title: string, path: string) 用于设置某个标签页的标签,接收 标题和一个完整的path路径

',21);p.render=function(a,t,p,e,c,u){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/hooks_useTagsView.md.08bfa096.lean.js b/assets/hooks_useTagsView.md.08bfa096.lean.js new file mode 100644 index 00000000..98379d4c --- /dev/null +++ b/assets/hooks_useTagsView.md.08bfa096.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.7e863e47.js";const t='{"title":"useTagsView(2.1.0+)","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"}],"relativePath":"hooks/useTagsView.md","lastUpdated":1718353615651}',p={},o=a('',21);p.render=function(a,t,p,e,c,u){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/hooks_useWatermark.md.138a8dc3.js b/assets/hooks_useWatermark.md.138a8dc3.js new file mode 100644 index 00000000..3564e05e --- /dev/null +++ b/assets/hooks_useWatermark.md.138a8dc3.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.7e863e47.js";const t='{"title":"useWatermark","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"}],"relativePath":"hooks/useWatermark.md","lastUpdated":1718353615651}',e={},p=s('

useWatermark

为元素设置水印

useWatermark 位于 src/hooks/web/useWatermark.ts

用法

<script setup lang="ts">\nimport { useWatermark } from '@/hooks/web/useWatermark'\nimport { onBeforeUnmount } from 'vue'\n\nconst { setWatermark, clear } = useWatermark()\n\nconst { t } = useI18n()\n\nsetWatermark('ElementPlusAdmin')\n\nonBeforeUnmount(() => {\n  clear()\n})\n</script>\n\n

参数介绍

const { setWatermark, clear } = useWatermark()\n

setWatermark

setWatermark 用于设置水印文案,接收一个 string 类型的参数

clear

clear 用于清除水印

',11);e.render=function(s,t,e,o,c,r){return n(),a("div",null,[p])};export default e;export{t as __pageData}; diff --git a/assets/hooks_useWatermark.md.138a8dc3.lean.js b/assets/hooks_useWatermark.md.138a8dc3.lean.js new file mode 100644 index 00000000..c0ee839d --- /dev/null +++ b/assets/hooks_useWatermark.md.138a8dc3.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.7e863e47.js";const t='{"title":"useWatermark","description":"","frontmatter":{},"headers":[{"level":2,"title":"用法","slug":"用法"},{"level":3,"title":"参数介绍","slug":"参数介绍"}],"relativePath":"hooks/useWatermark.md","lastUpdated":1718353615651}',e={},p=s('',11);e.render=function(s,t,e,o,c,r){return n(),a("div",null,[p])};export default e;export{t as __pageData}; diff --git a/assets/index.md.5bbe8fc9.js b/assets/index.md.5bbe8fc9.js new file mode 100644 index 00000000..bcb1fd17 --- /dev/null +++ b/assets/index.md.5bbe8fc9.js @@ -0,0 +1 @@ +import{o as t,c as e}from"./app.7e863e47.js";const i='{"title":"Home","description":"","frontmatter":{"home":true,"heroImage":"/logo.png","actionText":"快速开始 →","actionLink":"/guide/introduction","altActionText":"在线预览","altActionLink":"https://element-plus-admin.cn/","features":[{"title":"最新技术栈","details":"基于Vue3、Vite、TypeScript等最新技术栈开发"},{"title":"组件封装","details":"对常用功能进行组件化封装,统一维护,满足基础工作需求"},{"title":"丰富的示例","details":"常见的Web端插件示例实现"},{"title":"主题配置","details":"丰富的主题配置及黑暗主题适配"},{"title":"权限管理","details":"完善的前后端权限管理方案"},{"title":"持续更新","details":"持续关注最新的技术方向,保证第一时间更新"}],"footer":"MIT Licensed | Copyright © 2021-present Archer"},"relativePath":"index.md","lastUpdated":1718353615651}',o={};o.render=function(i,o,a,n,r,l){return t(),e("div")};export default o;export{i as __pageData}; diff --git a/assets/index.md.5bbe8fc9.lean.js b/assets/index.md.5bbe8fc9.lean.js new file mode 100644 index 00000000..bcb1fd17 --- /dev/null +++ b/assets/index.md.5bbe8fc9.lean.js @@ -0,0 +1 @@ +import{o as t,c as e}from"./app.7e863e47.js";const i='{"title":"Home","description":"","frontmatter":{"home":true,"heroImage":"/logo.png","actionText":"快速开始 →","actionLink":"/guide/introduction","altActionText":"在线预览","altActionLink":"https://element-plus-admin.cn/","features":[{"title":"最新技术栈","details":"基于Vue3、Vite、TypeScript等最新技术栈开发"},{"title":"组件封装","details":"对常用功能进行组件化封装,统一维护,满足基础工作需求"},{"title":"丰富的示例","details":"常见的Web端插件示例实现"},{"title":"主题配置","details":"丰富的主题配置及黑暗主题适配"},{"title":"权限管理","details":"完善的前后端权限管理方案"},{"title":"持续更新","details":"持续关注最新的技术方向,保证第一时间更新"}],"footer":"MIT Licensed | Copyright © 2021-present Archer"},"relativePath":"index.md","lastUpdated":1718353615651}',o={};o.render=function(i,o,a,n,r,l){return t(),e("div")};export default o;export{i as __pageData}; diff --git a/assets/style.e8023618.css b/assets/style.e8023618.css new file mode 100644 index 00000000..8c330b21 --- /dev/null +++ b/assets/style.e8023618.css @@ -0,0 +1 @@ +:root{--c-white:#ffffff;--c-white-dark:#f8f8f8;--c-black:#000000;--c-divider-light:rgba(60, 60, 67, 0.12);--c-divider-dark:rgba(84, 84, 88, 0.48);--c-text-light-1:#2c3e50;--c-text-light-2:#476582;--c-text-light-3:#90a4b7;--c-brand:#3eaf7c;--c-brand-light:#4abf8a;--font-family-base:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Fira Sans','Droid Sans','Helvetica Neue',sans-serif;--font-family-mono:source-code-pro,Menlo,Monaco,Consolas,'Courier New',monospace;--z-index-navbar:10;--z-index-sidebar:6;--shadow-1:0 1px 2px rgba(0, 0, 0, 0.04),0 1px 2px rgba(0, 0, 0, 0.06);--shadow-2:0 3px 12px rgba(0, 0, 0, 0.07),0 1px 4px rgba(0, 0, 0, 0.07);--shadow-3:0 12px 32px rgba(0, 0, 0, 0.1),0 2px 6px rgba(0, 0, 0, 0.08);--shadow-4:0 14px 44px rgba(0, 0, 0, 0.12),0 3px 9px rgba(0, 0, 0, 0.12);--shadow-5:0 18px 56px rgba(0, 0, 0, 0.16),0 4px 12px rgba(0, 0, 0, 0.16);--header-height:3.6rem;--c-divider:var(--c-divider-light);--c-text:var(--c-text-light-1);--c-text-light:var(--c-text-light-2);--c-text-lighter:var(--c-text-light-3);--c-bg:var(--c-white);--c-bg-accent:var(--c-white-dark);--code-line-height:24px;--code-font-family:var(--font-family-mono);--code-font-size:14px;--code-inline-bg-color:rgba(27, 31, 35, 0.05);--code-bg-color:#282c34}*,::after,::before{box-sizing:border-box}html{line-height:1.4;font-size:16px;-webkit-text-size-adjust:100%}body{margin:0;width:100%;min-width:320px;min-height:100vh;line-height:1.4;font-family:var(--font-family-base);font-size:16px;font-weight:400;color:var(--c-text);background-color:var(--c-bg);direction:ltr;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}main{display:block}h1,h2,h3,h4,h5,h6{margin:0;line-height:1.25}b,h1,h2,h3,h4,h5,h6,strong{font-weight:600}h1:focus .header-anchor,h1:hover .header-anchor,h2:focus .header-anchor,h2:hover .header-anchor,h3:focus .header-anchor,h3:hover .header-anchor,h4:focus .header-anchor,h4:hover .header-anchor,h5:focus .header-anchor,h5:hover .header-anchor,h6:focus .header-anchor,h6:hover .header-anchor{opacity:1}h1{margin-top:1.5rem;font-size:1.9rem}@media screen and (min-width:420px){h1{font-size:2.2rem}}h2{margin-top:2.25rem;margin-bottom:1.25rem;border-bottom:1px solid var(--c-divider);padding-bottom:.3rem;line-height:1.25;font-size:1.65rem}h2+h3{margin-top:1.5rem}h3{margin-top:2rem;font-size:1.35rem}h4{font-size:1.15rem}ol,p,ul{margin:1rem 0;line-height:1.7}[role=button],a,area,button,input,label,select,summary,textarea{touch-action:manipulation}a{text-decoration:none;color:var(--c-brand)}a:hover{text-decoration:underline}a.header-anchor{float:left;margin-top:.125em;margin-left:-.87em;padding-right:.23em;font-size:.85em;opacity:0}a.header-anchor:focus,a.header-anchor:hover{text-decoration:none}figure{margin:0}img{max-width:100%}ol,ul{padding-left:1.25em}li>ol,li>ul{margin:0}table{display:block;border-collapse:collapse;margin:1rem 0;overflow-x:auto}tr{border-top:1px solid #dfe2e5}tr:nth-child(2n){background-color:#f6f8fa}td,th{border:1px solid #dfe2e5;padding:.6em 1em}blockquote{margin:1rem 0;border-left:.2rem solid #dfe2e5;padding:.25rem 0 .25rem 1rem;font-size:1rem;color:#999}blockquote>p{margin:0}form{margin:0}.theme.sidebar-open .sidebar-mask{display:block}.theme.no-navbar>h1,.theme.no-navbar>h2,.theme.no-navbar>h3,.theme.no-navbar>h4,.theme.no-navbar>h5,.theme.no-navbar>h6{margin-top:1.5rem;padding-top:0}.theme.no-navbar aside{top:0}@media screen and (min-width:720px){.theme.no-sidebar aside{display:none}.theme.no-sidebar main{margin-left:0}}.sidebar-mask{position:fixed;z-index:2;display:none;width:100vw;height:100vh}code{margin:0;border-radius:3px;padding:.25rem .5rem;font-family:var(--code-font-family);font-size:.85em;color:var(--c-text-light);background-color:var(--code-inline-bg-color)}code .token.deleted{color:#ec5975}code .token.inserted{color:var(--c-brand)}div[class*=language-]{position:relative;margin:1rem -1.5rem;background-color:var(--code-bg-color);overflow-x:auto}li>div[class*=language-]{border-radius:6px 0 0 6px;margin:1rem -1.5rem 1rem -1.25rem}@media (min-width:420px){div[class*=language-]{margin:1rem 0;border-radius:6px}li>div[class*=language-]{margin:1rem 0 1rem 0;border-radius:6px}}[class*=language-] code,[class*=language-] pre{text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}[class*=language-] pre{position:relative;z-index:1;margin:0;padding:1.25rem 1.5rem;background:0 0;overflow-x:auto}[class*=language-] code{padding:0;line-height:var(--code-line-height);font-size:var(--code-font-size);color:#eee}.highlight-lines{position:absolute;top:0;bottom:0;left:0;padding:1.25rem 0;width:100%;line-height:var(--code-line-height);font-family:var(--code-font-family);font-size:var(--code-font-size);user-select:none;overflow:hidden}.highlight-lines .highlighted{background-color:rgba(0,0,0,.66)}div[class*=language-].line-numbers-mode{padding-left:3.5rem}.line-numbers-wrapper{position:absolute;top:0;bottom:0;left:0;z-index:3;border-right:1px solid rgba(0,0,0,.5);padding:1.25rem 0;width:3.5rem;text-align:center;line-height:var(--code-line-height);font-family:var(--code-font-family);font-size:var(--code-font-size);color:#888}[class*=language-]:before{position:absolute;top:.6em;right:1em;z-index:2;font-size:.8rem;color:#888}[class~=language-html]:before,[class~=language-markup]:before{content:'html'}[class~=language-markdown]:before,[class~=language-md]:before{content:'md'}[class~=language-css]:before{content:'css'}[class~=language-sass]:before{content:'sass'}[class~=language-scss]:before{content:'scss'}[class~=language-less]:before{content:'less'}[class~=language-stylus]:before{content:'styl'}[class~=language-javascript]:before,[class~=language-js]:before{content:'js'}[class~=language-ts]:before,[class~=language-typescript]:before{content:'ts'}[class~=language-json]:before{content:'json'}[class~=language-rb]:before,[class~=language-ruby]:before{content:'rb'}[class~=language-py]:before,[class~=language-python]:before{content:'py'}[class~=language-bash]:before,[class~=language-sh]:before{content:'sh'}[class~=language-php]:before{content:'php'}[class~=language-go]:before{content:'go'}[class~=language-rust]:before{content:'rust'}[class~=language-java]:before{content:'java'}[class~=language-c]:before{content:'c'}[class~=language-yaml]:before{content:'yaml'}[class~=language-dockerfile]:before{content:'dockerfile'}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}.custom-block.danger,.custom-block.tip,.custom-block.warning{margin:1rem 0;border-left:.5rem solid;padding:.1rem 1.5rem;overflow-x:auto}.custom-block.tip{background-color:#f3f5f7;border-color:var(--c-brand)}.custom-block.warning{border-color:#e7c000;color:#6b5900;background-color:rgba(255,229,100,.3)}.custom-block.warning .custom-block-title{color:#b29400}.custom-block.warning a{color:var(--c-text)}.custom-block.danger{border-color:#c00;color:#4d0000;background-color:#ffe6e6}.custom-block.danger .custom-block-title{color:#900}.custom-block.danger a{color:var(--c-text)}.custom-block.details{position:relative;display:block;border-radius:2px;margin:1.6em 0;padding:1.6em;background-color:#eee}.custom-block.details h4{margin-top:0}.custom-block.details figure:last-child,.custom-block.details p:last-child{margin-bottom:0;padding-bottom:0}.custom-block.details summary{outline:0;cursor:pointer}.custom-block-title{margin-bottom:-.4rem;font-weight:600}.sidebar-links{margin:0;padding:0;list-style:none}.sidebar-link-item{display:block;margin:0;border-left:.25rem solid transparent;color:var(--c-text)}a.sidebar-link-item:hover{text-decoration:none;color:var(--c-brand)}a.sidebar-link-item.active{color:var(--c-brand)}.sidebar>.sidebar-links{padding:.75rem 0 5rem}@media (min-width:720px){.sidebar>.sidebar-links{padding:1.5rem 0}}.sidebar>.sidebar-links>.sidebar-link+.sidebar-link{padding-top:.5rem}@media (min-width:720px){.sidebar>.sidebar-links>.sidebar-link+.sidebar-link{padding-top:1.25rem}}.sidebar>.sidebar-links>.sidebar-link>.sidebar-link-item{padding:.35rem 1.5rem .35rem 1.25rem;font-size:1.1rem;font-weight:700}.sidebar>.sidebar-links>.sidebar-link>a.sidebar-link-item.active{border-left-color:var(--c-brand);font-weight:600}.sidebar>.sidebar-links>.sidebar-link>.sidebar-links>.sidebar-link>.sidebar-link-item{display:block;padding:.35rem 1.5rem .35rem 2rem;line-height:1.4;font-size:1rem;font-weight:400}.sidebar>.sidebar-links>.sidebar-link>.sidebar-links>.sidebar-link>a.sidebar-link-item.active{border-left-color:var(--c-brand);font-weight:600}.sidebar>.sidebar-links>.sidebar-link>.sidebar-links>.sidebar-link>.sidebar-links>.sidebar-link>.sidebar-link-item{display:block;padding:.3rem 1.5rem .3rem 3rem;line-height:1.4;font-size:.9rem;font-weight:400}.sidebar>.sidebar-links>.sidebar-link>.sidebar-links>.sidebar-link>.sidebar-links>.sidebar-link>.sidebar-links>.sidebar-link>.sidebar-link-item{display:block;padding:.3rem 1.5rem .3rem 4rem;line-height:1.4;font-size:.9rem;font-weight:400}.debug[data-v-1be840de]{box-sizing:border-box;position:fixed;right:8px;bottom:8px;z-index:9999;border-radius:4px;width:74px;height:32px;color:#eee;overflow:hidden;cursor:pointer;background-color:rgba(0,0,0,.85);transition:all .15s ease}.debug[data-v-1be840de]:hover{background-color:rgba(0,0,0,.75)}.debug.open[data-v-1be840de]{right:0;bottom:0;width:100%;height:100%;margin-top:0;border-radius:0;padding:0 0;overflow:scroll}@media (min-width:512px){.debug.open[data-v-1be840de]{width:512px}}.debug.open[data-v-1be840de]:hover{background-color:rgba(0,0,0,.85)}.title[data-v-1be840de]{margin:0;padding:6px 16px 6px;line-height:20px;font-size:13px}.block[data-v-1be840de]{margin:2px 0 0;border-top:1px solid rgba(255,255,255,.16);padding:8px 16px;font-family:Hack,monospace;font-size:13px}.block+.block[data-v-1be840de]{margin-top:8px}.nav-bar-title[data-v-3e3fd8b1]{font-size:1.3rem;font-weight:600;color:var(--c-text)}.nav-bar-title[data-v-3e3fd8b1]:hover{text-decoration:none}.logo[data-v-3e3fd8b1]{margin-right:.75rem;height:1.3rem;vertical-align:bottom}.icon.outbound{position:relative;top:-1px;display:inline-block;vertical-align:middle;color:var(--c-text-lighter)}.item[data-v-c272f228]{display:block;padding:0 1.5rem;line-height:36px;font-size:1rem;font-weight:600;color:var(--c-text);white-space:nowrap}.item.active[data-v-c272f228],.item[data-v-c272f228]:hover{text-decoration:none;color:var(--c-brand)}.item.external[data-v-c272f228]:hover{border-bottom-color:transparent;color:var(--c-text)}@media (min-width:720px){.item[data-v-c272f228]{border-bottom:2px solid transparent;padding:0;line-height:24px;font-size:.9rem;font-weight:500}.item.active[data-v-c272f228],.item[data-v-c272f228]:hover{border-bottom-color:var(--c-brand);color:var(--c-text)}}.item[data-v-7b16fcd4]{display:block;padding:0 1.5rem 0 2.5rem;line-height:32px;font-size:.9rem;font-weight:500;color:var(--c-text);white-space:nowrap}@media (min-width:720px){.item[data-v-7b16fcd4]{padding:0 24px 0 12px;line-height:32px;font-size:.85rem;font-weight:500;color:var(--c-text);white-space:nowrap}.item.active .arrow[data-v-7b16fcd4]{opacity:1}}.item.active[data-v-7b16fcd4],.item[data-v-7b16fcd4]:hover{text-decoration:none;color:var(--c-brand)}.item.external[data-v-7b16fcd4]:hover{border-bottom-color:transparent;color:var(--c-text)}@media (min-width:720px){.arrow[data-v-7b16fcd4]{display:inline-block;margin-right:8px;border-top:6px solid #ccc;border-right:4px solid transparent;border-bottom:0;border-left:4px solid transparent;vertical-align:middle;opacity:0;transform:translateY(-2px) rotate(-90deg)}}.nav-dropdown-link[data-v-312de885]{position:relative;height:36px;overflow:hidden;cursor:pointer}@media (min-width:720px){.nav-dropdown-link[data-v-312de885]{height:auto;overflow:visible}.nav-dropdown-link:hover .dialog[data-v-312de885]{display:block}}.nav-dropdown-link.open[data-v-312de885]{height:auto}.button[data-v-312de885]{display:block;border:0;padding:0 1.5rem;width:100%;text-align:left;line-height:36px;font-family:var(--font-family-base);font-size:1rem;font-weight:600;color:var(--c-text);white-space:nowrap;background-color:transparent;cursor:pointer}.button[data-v-312de885]:focus{outline:0}@media (min-width:720px){.button[data-v-312de885]{border-bottom:2px solid transparent;padding:0;line-height:24px;font-size:.9rem;font-weight:500}}.button-arrow[data-v-312de885]{display:inline-block;margin-top:-1px;margin-left:8px;border-top:6px solid #ccc;border-right:4px solid transparent;border-bottom:0;border-left:4px solid transparent;vertical-align:middle}.button-arrow.right[data-v-312de885]{transform:rotate(-90deg)}@media (min-width:720px){.button-arrow.right[data-v-312de885]{transform:rotate(0)}}.dialog[data-v-312de885]{margin:0;padding:0;list-style:none}@media (min-width:720px){.dialog[data-v-312de885]{display:none;position:absolute;top:26px;right:-8px;border-radius:6px;padding:12px 0;min-width:128px;background-color:var(--c-bg);box-shadow:var(--shadow-3)}}.nav-links[data-v-1e870408]{padding:.75rem 0;border-bottom:1px solid var(--c-divider)}@media (min-width:720px){.nav-links[data-v-1e870408]{display:flex;padding:6px 0 0;align-items:center;border-bottom:0}.item+.item[data-v-1e870408]{padding-left:24px}}.sidebar-button{position:absolute;top:.6rem;left:1rem;display:none;padding:.6rem;cursor:pointer}.sidebar-button .icon{display:block;width:1.25rem;height:1.25rem}@media screen and (max-width:719px){.sidebar-button{display:block}}.nav-bar[data-v-47437b3e]{position:fixed;top:0;right:0;left:0;z-index:var(--z-index-navbar);display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--c-divider);padding:.7rem 1.5rem .7rem 4rem;height:var(--header-height);background-color:var(--c-bg)}@media (min-width:720px){.nav-bar[data-v-47437b3e]{padding:.7rem 1.5rem}}.flex-grow[data-v-47437b3e]{flex-grow:1}.nav[data-v-47437b3e]{display:none}@media (min-width:720px){.nav[data-v-47437b3e]{display:flex}.navbar__dark-mode[data-v-47437b3e]{display:none}}.nav-icons[data-v-47437b3e]{display:flex;padding:2px 0 0;align-items:center;border-bottom:0;margin-left:12px}.nav-icons .item[data-v-47437b3e]{padding-left:12px}.sidebar[data-v-4668b452]{position:fixed;top:var(--header-height);bottom:0;left:0;z-index:var(--z-index-sidebar);border-right:1px solid var(--c-divider);width:16.4rem;background-color:var(--c-bg);overflow-y:auto;transform:translateX(-100%);transition:transform .25s ease}@media (min-width:720px){.sidebar[data-v-4668b452]{transform:translateX(0)}}@media (min-width:960px){.sidebar[data-v-4668b452]{width:20rem}}.sidebar.open[data-v-4668b452]{transform:translateX(0)}.nav[data-v-4668b452]{display:block}@media (min-width:720px){.nav[data-v-4668b452]{display:none}}.link[data-v-045573c2]{display:inline-block;font-size:1rem;font-weight:500;color:var(--c-text-light)}.link[data-v-045573c2]:hover{text-decoration:none;color:var(--c-brand)}.icon[data-v-045573c2]{margin-left:4px}.last-updated[data-v-03e55a27]{display:inline-block;margin:0;line-height:1.4;font-size:.9rem;color:var(--c-text-light)}@media (min-width:960px){.last-updated[data-v-03e55a27]{font-size:1rem}}.prefix[data-v-03e55a27]{display:inline-block;font-weight:500}.datetime[data-v-03e55a27]{display:inline-block;margin-left:6px;font-weight:400}.page-footer[data-v-22e60b1a]{padding-top:1rem;padding-bottom:1rem;overflow:auto}@media (min-width:960px){.page-footer[data-v-22e60b1a]{display:flex;justify-content:space-between;align-items:center}}.updated[data-v-22e60b1a]{padding-top:4px}@media (min-width:960px){.updated[data-v-22e60b1a]{padding-top:0}}.next-and-prev-link[data-v-0facf926]{padding-top:1rem}.container[data-v-0facf926]{display:flex;justify-content:space-between;border-top:1px solid var(--c-divider);padding-top:1rem}.next[data-v-0facf926],.prev[data-v-0facf926]{display:flex;flex-shrink:0;width:50%}.prev[data-v-0facf926]{justify-content:flex-start;padding-right:12px}.next[data-v-0facf926]{justify-content:flex-end;padding-left:12px}.link[data-v-0facf926]{display:inline-flex;align-items:center;max-width:100%;font-size:1rem;font-weight:500}.text[data-v-0facf926]{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.icon[data-v-0facf926]{display:block;flex-shrink:0;width:16px;height:16px;fill:var(--c-text);transform:translateY(1px)}.icon-prev[data-v-0facf926]{margin-right:8px}.icon-next[data-v-0facf926]{margin-left:8px}.page[data-v-7abc59e6]{padding-top:var(--header-height)}@media (min-width:720px){.page[data-v-7abc59e6]{margin-left:16.4rem}}@media (min-width:960px){.page[data-v-7abc59e6]{margin-left:20rem}}.container[data-v-7abc59e6]{margin:0 auto;padding:0 1.5rem 4rem;padding:.025rem 0 2rem 0;width:calc(100% - var(--slug-width))}.content[data-v-7abc59e6]{padding-bottom:1.5rem}@media (max-width:420px){.content[data-v-7abc59e6]{clear:both}}#ads-container{margin:0 auto}@media (min-width:420px){#ads-container{position:relative;right:0;float:right;margin:-8px -8px 24px 24px;width:146px}}@media (max-width:420px){#ads-container{height:105px;margin:1.75rem 0}}@media (min-width:1400px){#ads-container{position:fixed;right:8px;bottom:8px}}.inline{display:inline}.table{display:table}.grid{display:-ms-grid;display:grid}.hidden{display:none}.h-full{height:100%}.px-4{padding-left:1rem;padding-right:1rem}.tab{-moz-tab-size:4;-o-tab-size:4;tab-size:4}.static{position:static}.relative{position:relative}.content{content:""}.w-full{width:100%}.duration{-webkit-transition-duration:150ms;-o-transition-duration:150ms;transition-duration:150ms}:root{--c-brand:#409eff;--c-brand-light:#66b1ff;--c-white-dark:#f8f8f8;--c-black:#111827;--c-black-light:#161f32;--c-black-lighter:#262a44;--c-text-dark-1:#d9e6eb;--c-text-dark-2:#c4dde6;--c-text-dark-3:#abc4cc;--c-brand-text:var(--c-white);--c-bg-accent:var(--c-white-dark);--code-bg-color:var(--c-white-dark);--code-inline-bg-color:var(--c-white-dark);--code-font-family:'dm',source-code-pro,Menlo,Monaco,Consolas,'Courier New',monospace;--code-font-size:16px;--slug-width:10rem;--header-height:3.6rem;--sidebar-width:16.4rem}html:not(.light):root{--c-text:var(--c-text-dark-1);--c-text-light:var(--c-text-dark-2);--c-text-lighter:var(--c-text-dark-3);--c-divider:var(--c-divider-dark);--c-bg:var(--c-black);--c-bg-accent:var(--c-black-light);--code-bg-color:var(--c-black-light);--code-inline-bg-color:var(--c-black-light)}html:not(.light) .DocSearch{--docsearch-text-color:var(--c-white-dark);--docsearch-container-background:rgba(9, 10, 17, 0.8);--docsearch-modal-background:var(--c-black);--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309;--docsearch-searchbox-background:var(--c-black-lighter);--docsearch-searchbox-focus-background:var(--c-black-light);--docsearch-hit-color:var(--c-text-dark-1);--docsearch-hit-active-color:var(--c-brand-text);--docsearch-hit-shadow:none;--docsearch-hit-background:var(--c-black-light);--docsearch-key-gradient:linear-gradient(-26.5deg, #565872, #31355b);--docsearch-key-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 2px 2px 0 rgba(3, 4, 9, 0.3);--docsearch-footer-background:var(--c-black-light);--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73, 76, 106, 0.5),0 -4px 8px 0 rgba(0, 0, 0, 0.2);--docsearch-logo-color:var(--c-white-dark);--docsearch-muted-color:var(--c-text-dark-1)}.home-hero .image{width:100px;height:100px}.nav-bar .logo{height:26px;margin-right:8px}.nav-bar .nav-bar-title{display:flex;align-items:center}.content img{border-radius:10px}.nav-dropdown-link-item .icon{display:none}.action.alt .icon.outbound{color:currentColor}.action .item.item.item{border-color:var(--c-brand);background-color:var(--c-brand)}.action.alt .item.item.item{background-color:transparent;color:var(--c-text);border-color:var(--c-brand);transition:background-color 150ms ease-in-out,border-color 150ms ease-in-out,color 150ms ease-in-out}.action .item.item.item,.action .item.item.item:hover{color:var(--c-brand-text)}html.dark .action.alt .item.item.item{border-color:var(--c-text)}.action.alt .item.item.item:hover{background-color:var(--c-brand-light);border-color:var(--c-brand-light);color:var(--c-brand-text)}.nav-bar.nav-bar{background-color:var(--c-bg)}.custom-block.tip{border-color:var(--c-brand-light);background-color:var(--c-bg-accent)}.carbon-ads{padding:8px}.bsa-cpc,.carbon-ads{background-color:var(--c-bg-accent)!important}html:not(.light) .custom-block.warning{color:var(--c-text)}html:not(.light) .custom-block.warning a{color:var(--c-brand)}.action.alt .item.item,.bsa-cpc,.carbon-ads,.custom-block.tip,.nav-bar,body,code,div[class*=language-]{transition:background-color .3s ease-in-out,color .3s ease-in-out}.DocSearch,.DocSearch-Form,.DocSearch-Search-Icon,.sidebar.sidebar.sidebar{transition:transform 250ms ease,background-color .3s ease-in-out,color .3s ease-in-out}.DocSearch-Button-Key,.DocSearch-Footer,.DocSearch-Modal{transition:background-color .3s ease-in-out,color .3s ease-in-out,box-shadow 250ms ease-in-out}code{font-size:.95em;padding:.175em .35em}input{border:1px solid #d9d9d9;padding:8px 6px;border-radius:4px;color:rgba(0,0,0,.65);outline:0}input:focus{border-color:#40a9ff}button{padding:5px 16px;border-radius:2px;color:rgba(0,0,0,.65);outline:0;border:1px solid #d9d9d9;cursor:pointer;background:#fff}button:hover{border-color:#40a9ff;color:#40a9ff}:-moz-placeholder,:-ms-input-placeholder,::-moz-placeholder,::-webkit-input-placeholder{color:var(--placeholder-color)}.nav-btn{display:flex;font-size:1.05rem;border:0;outline:0;background:0 0;color:var(--c-text);opacity:.8;cursor:pointer}.nav-btn:hover{opacity:1}.nav-btn svg{margin:auto}.slugs{position:fixed;top:var(--header-height);right:0;max-height:calc(100% - var(--header-height) - 10rem);width:var(--slug-width);padding:50px 24px 0 0;border-right:1px solid var(--border-color);background-color:#fff;z-index:3;overflow-y:auto}[class*=language-] code{color:inherit}code[class*=language-],pre[class*=language-]{color:#d6deeb;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:rgba(29,59,83,.99)}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:rgba(29,59,83,.99)}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{color:#fff;background:#011627}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.prolog{color:#637777}.token.punctuation{color:#c792ea}.namespace{color:#b2ccd6}.token.deleted{color:#ef5350}.token.property,.token.symbol{color:#80cbc4}.token.keyword,.token.operator,.token.tag{color:#7fdbca}.token.boolean{color:#ff5874}.token.number{color:#f78c6c}.token.builtin,.token.char,.token.constant,.token.function{color:#82aaff}.token.doctype,.token.function,.token.selector{color:#c792ea}.token.attr-name,.token.inserted,code .token.inserted{color:#addb67}.language-css .token.string,.style .token.string,.token.entity,.token.string,.token.url{color:#addb67}.token.atrule,.token.attr-value,.token.class-name{color:#ffcb8b}.token.important,.token.regex,.token.variable{color:#d6deeb}.token.bold,.token.important{font-weight:700}html.dark tr:nth-child(2n){background-color:transparent!important}html.light code[class*=language-],html.light pre[class*=language-]{color:#403f53;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}html.light code[class*=language-] ::-moz-selection,html.light code[class*=language-]::-moz-selection,html.light pre[class*=language-] ::-moz-selection,html.light pre[class*=language-]::-moz-selection{text-shadow:none;background:#fbfbfb}html.light code[class*=language-] ::selection,html.light code[class*=language-]::selection,html.light pre[class*=language-] ::selection,html.light pre[class*=language-]::selection{text-shadow:none;background:#fbfbfb}@media print{html.light code[class*=language-],html.light pre[class*=language-]{text-shadow:none}}html.light pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}html.light :not(pre)>code[class*=language-],html.light pre[class*=language-]{color:#fff;background:#fbfbfb}html.light :not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}html.light .token.cdata,html.light .token.comment,html.light .token.prolog{color:#989fb1}html.light .token.punctuation{color:#994cc3}html.light .namespace{color:#0c969b}html.light code .token.deleted{color:#ec5975}html.light .token.keyword,html.light .token.operator,html.light .token.property,html.light .token.symbol{color:#0c969b}html.light .token.tag{color:#994cc3}html.light .token.boolean{color:#bc5454}html.light .token.number{color:#aa0982}html.light .language-css .token.string,html.light .style .token.string,html.light .token.builtin,html.light .token.char,html.light .token.constant,html.light .token.entity,html.light .token.string,html.light .token.url{color:#4876d6}html.light .token.doctype,html.light .token.function,html.light .token.selector{color:#994cc3}html.light .token.attr-name,html.light .token.inserted{color:#4876d6}html.light .token.atrule,html.light .token.attr-value,html.light .token.class-name{color:#111}html.light .token.important,html.light .token.regex,html.light .token.variable{color:#c96765}html.light .token.bold,html.light .token.important{font-weight:700}.home-hero[data-v-e065f044]{margin:2.5rem 0 2.75rem;padding:0 1.5rem;text-align:center}@media (min-width:420px){.home-hero[data-v-e065f044]{margin:3.5rem 0}}@media (min-width:720px){.home-hero[data-v-e065f044]{margin:4rem 0 4.25rem}}.figure[data-v-e065f044]{padding:0 1.5rem}.image[data-v-e065f044]{display:block;margin:0 auto;width:auto;max-width:100%;max-height:280px}.title[data-v-e065f044]{margin-top:1.5rem;font-size:2rem}@media (min-width:420px){.title[data-v-e065f044]{font-size:3rem}}@media (min-width:720px){.title[data-v-e065f044]{margin-top:2rem}}.description[data-v-e065f044]{margin:0;margin-top:.25rem;line-height:1.3;font-size:1.2rem;color:var(--c-text-light)}@media (min-width:420px){.description[data-v-e065f044]{line-height:1.2;font-size:1.6rem}}.action[data-v-e065f044]{margin-top:1.5rem;display:inline-block}.action.alt[data-v-e065f044]{margin-left:1.5rem}@media (min-width:420px){.action[data-v-e065f044]{margin-top:2rem;display:inline-block}}.action[data-v-e065f044] .item{display:inline-block;border-radius:6px;padding:0 20px;line-height:44px;font-size:1rem;font-weight:500;color:var(--c-bg);background-color:var(--c-brand);border:2px solid var(--c-brand);transition:background-color .1s ease}.action.alt[data-v-e065f044] .item{background-color:var(--c-bg);color:var(--c-brand)}.action[data-v-e065f044] .item:hover{text-decoration:none;color:var(--c-bg);background-color:var(--c-brand-light)}@media (min-width:420px){.action[data-v-e065f044] .item{padding:0 24px;line-height:52px;font-size:1.2rem;font-weight:500}}.home-features[data-v-9c9c2344]{margin:0 auto;padding:2.5rem 0 2.75rem;max-width:960px}.home-hero+.home-features[data-v-9c9c2344]{padding-top:0}@media (min-width:420px){.home-features[data-v-9c9c2344]{padding:3.25rem 0 3.5rem}.home-hero+.home-features[data-v-9c9c2344]{padding-top:0}}@media (min-width:720px){.home-features[data-v-9c9c2344]{padding-right:1.5rem;padding-left:1.5rem}}.wrapper[data-v-9c9c2344]{padding:0 1.5rem}.home-hero+.home-features .wrapper[data-v-9c9c2344]{border-top:1px solid var(--c-divider);padding-top:2.5rem}@media (min-width:420px){.home-hero+.home-features .wrapper[data-v-9c9c2344]{padding-top:3.25rem}}@media (min-width:720px){.wrapper[data-v-9c9c2344]{padding-right:0;padding-left:0}}.container[data-v-9c9c2344]{margin:0 auto;max-width:392px}@media (min-width:720px){.container[data-v-9c9c2344]{max-width:960px}}.features[data-v-9c9c2344]{display:flex;flex-wrap:wrap;margin:-20px -24px}.feature[data-v-9c9c2344]{flex-shrink:0;padding:20px 24px;width:100%}@media (min-width:720px){.feature[data-v-9c9c2344]{width:calc(100% / 3)}}.title[data-v-9c9c2344]{margin:0;border-bottom:0;line-height:1.4;font-size:1.25rem;font-weight:500}@media (min-width:420px){.title[data-v-9c9c2344]{font-size:1.4rem}}.details[data-v-9c9c2344]{margin:0;line-height:1.6;font-size:1rem;color:var(--c-text-light)}.title+.details[data-v-9c9c2344]{padding-top:.25rem}.footer[data-v-44324124]{margin:0 auto;max-width:960px}@media (min-width:720px){.footer[data-v-44324124]{padding:0 1.5rem}}.container[data-v-44324124]{padding:2rem 1.5rem 2.25rem}.home-content+.footer .container[data-v-44324124],.home-features+.footer .container[data-v-44324124],.home-hero+.footer .container[data-v-44324124]{border-top:1px solid var(--c-divider)}@media (min-width:420px){.container[data-v-44324124]{padding:3rem 1.5rem 3.25rem}}.text[data-v-44324124]{margin:0;text-align:center;line-height:1.4;font-size:.9rem;color:var(--c-text-light)}.home[data-v-1fd43058]{padding-top:var(--header-height)}.home-content[data-v-1fd43058]{max-width:960px;margin:0 auto;padding:0 1.5rem}@media (max-width:720px){.home-content[data-v-1fd43058]{max-width:392px;padding:0}} \ No newline at end of file diff --git a/components/avatars.html b/components/avatars.html new file mode 100644 index 00000000..0388e45a --- /dev/null +++ b/components/avatars.html @@ -0,0 +1,78 @@ + + + + + + Avatars 头像列表 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Avatars 头像列表

展示多个头像集合

Avatars 组件位于 src/components/Avatars

用法

<script lang="ts" setup>
+import { Avatars } from '@/components/Avatars'
+
+const data = ref<AvatarItem[]>([
+  {
+    name: 'Lily',
+    url: 'https://avatars.githubusercontent.com/u/3459374?v=4'
+  },
+  {
+    name: 'Amanda',
+    url: 'https://avatars.githubusercontent.com/u/3459375?v=4'
+  },
+  {
+    name: 'Daisy',
+    url: 'https://avatars.githubusercontent.com/u/3459376?v=4'
+  },
+  {
+    name: 'Olivia',
+    url: 'https://avatars.githubusercontent.com/u/3459377?v=4'
+  },
+  {
+    name: 'Tina',
+    url: 'https://avatars.githubusercontent.com/u/3459378?v=4'
+  },
+  {
+    name: 'Kitty',
+    url: 'https://avatars.githubusercontent.com/u/3459323?v=4'
+  },
+  {
+    name: 'Helen',
+    url: 'https://avatars.githubusercontent.com/u/3459324?v=4'
+  },
+  {
+    name: 'Sophia',
+    url: 'https://avatars.githubusercontent.com/u/3459325?v=4'
+  },
+  {
+    name: 'Wendy',
+    url: 'https://avatars.githubusercontent.com/u/3459326?v=4'
+  }
+])
+</script>
+
+<template>
+  <Avatars :data="data" />
+</template>
+
+

Avatars 属性

属性说明类型可选值默认值
size头像尺寸ComponentSize、number--
max最大展示个数number-5
data头像数据,详见AvatarItem[]--
showTooltip是否展示名称tipboolean-true

data

属性说明类型可选值默认值
url头像图片地址string--
name名称,非必填string--
+ + + + \ No newline at end of file diff --git a/components/button.html b/components/button.html new file mode 100644 index 00000000..1082a308 --- /dev/null +++ b/components/button.html @@ -0,0 +1,35 @@ + + + + + + BaseButton 按钮组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

BaseButton 按钮组件

二次封装 ElButton ,支持修改主题色

BaseButton 组件位于 src/components/Button

BaseButton 已经全局引入,无需在手动引入

基本用法

<template>
+  <BaseButton type="primary"> Add </BaseButton>
+</template>
+
+

BaseButton 属性

支持 ElButton 的所有属性

+ + + + \ No newline at end of file diff --git a/components/content-detail-wrap.html b/components/content-detail-wrap.html new file mode 100644 index 00000000..176d13da --- /dev/null +++ b/components/content-detail-wrap.html @@ -0,0 +1,41 @@ + + + + + + ContentDetailWrap 详情包裹组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

ContentDetailWrap 详情包裹组件

1.2.4 新增

用于展示详情,自带返回按钮。

ContentDetailWrap 组件位于 src/components/ContentDetailWrap

用法

<script setup lang="ts">
+import { ContentDetailWrap } from '@/components/ContentDetailWrap'
+</script>
+
+<template>
+  <ContentDetailWrap title="详情" @back="push('/example/example-page')">
+    Details
+  </ContentDetailWrap>
+</template>
+
+

ContentDetailWrap 属性

属性说明类型可选值默认值
title标题string--

ContentDetailWrap 事件

方法名说明回调参数
back返回事件-

ContentDetailWrap 插槽

插槽名说明子标签
-默认展示内容-
title自定义标题内容-
right自定义右侧内容-
+ + + + \ No newline at end of file diff --git a/components/count-to.html b/components/count-to.html new file mode 100644 index 00000000..15980fb9 --- /dev/null +++ b/components/count-to.html @@ -0,0 +1,39 @@ + + + + + + CountTo 数字动画组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

CountTo 数字动画组件

基于 vue-count-to 改造

CountTo 组件位于 src/components/CountTo

用法

更复杂点的例子,请在线预览

<script setup lang="ts">
+import { CountTo } from '@/components/CountTo'
+</script>
+
+<template>
+  <CountTo :start-val="0" :end-val="35225" />
+</template>
+
+

CountTo 属性

属性说明类型可选值默认值
startVal初始值number-0
endVal最后展示的值number-2021
duration动画时间number-3000
autoplay是否自动播放boolean-true
decimals小位数number-0
decimal小位数分割符号string-.
separator分割符号string-,
prefix前缀string--
suffix后缀string--
useEasing过渡动画boolean-true
easingFn自定义动画效果(t: number, b: number, c: number, d: number) => number--
+ + + + \ No newline at end of file diff --git a/components/descriptions.html b/components/descriptions.html new file mode 100644 index 00000000..3627de46 --- /dev/null +++ b/components/descriptions.html @@ -0,0 +1,80 @@ + + + + + + Descriptions 描述组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Descriptions 描述组件

注意

从 v2.5.3之后,Descriptions 组件不再基于 element-plusDescriptions 进行二次封装,所以可能有的属性无法使用,具体可以自行修改或者改造,或者可以提issue。

element-plusDescriptions 组件进行封装。

Descriptions 组件位于 src/components/Descriptions

注意

推荐使用 tsx 来使用 Descriptions 组件

用法

更复杂点的例子,请在线预览

<script setup lang="tsx">
+import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
+import { reactive } from 'vue'
+
+const data = reactive({
+  username: 'chenkl',
+  nickName: '梦似花落。',
+  age: 26,
+  phone: '13655971xxxx',
+  email: '502431556@qq.com',
+  addr: '这是一个很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的地址',
+  sex: '男',
+  certy: '3505831994xxxxxxxx'
+})
+
+const schema = reactive<DescriptionsSchema[]>([
+  {
+    field: 'username',
+    label: 'username'
+  },
+  {
+    field: 'nickName',
+    label: 'nickName'
+  },
+  {
+    field: 'phone',
+    label: 'phone'
+  },
+  {
+    field: 'email',
+    label: 'email'
+  },
+  {
+    field: 'addr',
+    label: 'addr',
+    span: 24
+  }
+])
+</script>
+
+<template>
+  <Descriptions
+    title="descriptions"
+    message="message"
+    :data="data"
+    :schema="schema"
+  />
+</template>
+
+

Descriptions 属性

除以下参数外,还支持 element-plusDescriptions 所有属性,详见

属性说明类型可选值默认值
title标题string--
message提示string--
collapse是否显示展开按钮boolean-true
schema布局结构数据,详见DescriptionsSchema[]-[]
data展示的数据Recordable-{}

Schema

属性说明类型可选值默认值
span栅格占比number--
field字段名,唯一值,需要与 data 中的属性名对应string--
label列表标题string--
width列表宽度string/number--
minWidth列表最小宽度string/number--
align内容对齐方式stringleft/center/rightleft
labelAlign标题对齐方式stringleft/center/rightleft
className自定义内容标签类名string--
labelClassName自定义标题标签类名string--
slots插槽对象object--
+ + + + \ No newline at end of file diff --git a/components/dialog.html b/components/dialog.html new file mode 100644 index 00000000..19e269bb --- /dev/null +++ b/components/dialog.html @@ -0,0 +1,51 @@ + + + + + + Dialog 弹窗组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Dialog 弹窗组件

element-plusDialog 组件进行封装。

Dialog 组件位于 src/components/Dialog

用法

<script setup lang="ts">
+import { Dialog } from '@/components/Dialog'
+import { ElButton } from 'element-plus'
+import { ref } from 'vue'
+
+const dialogVisible = ref(false)
+</script>
+
+<template>
+  <ElButton type="primary" @click="dialogVisible = !dialogVisible">
+    open
+  </ElButton>
+  <Dialog v-model="dialogVisible" title="dialog">
+    <div v-for="v in 10000" :key="v">{{ v }}</div>
+    <template #footer>
+      <el-button @click="dialogVisible = false">close</el-button>
+    </template>
+  </Dialog>
+</template>
+
+

Dialog 属性

除以下参数外,还支持 element-plusDialog 所有属性,详见

属性说明类型可选值默认值
modelValue是否显示弹窗,支持v-modelboolean-false
fullscreen是否显示全屏按钮boolean-true
title弹窗标题string-Dialog
maxHeight弹窗内容最大高度string/number-500px

Dialog 插槽

插槽名说明子标签
-弹窗内容-
title弹窗标题内容-
footer弹窗底部内容-
+ + + + \ No newline at end of file diff --git a/components/echart.html b/components/echart.html new file mode 100644 index 00000000..442255c5 --- /dev/null +++ b/components/echart.html @@ -0,0 +1,34 @@ + + + + + + Echart 图表组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Echart 图表组件

echarts 进行封装,自适应窗口大小。

Echart 组件位于 src/components/Echart

用法

只需传入对应的 optionsheight 即可展示图表。

<template>
+  <Echart :options="pieOptions" :height="300" />
+</template>
+

Echart 属性

属性说明类型可选值默认值
optionsechart 对应的配置项,详见EChartsOption-[]
width图表宽度string/number--
height图表高度string/number-500
+ + + + \ No newline at end of file diff --git a/components/editor.html b/components/editor.html new file mode 100644 index 00000000..2b903907 --- /dev/null +++ b/components/editor.html @@ -0,0 +1,46 @@ + + + + + + Editor 富文本组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Editor 富文本组件

基于 wangeditor 封装。

目前项目中的 editor 只是做了简单的封装,需要开发者根据实际情况,自行配置 editorConfig 属性,如,上传图片功能。

可自行阅读 wangeditor文档

Editor 组件位于 src/components/Editor

用法

<script setup lang="ts">
+import { Editor } from '@/components/Editor'
+import { ref} from 'vue'
+
+const defaultHtml = ref('<p>hello <strong>world</strong></p>')
+
+const change = (html: string) => {
+  console.log(html)
+}
+</script>
+
+<template>
+  <Editor v-model="defaultHtml" ref="editorRef" @change="change" />
+</template>
+
+

Editor 属性

属性说明类型可选值默认值
editorId富文本组件唯一值,必填项string-wangeEditor-1
height高度string/number-500px
editorConfigwangeditor 组件的所有配置项IEditorConfig--
modelValue内容双向绑定,支持v-modelstring--

Editor 事件

方法名说明回调参数
change内容改变时,返回 editor 实例editor: IDomEditor

Editor 方法

方法名说明回调参数
getEditorRef获取 editor 实例() => Promise<IDomEditor>
+ + + + \ No newline at end of file diff --git a/components/error.html b/components/error.html new file mode 100644 index 00000000..6a31e0eb --- /dev/null +++ b/components/error.html @@ -0,0 +1,39 @@ + + + + + + Error 缺省组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Error 缺省组件

用于各种占位图组件,如 404403500 等错误页面。

Error 组件位于 src/components/Error

用法

<script setup lang="ts">
+import { Error } from '@/components/Error'
+</script>
+
+<template>
+  <Error />
+</template>
+
+

Error 属性

属性说明类型可选值默认值
type占位图类型string-404

Error 事件

方法名说明回调参数
errorClick点击按钮后的回调-

如何扩展新类型

目前只提供了 404403500 三种类型,如果不满足实际需求,可自行扩展。

只需在 src/components/Error/src/Error.vue 文件的 errorMap 对象扩展对应类型即可。

+ + + + \ No newline at end of file diff --git a/components/footer.html b/components/footer.html new file mode 100644 index 00000000..50a722e5 --- /dev/null +++ b/components/footer.html @@ -0,0 +1,39 @@ + + + + + + Footer 页脚 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Footer 页脚

为整个项目提供页脚信息,自动适应,内容高度不够时,会一直保持在最底部,内容超出则跟随在内容后面。

Footer 组件位于 src/components/Footer 内,如果需要修改页脚信息,可在组件内自定义修改。

用法

<script setup lang="ts">
+import { Footer } from '@/components/Footer'
+</script>
+
+<template>
+  <Footer />
+</template>
+
+
+ + + + \ No newline at end of file diff --git a/components/form.html b/components/form.html new file mode 100644 index 00000000..9332616e --- /dev/null +++ b/components/form.html @@ -0,0 +1,83 @@ + + + + + + Form 表单组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Form 表单组件

element-plusForm 组件进行封装,支持 element-plus 的所有表单组件,并额外扩展了一些功能。

Form 组件位于 src/components/Form

注意

推荐使用 tsx 来使用 Form 组件。

用法

目前支持的表单组件,你可以在 在线预览 中看到。

基础用法

<script setup lang="ts">
+import { Form, FormSchema } from '@/components/Form'
+import { reactive } from 'vue'
+
+const schema = reactive<FormSchema[]>([
+  {
+    field: 'field1',
+    label: 'input',
+    component: 'Input'
+  }
+])
+</script>
+
+<template>
+  <Form :schema="schema" />
+</template>
+
+

useForm

对于复杂的场景,可以配合 useForm 来使用。

如果想看更复杂点的例子,请在线预览

<script setup lang="tsx">
+import { Form, FormSchema } from '@/components/Form'
+import { reactive } from 'vue'
+import { useForm } from '@/hooks/web/useForm'
+
+const schema = reactive<FormSchema[]>([
+  {
+    field: 'field1',
+    label: 'input',
+    component: 'Input'
+  }
+])
+
+const { formRegister } = useForm()
+</script>
+
+<template>
+  <Form :schema="schema" @register="formRegister" />
+</template>
+
+

参数介绍

const { formRegister, formMethods } = useForm()
+

register

formRegister 用于注册 useForm,如果需要使用 useForm 提供的 api,必须将 formRegister 传入组件的 onRegister

formMethods

方法名说明回调参数
setValues用于设置表单值(data: Recordable) => void
setProps用于设置表单属性(props: Recordable) => void
delSchema用于删除表单结构(field: string) => void
addSchema用于新增表单结构(formSchema: FormSchema, index?: number) => void
setSchema用于编辑表单结构(schemaProps: FormSetPropsType[]) => void
getFormData用于获取表单数据<T = Recordable>() => Promise<T>
getComponentExpose用于获取表单组件实例,如 ElInput 实例(field: string) => any
getFormItemExpose用于获取 formItem 组件实例(field: string) => Promise<ComponentRef<typeof ElFormItem>>
getElFormExpose用于获取 elForm 组件实例(field: string) => Promise<ComponentRef<typeof ElForm>>
getFormExpose用于获取二次封装的 Form 组件实例() => Promise<ComponentRef<typeof Form>>

Form 属性

除以下参数外,还支持 element-plusForm 所有属性,详见

属性说明类型可选值默认值
schema生成 Form 的布局结构数组,详见FormSchema-[]
isCol是否需要栅格布局boolean-true
model表单数据对象Recordable-{}
autoSetPlaceholder是否自动设置 placeholderboolean-true
isCustom是否自定义内容boolean-false
labelWidth表单 label 宽度string/number-auto

Schema

属性说明类型可选值默认值
field唯一值,必填项string--
label标题string--
colPropselement-plus 的 col 组件属性ColProps--
componentProps表单组件子属性,详见any--
formItemPropselement-plus 的 form-item 组件属性,详见FormItemProps--
component需要渲染的表单子组件ComponentName--
value表单子组件初始值any--
hidden表单子组件是否隐藏boolean--
remove表单子组件是否隐藏,如果为true,会连同值一同删除,类似v-ifboolean--
optionApi加载 options 方法() => Promise<any>--

ComponentProps

componentProps的类型有: InputComponentProps AutocompleteComponentProps InputNumberComponentProps SelectComponentProps SelectV2ComponentProps CascaderComponentProps SwitchComponentProps RateComponentProps ColorPickerComponentProps TransferComponentProps RadioGroupComponentProps RadioButtonComponentProps DividerComponentProps DatePickerComponentProps DateTimePickerComponentProps TimePickerComponentProps InputPasswordComponentProps TreeSelectComponentProps UploadComponentProps any

基本上每个表单组件都有 slots 的插槽对象,用于自定义插槽,如 InputComponentProps :

slots?: {
+  prefix?: (...args: any[]) => JSX.Element | null
+  suffix?: (...args: any[]) => JSX.Element | null
+  prepend?: (...args: any[]) => JSX.Element | null
+  append?: (...args: any[]) => JSX.Element | null
+}
+

如果需要监听组件事件,如 change 事件,每个 ComponentProps 基本上都有 on 对象用来接收事件,如 InputComponentProps :

on?: {
+  blur?: (event: FocusEvent) => void
+  focus?: (event: FocusEvent) => void
+  change?: (value: string | number) => void
+  clear?: () => void
+  input?: (value: string | number) => void
+}
+
+

FormItemProps

除了以下属性,还支持 element-plus 中的 FormItem 的所有属性

属性说明类型可选值默认值
slotsFormItem的插槽Object--
style子表单项的样式CSSProperties--

Form 方法

方法名说明回调参数
setValues用于设置表单值(data: Recordable) => void
setProps用于设置表单属性(props: Recordable) => void
delSchema用于删除表单结构(field: string) => void
addSchema用于新增表单结构(formSchema: FormSchema, index?: number) => void
setSchema用于编辑表单结构(schemaProps: FormSetPropsType[]) => void
getComponentExpose用于获取表单子组件的实例,如 ElInput 实例(field: string) => any
getFormItemExpose用于获取 FormItem 组件的实例() => Promise<typeof FormItem>

如何新增表单子组件

当项目中内置的表单组件不满足需求时,可以自行添加组件进去。

  1. src/components/Form/src/types/index.tsComponentName 添加你组件名称。
  2. src/components/Form/src/helper/componentMap.ts 引入你自己的组件,并在 componentMap 对象中添加键值对即可。
  3. 如果想要更好的类型提示,可以把自定义组件的类型也添加到 componentProps
+ + + + \ No newline at end of file diff --git a/components/highlight.html b/components/highlight.html new file mode 100644 index 00000000..16b7b212 --- /dev/null +++ b/components/highlight.html @@ -0,0 +1,41 @@ + + + + + + Highlight 高亮组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Highlight 高亮组件

Highlight 组件位于 src/components/Highlight

用法

组件只能接收纯文本。

<script setup lang="ts">
+import { Highlight } from '@/components/Highlight'
+</script>
+
+<template>
+  <Highlight :keys="['十年前', '现在']">
+    种一棵树最好的时间是十年前,其次就是现在。
+  </Highlight>
+</template>
+
+

Highlight 属性

属性说明类型可选值默认值
tag包裹标签string-span
keys高亮的关键字string[]-[]
color高亮的颜色string-var(--el-color-primary)

Highlight 事件

方法名说明回调参数
click关键字点击事件key: string
+ + + + \ No newline at end of file diff --git a/components/i-agree.html b/components/i-agree.html new file mode 100644 index 00000000..4d954f68 --- /dev/null +++ b/components/i-agree.html @@ -0,0 +1,43 @@ + + + + + + IAgree 我同意 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

IAgree 我同意

用于同意协议选项

IAgree 组件位于 src/components/IAgree

用法

<template>
+  <IAgree
+    :link="[
+      {
+        text: '《隐私政策》',
+        url: 'https://www.baidu.com'
+      }
+    ]"
+    text="我同意《隐私政策》"
+  />
+</template>
+
+

Avatars 属性

属性说明类型可选值默认值
text文案string--
link需要跳转的高亮数据,详见LinkItem[]--
属性说明类型可选值默认值
url跳转地址,非必填string--
text高亮文案string--
onClick点击高亮文案执行的方法,非必填() => void--
+ + + + \ No newline at end of file diff --git a/components/icon-picker.html b/components/icon-picker.html new file mode 100644 index 00000000..1ba65256 --- /dev/null +++ b/components/icon-picker.html @@ -0,0 +1,41 @@ + + + + + + IconPicker 图标选择器组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

IconPicker 图标选择器组件

用于快速选择 Iconify 图标。

IconPicker 组件位于 src/components/IconPicker

TIP

目前只集成了 Ant Design Icons 、Element Plus、TDesign Icons 三个开源项目图标

用法

<script lang="ts" setup>
+import { IconPicker } from '@/components/IconPicker'
+
+const currentIcon = ref('tdesign:book-open')
+</script>
+
+<template>
+  <IconPicker v-model="currentIcon" />
+</template>
+
+

如何添加其他开源项目的图标

可以执行 pnpm run icon 然后选择你想要的图标集

之后,在 IconPicker.vue 导入,并添加到 icons 中即可。

Icon 属性

属性说明类型可选值默认值
modelValue选中项绑定值,支持v-modelstring--
+ + + + \ No newline at end of file diff --git a/components/icon.html b/components/icon.html new file mode 100644 index 00000000..5c75a3f1 --- /dev/null +++ b/components/icon.html @@ -0,0 +1,50 @@ + + + + + + Icon 图标组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Icon 图标组件

用于项目内组件的展示,基本支持所有图标库(支持按需加载,只打包所用到的图标),支持使用本地 svg 和 Iconify 图标。

Icon 组件位于 src/components/Icon

TIP

Iconify 上,你可以查询到你想要的所有图标并使用,不管是不是 element-plus 的图标库。

用法

基本用法

如果以svg-icon: 开头,则会在本地中找到该 svg 图标,否则,会加载 Iconify 图标。

<template>
+  <!-- 加载本地 svg -->
+  <Icon icon="svg-icon:peoples" />
+
+  <!-- 加载 Iconify -->
+  <Icon icon="ep:aim" />
+</template>
+
+

useIcon

如果需要在其他组件中如 ElButton 传入 icon 属性,可以使用 useIcon

<script setup lang="ts">
+import { useIcon } from '@/hooks/web/useIcon'
+import { ElButton } from 'element-plus'
+
+const icon = useIcon({ icon: 'svg-icon:save' })
+</script>
+
+<template>
+  <ElButton :icon="icon"> button </ElButton>
+</template>
+

参数介绍

const icon = useIcon(props)
+

props

详见

Icon 属性

属性说明类型可选值默认值
icon图标名string--
color图标颜色string--
size图标大小number-16
hoverColorhover颜色string--
+ + + + \ No newline at end of file diff --git a/components/image-viewer.html b/components/image-viewer.html new file mode 100644 index 00000000..f6fe38b9 --- /dev/null +++ b/components/image-viewer.html @@ -0,0 +1,53 @@ + + + + + + ImageViewer 图片预览组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

ImageViewer 图片预览组件

element-plusImageViewer 组件函数化,通过函数方便创建组件。

ImageViewer 组件位于 src/components/ImageViewer

用法

<script setup lang="ts">
+import { createImageViewer } from '@/components/ImageViewer'
+import { ElButton } from 'element-plus'
+
+const open = () => {
+  createImageViewer({
+    urlList: [
+      'https://img1.baidu.com/it/u=657828739,1486746195&fm=26&fmt=auto&gp=0.jpg',
+      'https://img0.baidu.com/it/u=3114228356,677481409&fm=26&fmt=auto&gp=0.jpg',
+      'https://img1.baidu.com/it/u=508846955,3814747122&fm=26&fmt=auto&gp=0.jpg',
+      'https://img1.baidu.com/it/u=3536647690,3616605490&fm=26&fmt=auto&gp=0.jpg',
+      'https://img1.baidu.com/it/u=4087287201,1148061266&fm=26&fmt=auto&gp=0.jpg',
+      'https://img2.baidu.com/it/u=3429163260,2974496379&fm=26&fmt=auto&gp=0.jpg'
+    ]
+  })
+}
+</script>
+
+<template>
+  <ElButton type="primary" @click="open">预览</ElButton>
+</template>
+
+

createImageViewer

参数

属性说明类型可选值默认值
urlList图片列表string[]--
zIndex层级number--
initialIndex默认展示第几张number-1
infinite是否可以循环切换boolean-true
hideOnClickModal点击蒙版是否关闭boolean-false
appendToBody是否添加到 body 中boolean-false
show是否显示图片预览boolean-false
+ + + + \ No newline at end of file diff --git a/components/infotip.html b/components/infotip.html new file mode 100644 index 00000000..40e5d1bc --- /dev/null +++ b/components/infotip.html @@ -0,0 +1,51 @@ + + + + + + Infotip 信息提示组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Infotip 信息提示组件

基于 Highlight 组件封装。

Infotip 组件位于 src/components/Infotip

用法

<script setup lang="ts">
+import { Infotip } from '@/components/Infotip'
+</script>
+
+<template>
+  <Infotip
+    title="推荐使用Iconify组件"
+    :schema="[
+      {
+        label: 'Iconify组件基本包含所有的图标,你可以查询到你想要的任何图标。并且打包只会打包所用到的图标。',
+        keys: ['Iconify']
+      },
+      {
+        label: '访问地址',
+        keys: ['访问地址']
+      }
+    ]"
+  />
+</template>
+
+

Infotip 属性

属性说明类型可选值默认值
title标题string--
schema展示的数据内容string[]/TipSchema[]-[]
showIndex显示序号boolean-true
highlightColor高亮颜色string-var(--el-color-primary)

Infotip 事件

方法名说明回调参数
click关键字点击事件key: string
+ + + + \ No newline at end of file diff --git a/components/input-password.html b/components/input-password.html new file mode 100644 index 00000000..5746875d --- /dev/null +++ b/components/input-password.html @@ -0,0 +1,42 @@ + + + + + + InputPassword 密码输入框 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

InputPassword 密码输入框

element-plusInput 组件进行封装。

InputPassword 组件位于 src/components/InputPassword

用法

<script setup lang="ts">
+import { InputPassword } from '@/components/InputPassword'
+import { ref } from 'vue'
+
+const password = ref('')
+</script>
+
+<template>
+  <InputPassword v-model="password" strength />
+</template>
+
+

InputPassword 属性

除以下参数外,还支持 element-plusInput 所有属性,详见

属性说明类型可选值默认值
strength是否显示强度校验boolean-false
modelValue选中项绑定值,支持v-modelstring--
+ + + + \ No newline at end of file diff --git a/components/introduction.html b/components/introduction.html new file mode 100644 index 00000000..01acb8c4 --- /dev/null +++ b/components/introduction.html @@ -0,0 +1,31 @@ + + + + + + 前言 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

前言

本项目中集成了很多常用的功能组件,方便开发者使用。如果你开发项目中自带的组件不满足你的需求,可以不使用或者自行改造,这都是可以的。

目前本项目中基本上都是采用按需引入的方式,只有 IconPermission 进行了全局注册。

如果不喜欢按需引入,可以自行去集成 unplugin-auto-import

+ + + + \ No newline at end of file diff --git a/components/json-editor.html b/components/json-editor.html new file mode 100644 index 00000000..afc4b219 --- /dev/null +++ b/components/json-editor.html @@ -0,0 +1,69 @@ + + + + + + JsonEditor JSON编辑器组件(2.2.0+) | vue-element-plus-admin + + + + + + + + + + + + + + + + +

JsonEditor JSON编辑器组件(2.2.0+)

基于 vue-json-pretty 封装。

可自行阅读 vue-json-pretty文档

JsonEditor 组件位于 src/components/JsonEditor

用法

<script setup lang="ts">
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { JsonEditor } from '@/components/JsonEditor'
+import { useI18n } from '@/hooks/web/useI18n'
+import { ref, watch } from 'vue'
+
+const { t } = useI18n()
+
+const defaultData = ref({
+  title: '标题',
+  content: '内容'
+})
+
+watch(
+  () => defaultData.value,
+  (val) => {
+    console.log(val)
+  },
+  {
+    deep: true
+  }
+)
+
+setTimeout(() => {
+  defaultData.value = {
+    title: '异步标题',
+    content: '异步内容'
+  }
+}, 4000)
+</script>
+
+<template>
+  <ContentWrap :title="t('richText.jsonEditor')" :message="t('richText.jsonEditorDes')">
+    <JsonEditor v-model="defaultData" />
+  </ContentWrap>
+</template>
+
+

JsonEditor 属性

可查看 vue-json-pretty文档

Editor 事件

可查看 vue-json-pretty文档

+ + + + \ No newline at end of file diff --git a/components/permission.html b/components/permission.html new file mode 100644 index 00000000..90da65c2 --- /dev/null +++ b/components/permission.html @@ -0,0 +1,43 @@ + + + + + + Permission 权限组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Permission 权限组件

用于颗粒级别的按钮权限组件

Permission 组件位于 src/components/Permission

在线例子

在线例子

用法

由于项目中的颗粒级别的权限,是放在路由表中,所以会判断在当前路由 meta.permission 是否包含传入的权限值,有的话则展示。

如果权限实现不一致的话,可以自行改造下。

基本用法

<template>
+  <Permission permission="add">
+    <ElButton type="primary"> Add </ElButton>
+  </Permission>
+</template>
+
+

指令形式

权限控制目前还提供了指令的使用方式,并且已经全局注册,所以可以在任意组件中使用 v-hasPermi

<ElButton v-hasPermi="'add'" type="primary"> Add </ElButton>
+
+

函数形式

除了以上两种,还可以使用函数的形式进行控制

import { hasPermi } from '@/components/Permission'
+
+
<ElButton v-if="hasPermi('add')" type="primary"> Add </ElButton>
+
+

Permission 属性

属性说明类型可选值默认值
permission权限值string--
+ + + + \ No newline at end of file diff --git a/components/qrcode.html b/components/qrcode.html new file mode 100644 index 00000000..c73ef1be --- /dev/null +++ b/components/qrcode.html @@ -0,0 +1,39 @@ + + + + + + Qrcode 二维码组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Qrcode 二维码组件

基于 qrcode 封装。

Qrcode 组件位于 src/components/Qrcode

用法

更复杂点的例子,请在线预览

<script setup lang="ts">
+import { Qrcode } from '@/components/Qrcode'
+</script>
+
+<template>
+  <Qrcode text="vue-element-plus-admin" />
+</template>
+
+

Qrcode 属性

属性说明类型可选值默认值
tag以什么标签生成二维码stringcanvas/imgcanvas
text二维码内容string/Array--
optionsqrcode.js 配置项QRCodeRenderersOptions-{}
width二维码宽度number-200
logo二维码 logoQrcodeLogo/string--
disabled二维码是否过期boolean-false
disabledText二维码过期提示内容string--

Qrcode 事件

方法名说明回调参数
done生成二维码后的回调-
click二维码点击事件-
disabled-click二维码过期后点击事件-
+ + + + \ No newline at end of file diff --git a/components/search.html b/components/search.html new file mode 100644 index 00000000..9791de0c --- /dev/null +++ b/components/search.html @@ -0,0 +1,317 @@ + + + + + + Search 查询组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Search 查询组件

基于 Form 组件封装,支持收缩展开。

Search 组件位于 src/components/Search

注意

推荐使用 tsx 来使用 Search 组件

用法

基础用法

更复杂例子,请在线预览

<script setup lang="ts">
+import { Search } from '@/components/Search'
+import { FormSchema } from '@/components/Form'
+import { reactive } from 'vue'
+
+const schema = reactive<FormSchema[]>([
+  {
+    field: 'field1',
+    label: 'input',
+    component: 'Input'
+  }
+])
+</script>
+
+<template>
+  <Search :schema="schema" />
+</template>
+
+

useSearch

对于复杂的场景,可以配合 useSearch 来使用。

<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Search } from '@/components/Search'
+import { reactive, ref, unref } from 'vue'
+import { ElButton } from 'element-plus'
+import { getDictOneApi } from '@/api/common'
+import { FormSchema } from '@/components/Form'
+import { useSearch } from '@/hooks/web/useSearch'
+
+const { t } = useI18n()
+
+const { searchRegister, searchMethods } = useSearch()
+const { setSchema, setProps, setValues } = searchMethods
+
+const schema = reactive<FormSchema[]>([
+  {
+    field: 'field1',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field2',
+    label: t('formDemo.select'),
+    component: 'Select',
+    componentProps: {
+      options: [
+        {
+          label: 'option1',
+          value: '1'
+        },
+        {
+          label: 'option2',
+          value: '2'
+        }
+      ],
+      on: {
+        change: (value: string) => {
+          console.log(value)
+        }
+      }
+    }
+  },
+  {
+    field: 'field3',
+    label: t('formDemo.radio'),
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        {
+          label: 'option-1',
+          value: '1'
+        },
+        {
+          label: 'option-2',
+          value: '2'
+        }
+      ]
+    }
+  },
+  {
+    field: 'field5',
+    component: 'DatePicker',
+    label: t('formDemo.datePicker'),
+    componentProps: {
+      type: 'date'
+    }
+  },
+  {
+    field: 'field6',
+    component: 'TimeSelect',
+    label: t('formDemo.timeSelect')
+  },
+  {
+    field: 'field8',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field9',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field10',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field11',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field12',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field13',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field14',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field15',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field16',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field17',
+    label: t('formDemo.input'),
+    component: 'Input'
+  },
+  {
+    field: 'field18',
+    label: t('formDemo.input'),
+    component: 'Input'
+  }
+])
+
+const isGrid = ref(false)
+
+const changeGrid = (grid: boolean) => {
+  setProps({
+    isCol: grid
+  })
+  // isGrid.value = grid
+}
+
+const layout = ref('inline')
+
+const changeLayout = () => {
+  layout.value = unref(layout) === 'inline' ? 'bottom' : 'inline'
+}
+
+const buttonPosition = ref('left')
+
+const changePosition = (position: string) => {
+  layout.value = 'bottom'
+  buttonPosition.value = position
+}
+
+const getDictOne = async () => {
+  const res = await getDictOneApi()
+  if (res) {
+    setSchema([
+      {
+        field: 'field2',
+        path: 'componentProps.options',
+        value: res.data
+      }
+    ])
+  }
+}
+
+const handleSearch = (data: any) => {
+  console.log(data)
+}
+
+const delRadio = () => {
+  setSchema([
+    {
+      field: 'field3',
+      path: 'remove',
+      value: true
+    }
+  ])
+}
+
+const restoreRadio = () => {
+  setSchema([
+    {
+      field: 'field3',
+      path: 'remove',
+      value: false
+    }
+  ])
+}
+
+const setValue = () => {
+  setValues({
+    field1: 'Joy'
+  })
+}
+
+const searchLoading = ref(false)
+const changeSearchLoading = () => {
+  searchLoading.value = true
+  setTimeout(() => {
+    searchLoading.value = false
+  }, 2000)
+}
+
+const resetLoading = ref(false)
+const changeResetLoading = () => {
+  resetLoading.value = true
+  setTimeout(() => {
+    resetLoading.value = false
+  }, 2000)
+}
+</script>
+
+<template>
+  <ContentWrap
+    :title="`${t('searchDemo.search')} ${t('searchDemo.operate')}`"
+    style="margin-bottom: 20px"
+  >
+    <ElButton @click="changeGrid(true)">{{ t('searchDemo.grid') }}</ElButton>
+    <ElButton @click="changeGrid(false)">
+      {{ t('searchDemo.restore') }} {{ t('searchDemo.grid') }}
+    </ElButton>
+
+    <ElButton @click="changeLayout">
+      {{ t('searchDemo.button') }} {{ t('searchDemo.position') }}
+    </ElButton>
+
+    <ElButton @click="changePosition('left')">
+      {{ t('searchDemo.bottom') }} {{ t('searchDemo.position') }}-{{ t('searchDemo.left') }}
+    </ElButton>
+    <ElButton @click="changePosition('center')">
+      {{ t('searchDemo.bottom') }} {{ t('searchDemo.position') }}-{{ t('searchDemo.center') }}
+    </ElButton>
+    <ElButton @click="changePosition('right')">
+      {{ t('searchDemo.bottom') }} {{ t('searchDemo.position') }}-{{ t('searchDemo.right') }}
+    </ElButton>
+    <ElButton @click="getDictOne">
+      {{ t('formDemo.select') }} {{ t('searchDemo.dynamicOptions') }}
+    </ElButton>
+    <ElButton @click="delRadio">{{ t('searchDemo.deleteRadio') }}</ElButton>
+    <ElButton @click="restoreRadio">{{ t('searchDemo.restoreRadio') }}</ElButton>
+    <ElButton @click="setValue">{{ t('formDemo.setValue') }}</ElButton>
+
+    <ElButton @click="changeSearchLoading">
+      {{ t('searchDemo.search') }} {{ t('searchDemo.loading') }}
+    </ElButton>
+    <ElButton @click="changeResetLoading">
+      {{ t('searchDemo.reset') }} {{ t('searchDemo.loading') }}
+    </ElButton>
+  </ContentWrap>
+
+  <ContentWrap :title="t('searchDemo.search')" :message="t('searchDemo.searchDes')">
+    <Search
+      :schema="schema"
+      :is-col="isGrid"
+      :layout="layout"
+      :button-position="buttonPosition"
+      :search-loading="searchLoading"
+      :reset-loading="resetLoading"
+      show-expand
+      expand-field="field6"
+      @search="handleSearch"
+      @reset="handleSearch"
+      @register="searchRegister"
+    />
+  </ContentWrap>
+</template>
+
+

参数介绍

const { searchRegister, searchMethods } = useSearch()
+

register

searchRegister 用于注册 useSearch,如果需要使用 useSearch 提供的 api,必须将 searchRegister 传入组件的 onRegister

formMethods

方法名说明回调参数
setValues用于设置表单值(data: Recordable) => void
setProps用于设置表单属性(props: Recordable) => void
delSchema用于删除表单结构(field: string) => void
addSchema用于新增表单结构(formSchema: FormSchema, index?: number) => void
setSchema用于编辑表单结构(schemaProps: FormSetPropsType[]) => void
getFormData用于获取表单数据<T = Recordable>() => Promise<T>

Search 属性

属性说明类型可选值默认值
schema生成 Search 的布局结构数组,详见FormSchema-[]
isCol是否需要栅格布局boolean-true
labelWidth表单 label 宽度string/number-auto
layout操作按钮风格位置stringinline/bottominline
buttonPosition底部操作按钮的对齐方式stringleft/center/rightcenter
showSearch是否显示查询按钮boolean-true
showReset是否显示重置按钮boolean-true
expand是否显示伸缩按钮boolean-false
expandField伸缩的界限字段string--
inline是否是行内boolean-true
removeNoValueItem是否自动去除空值boolean-true
model初始化数据object--
searchLoading查询按钮加载状态boolean-false
resetLoading重置按钮加载状态boolean-false

Search 事件

方法名说明回调参数
search查询后的回调data: Recordable
reset重置后的回调data: Recordable

Search 方法

方法名说明回调参数
setValues用于设置表单值(data: Recordable) => void
setProps用于设置表单属性(props: Recordable) => void
delSchema用于删除表单结构(field: string) => void
addSchema用于新增表单结构(formSchema: FormSchema, index?: number) => void
setSchema用于编辑表单结构(schemaProps: FormSetPropsType[]) => void
getElFormExpose用于获取 Form 组件的实例() => Promise<typeof ElForm>
+ + + + \ No newline at end of file diff --git a/components/sticky.html b/components/sticky.html new file mode 100644 index 00000000..49c70970 --- /dev/null +++ b/components/sticky.html @@ -0,0 +1,41 @@ + + + + + + Sticky 黏性组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Sticky 黏性组件

1.2.4 新增

Sticky 组件位于 src/components/Sticky

用法

<script setup lang="ts">
+import { Sticky } from '@/components/Sticky'
+</script>
+
+<template>
+  <Sticky :offset="90">
+    <div style="padding: 10px; background-color: lightblue"> Sticky 距离顶部90px </div>
+  </Sticky>
+</template>
+
+

Sticky 属性

属性说明类型可选值默认值
offset距离顶部或者底部的距离number-0
zIndex设置元素的堆叠顺序number-999
className设置指定的classstring/number--
position定位方式,默认为(top),表示距离顶部位置,可以设置为top或者bottomstringtop/bottomtop
+ + + + \ No newline at end of file diff --git a/components/table.html b/components/table.html new file mode 100644 index 00000000..2f55493f --- /dev/null +++ b/components/table.html @@ -0,0 +1,193 @@ + + + + + + Table 表格组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Table 表格组件

element-plusTable 组件进行封装,只需传入 columnsdata 参数,即可渲染出响应的表格出来。

Table 组件位于 src/components/Table

注意

推荐使用 tsx 来使用 Table 组件。

用法

基础用法

<script setup lang="ts">
+import { reactive } from 'vue'
+import { Table, TableColumn } from '@/components/Table'
+
+const columns = reactive<TableColumn[]>([
+  {
+    field: 'title',
+    label: 'title'
+  },
+  {
+    field: 'author',
+    label: 'author'
+  }
+])
+
+const data = reactive([
+  {
+    title: 'title1',
+    author: 'author1'
+  },
+  {
+    title: 'title2',
+    author: 'author2'
+  },
+  {
+    title: 'title3',
+    author: 'author3'
+  }
+])
+</script>
+
+<template>
+  <Table :columns="columns" :data="data" />
+</template>
+
+

useTable

推荐配合 useTable 来使用

复杂点的例子,请在线预览

<script setup lang="tsx">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableColumn, TableSlotDefault } from '@/components/Table'
+import { getTreeTableListApi } from '@/api/table'
+import { reactive, unref } from 'vue'
+import { ElTag, ElButton } from 'element-plus'
+import { useTable } from '@/hooks/web/useTable'
+
+const { tableRegister, tableState } = useTable({
+  fetchDataApi: async () => {
+    const { currentPage, pageSize } = tableState
+    const res = await getTreeTableListApi({
+      pageIndex: unref(currentPage),
+      pageSize: unref(pageSize)
+    })
+    return {
+      list: res.data.list,
+      total: res.data.total
+    }
+  }
+})
+const { loading, dataList, total, currentPage, pageSize } = tableState
+
+const { t } = useI18n()
+
+const columns = reactive<TableColumn[]>([
+  {
+    field: 'selection',
+    type: 'selection'
+  },
+  {
+    field: 'index',
+    label: t('tableDemo.index'),
+    type: 'index'
+  },
+  {
+    field: 'content',
+    label: t('tableDemo.header'),
+    children: [
+      {
+        field: 'title',
+        label: t('tableDemo.title')
+      },
+      {
+        field: 'author',
+        label: t('tableDemo.author')
+      },
+      {
+        field: 'display_time',
+        label: t('tableDemo.displayTime')
+      },
+      {
+        field: 'importance',
+        label: t('tableDemo.importance'),
+        formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
+          return (
+            <ElTag type={cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'}>
+              {cellValue === 1
+                ? t('tableDemo.important')
+                : cellValue === 2
+                ? t('tableDemo.good')
+                : t('tableDemo.commonly')}
+            </ElTag>
+          )
+        }
+      },
+      {
+        field: 'pageviews',
+        label: t('tableDemo.pageviews')
+      }
+    ]
+  },
+  {
+    field: 'action',
+    label: t('tableDemo.action'),
+    slots: {
+      default: (data) => {
+        return (
+          <ElButton type="primary" onClick={() => actionFn(data)}>
+            {t('tableDemo.action')}
+          </ElButton>
+        )
+      }
+    }
+  }
+])
+
+const actionFn = (data: TableSlotDefault) => {
+  console.log(data)
+}
+</script>
+
+<template>
+  <ContentWrap :title="`${t('router.treeTable')} ${t('tableDemo.example')}`">
+    <Table
+      v-model:pageSize="pageSize"
+      v-model:currentPage="currentPage"
+      :columns="columns"
+      :data="dataList"
+      row-key="id"
+      :loading="loading"
+      sortable
+      :pagination="{
+        total: total
+      }"
+      @register="tableRegister"
+    />
+  </ContentWrap>
+</template>
+
+</script>
+
+<template>
+  <Table
+    v-model:pageSize="tableObject.pageSize"
+    v-model:currentPage="tableObject.currentPage"
+    :data="tableObject.tableList"
+    :loading="tableObject.loading"
+    :pagination="{
+      total: tableObject.total
+    }"
+    @register="register"
+  />
+</template>
+
+

参数介绍

const { tableRegister, tableState, tableMethods } = useTable(props: UseTableConfig)
+

props

在使用 useTable 的时候,需要传入 fetchDataApi,为了保证可定制,需要自行在 fetchDataApi 中完成请求逻辑,之后返回结果 { list: Array, total?: number },后续分页,就可以自动请求数据。

如果需要删除,同样需要传入 fetchDelApi ,返回一个 Boolean 来判断是否删除完成,后续 useTable 将自行刷新表格。

tableRegister

tableRegister 用于注册 useTable,如果需要使用 useTable 提供的 api,必须将 tableRegister 传入组件的 onRegister

tableState

表格状态

属性说明类型可选值默认值
pageSize每页显示多少条number-10
currentPage当前页number-1
total总条数number--
dataList表格数据any[]-[]
loading表格是否加载中boolean-false

tableMethods

方法名说明回调参数
setProps用于表格组件属性(props: Recordable) => void
getList获取表格数据() => Promise<void>
setColumn设置表头结构(columnProps: TableSetProps[]) => void
addColumn新增表头结构(tableColumn: TableColumn, index?: number) => void
delColumn删除表头结构(field: string) => void
getElTableExpose获取 ElTable 实例() => Promise<typeof ElTable>
refresh刷新表格() => void
delList删除数据(idsLength: number) => Promise<void>

Table 属性

除以下参数外,还支持 element-plusTable 所有属性,详见

属性说明类型可选值默认值
pageSize每页显示多少条,支持 v-model 双向绑定number-10
currentPage当前页,支持 v-model 双向绑定number-1
selection是否多选boolean-true
showOverflowTooltip是否所有的超出隐藏,优先级低于 schema 中的 showOverflowTooltipboolean-true
columns表头结构,详见TableColumn[]-[]
expand是否显示展开行boolean-false
pagination是否展示分页,详见Pagination/undefined--
reserveSelection仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)boolean-false
loading加载状态boolean-false
reserveIndex是否叠加索引boolean-false
align内容对齐方式stringleft/center/rightleft
headerAlign表头对齐方式stringleft/center/rightleft
data表格数据Recordable[]-[]
showAction是否显示表格操作boolean-false
imagePreview需要展示图片的字段string[]--
videoPreview需要展示视频的字段string[]--
customContent是否自定义内容boolean-false
cardBodyStyle卡片内容样式CSSProperties--
cardBodyClass卡片内容类名string--
cardWrapStyle卡片容器样式CSSProperties--
cardWrapClass卡片容器类名string--

Columns

除了以下属性,还支持 element-plusTableColumn 属性。

属性说明类型可选值默认值
field唯一值,如需展示正确的数据,需要与 data 中的属性名对应string--
label表头名称string--
hidden是否隐藏boolean--
slots插槽对象object--
children子项,用于多级表头TableColumn[]--

Pagination

支持 element-plusPagination 所有属性,详见

Table 方法

方法名说明回调参数
setProps用于设置表格属性(props: Recordable) => void
setColumn用于修改表头结构(columnProps: TableSetPropsType[]) => void
addColumn新增表头结构(tableColumn: TableColumn, index?: number) => void
delColumn删除表头结构(field: string) => void
+ + + + \ No newline at end of file diff --git a/components/video-player.html b/components/video-player.html new file mode 100644 index 00000000..c5e346b0 --- /dev/null +++ b/components/video-player.html @@ -0,0 +1,42 @@ + + + + + + VideoPlayer 视频播放器组件(2.5.0+) | vue-element-plus-admin + + + + + + + + + + + + + + + + +

VideoPlayer 视频播放器组件(2.5.0+)

基于 xgplayer 二次封装的视频播放器

VideoPlayer 组件位于 src/components/VideoPlayer

用法

<script lang="ts" setup>
+import { VideoPlayer } from '@/components/VideoPlayer'
+</script>
+
+<template>
+  <VideoPlayer
+    url="//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-720p.mp4"
+    poster="//lf3-static.bytednsdoc.com/obj/eden-cn/nupenuvpxnuvo/xgplayer_doc/poster.jpg"
+  />
+</template>
+
+

VideoPlayer 属性

属性说明类型可选值默认值
url视频的地址string--
poster视频的封面string--
+ + + + \ No newline at end of file diff --git a/components/video-viewer.html b/components/video-viewer.html new file mode 100644 index 00000000..165bb2ab --- /dev/null +++ b/components/video-viewer.html @@ -0,0 +1,46 @@ + + + + + + VideoViewer 图片预览组件(2.5.0+) | vue-element-plus-admin + + + + + + + + + + + + + + + + +

VideoViewer 图片预览组件(2.5.0+)

VideoPlayer 组件函数化,通过函数方便创建组件。

VideoViewer 组件位于 src/components/VideoViewer

用法

<script setup lang="ts">
+import { createVideoViewer } from '@/components/VideoPlayer'
+
+const open = () => {
+  createVideoViewer({
+    url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-720p.mp4',
+    poster: '//lf3-static.bytednsdoc.com/obj/eden-cn/nupenuvpxnuvo/xgplayer_doc/poster.jpg'
+  })
+}
+</script>
+
+<template>
+  <ElButton type="primary" @click="open">预览</ElButton>
+</template>
+
+

VideoViewer

参数

属性说明类型可选值默认值
url视频的地址string--
poster视频的封面string--
+ + + + \ No newline at end of file diff --git a/components/waterfall.html b/components/waterfall.html new file mode 100644 index 00000000..5d005832 --- /dev/null +++ b/components/waterfall.html @@ -0,0 +1,87 @@ + + + + + + Waterfall 瀑布流组件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Waterfall 瀑布流组件

瀑布流组件

Waterfall 组件位于 src/components/Waterfall

TIP

data 数据必须带有高度字段,用于确保计算出正确的位置

用法

<script lang="ts" setup>
+import { Waterfall } from '@/components/Waterfall'
+import Mock from 'mockjs'
+import { ref, unref } from 'vue'
+import { toAnyString } from '@/utils'
+
+const data = ref<any>([])
+
+const getList = () => {
+  const list: any = []
+  for (let i = 0; i < 20; i++) {
+    // 随机 100, 500 之间的整数
+    const height = Mock.Random.integer(100, 500)
+    const width = Mock.Random.integer(100, 500)
+    list.push(
+      Mock.mock({
+        width,
+        height,
+        id: toAnyString(),
+        image_uri: Mock.Random.image(`${width}x${height}`)
+      })
+    )
+  }
+  data.value = [...unref(data), ...list]
+  if (unref(data).length >= 60) {
+    end.value = true
+  }
+}
+getList()
+
+const loading = ref(false)
+
+const end = ref(false)
+
+const loadMore = () => {
+  loading.value = true
+  setTimeout(() => {
+    getList()
+    loading.value = false
+  }, 1000)
+}
+</script>
+
+<template>
+  <Waterfall
+    :data="data"
+    :loading="loading"
+    :end="end"
+    :props="{
+      src: 'image_uri',
+      height: 'height'
+    }"
+    @load-more="loadMore"
+  />
+</template>
+
+

Waterfall 属性

属性说明类型可选值默认值
data要展示的数据Array--
reset窗口变化是否重新布局booleantrue/falsetrue
width每个项的宽度number-200
gap每个项的间距number-20
loadingText加载中文字string-加载中...
loading是否加载中boolean-false
end是否加载结束boolean-false
endText是否加载结束文字string-没有更多了
props字段别名object-{ src: 'src', height: 'height' }

Waterfall 事件

方法名说明回调参数
loadMore加载更多事件-
+ + + + \ No newline at end of file diff --git a/dep/create-module.html b/dep/create-module.html new file mode 100644 index 00000000..e979fcd5 --- /dev/null +++ b/dep/create-module.html @@ -0,0 +1,33 @@ + + + + + + 模版生成 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

模版生成

介绍

为了方便开发者快速生成 组件视图 文件,本项目提供了 plop 为开发者生成统一的文件模版。

生成组件

运行

npm run p
+

选择 component 之后,输入组件名,如 newCom,既可在 src/components 目录下创建对应的组件。

组件名开头如果是小写,会自动转换为大写。

生成视图

运行

npm run p
+

选择 view 之后,输入路径,默认为 views,接着输入模块名,如 newView,既可在 src/${views} 目录下创建对应的视图文件。

如何扩展

如果需要扩展额外的视图模版,可以在根目录 plopfile.js 文件中,添加初始模版,然后到根目录 plop 文件夹中,添加对应的模块代码。具体可以参考 component 下的代码。

更多的 plop 配置,则可以查阅 文档

+ + + + \ No newline at end of file diff --git a/dep/dark.html b/dep/dark.html new file mode 100644 index 00000000..7242cc70 --- /dev/null +++ b/dep/dark.html @@ -0,0 +1,31 @@ + + + + + + 黑暗主题 | vue-element-plus-admin + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dep/i18n.html b/dep/i18n.html new file mode 100644 index 00000000..8e3b74c0 --- /dev/null +++ b/dep/i18n.html @@ -0,0 +1,121 @@ + + + + + + 国际化 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

国际化

如果你使用的 vscode 开发工具,则推荐安装 I18n-ally 这个插件

I18n-ally 插件

安装了该插件后,你的代码内可以实时看到对应的语言内容

配置默认语言

src/config/locale.ts 内配置 currentLocale 为其他语言。

import { useCache } from '@/hooks/web/useCache'
+import zhCn from 'element-plus/lib/locale/lang/zh-cn'
+import en from 'element-plus/lib/locale/lang/en'
+
+const { wsCache } = useCache()
+
+export const elLocaleMap = {
+  'zh-CN': zhCn,
+  en: en
+}
+export interface LocaleState {
+  currentLocale: LocaleDropdownType
+  localeMap: LocaleDropdownType[]
+}
+
+export const localeModules: LocaleState = {
+  currentLocale: {
+    lang: wsCache.get('lang') || 'zh-CN',
+    elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN']
+  },
+  // 多语言
+  localeMap: [
+    {
+      lang: 'zh-CN',
+      name: '简体中文'
+    },
+    {
+      lang: 'en',
+      name: 'English'
+    }
+  ]
+}
+
+

语言文件

src/locales 可以配置具体的语言,目前项目中的语言都是没有拆分的,全部放一起,后续会考虑拆分出来,比较好维护。

语言导入逻辑说明

src/plugins/vueI18n/index.ts 内可以看到

const defaultLocal = await import(`../../locales/${locale.lang}.ts`)
+

这会导入 src/locales 文件语言包。

使用

引入项目自带的 useI18n 注意不要引入 vue-i18n 的 useI18n

import { useI18n } from '/@/hooks/web/useI18n'
+
+const { t } = useI18n()
+
+const title = t('common.menu')
+

切换语言

切换语言需要使用 src/locales/useLocale.ts

import { useLocale } from '@/hooks/web/useLocale'
+const { changeLocale } = useLocale()
+
+changeLocale('en')
+

新增

语言文件

src/locales 增加对应语言的文件即可

新增语言

目前项目自带的语言只有 zh_CNen 两种

如果需要新增,按以下操作即可

  1. src/locales 下语言文件
  2. types/global.d.tsLocaleType 添加对应的类型
  3. src/config/locale.ts localeMap 中添加对应语言

远程读取语言数据

目前项目会在 src/main.ts 内等待 setupI18n 这个函数执行完之后才会渲染界面,所以只需在 setupI18n 内的 createI18nOptions 发送 ajax 请求,将对应的数据设置到 i18n 实例上即可。

const createI18nOptions = async (): Promise<I18nOptions> => {
+  const localeStore = useLocaleStoreWithOut()
+  const locale = localeStore.getCurrentLocale
+  const localeMap = localeStore.getLocaleMap
+  // 这里改为远程请求即可。
+  const defaultLocal = await import(`../../locales/${locale.lang}.ts`)
+  const message = defaultLocal.default ?? {}
+
+  setHtmlPageLang(locale.lang)
+
+  localeStore.setCurrentLocale({
+    lang: locale.lang
+    // elLocale: elLocal
+  })
+
+  return {
+    legacy: false,
+    locale: locale.lang,
+    fallbackLocale: locale.lang,
+    messages: {
+      [locale.lang]: message
+    },
+    availableLocales: localeMap.map((v) => v.lang),
+    sync: true,
+    silentTranslationWarn: true,
+    missingWarn: false,
+    silentFallbackWarn: true
+  }
+}
+

useLocale

代码: src/hooks/web/useLocale/

当手动切换语言的时候会触发 useLocale 函数,useLocale 也是异步函数,只需等待接口返回响应的数据后,再进行设置即可

export const useLocale = () => {
+  // Switching the language will change the locale of useI18n
+  // And submit to configuration modification
+  const changeLocale = async (locale: LocaleType) => {
+    const globalI18n = i18n.global
+    
+    // 改为远程获取
+    const langModule = await import(`../../locales/${locale}.ts`)
+
+    globalI18n.setLocaleMessage(locale, langModule.default)
+
+    setI18nLanguage(locale)
+  }
+
+  return {
+    changeLocale
+  }
+}
+
+ + + + \ No newline at end of file diff --git a/dep/lint.html b/dep/lint.html new file mode 100644 index 00000000..18b36e49 --- /dev/null +++ b/dep/lint.html @@ -0,0 +1,48 @@ + + + + + + Lint | vue-element-plus-admin + + + + + + + + + + + + + + + + +

Lint

介绍

使用 lint 的好处

具备基本工程素养的同学都会注重编码规范,而代码风格检查(Code Linting,简称 Lint)是保障代码规范一致性的重要手段。

遵循相应的代码规范有以下好处

  • 较少 bug 错误率
  • 高效的开发效率
  • 更高的可读性

项目内集成了以下几种代码校验方式

  1. eslint 用于校验代码格式规范
  2. commitlint 用于校验 git 提交信息规范
  3. stylelint 用于校验 css/less 规范
  4. prettier 代码格式化

注意

lint 不是必须的,但是很有必要,一个项目做大了以后或者参与人员过多后,就会出现各种风格迥异的代码,对后续的维护造成了一定的麻烦。

ESLint

ESLint 是一个代码规范和错误检查工具,可以根据自己的团队设置符合自己团队的规范

手动校验代码

# 执行下面代码.能修复的会自动修复,不能修复的需要手动修改
+pnpm run lint:eslint
+

配置项

项目的 eslint 配置位于根目录下 .eslintrc.js 内,可以根据团队自行修改代码规范

CommitLint

在一个团队中,每个人的 git 的 commit 信息都不一样,五花八门,没有一个机制很难保证规范化,如何才能规范化呢?可能你想到的是 git 的 hook 机制,去写 shell 脚本去实现。这当然可以,其实 JavaScript 有一个很好的工具可以实现这个模板,它就是 commitlint(用于校验 git 提交信息规范)。

配置

commit-lint 的配置位于项目根目录下 commitlint.config.js

Git 提交规范

  • feat 新功能
  • fix 修补 bug
  • docs 文档
  • style 格式、样式(不影响代码运行的变动)
  • refactor 重构(即不是新增功能,也不是修改 BUG 的代码)
  • perf 优化相关,比如提升性能、体验
  • test 添加测试
  • build 编译相关的修改,对项目构建或者依赖的改动
  • ci 持续集成修改
  • chore 构建过程或辅助工具的变动
  • revert 回滚到上一个版本
  • workflow 工作流改进
  • mod 不确定分类的修改
  • wip 开发中
  • types 类型

如何关闭

.husky/commit-msg 内注释以下代码即可

# npx --no-install commitlint --edit "$1"
+

示例


+git commit -m 'feat: add new component'
+
+

Stylelint

stylelint 用于校验项目内部 css 的风格,加上编辑器的自动修复,可以很好的统一项目内部 css 风格

配置

stylelint 配置位于根目录下 stylelint.config.js

编辑器配合

如果您使用的是 vscode 编辑器的话,只需要安装下面插件,即可在保存的时候自动格式化文件内部 css 样式

插件

StyleLint

Prettier

prettier 可以用于统一项目代码风格,统一的缩进,单双引号,尾逗号等等风格

配置

prettier 配置文件位于项目根目录下 prettier.config.js

编辑器配合

如果您使用的是 vscode 编辑器的话,只需要安装下面插件,即可在保存的时候自动格式化文件内部 js 格式

插件

Prettier

Git Hook

git hook 一般结合各种 lint,在 git 提交代码的时候进行代码风格校验,如果校验没通过,则不会进行提交。需要开发者自行修改后再次进行提交

husky

有一个问题就是校验会校验全部代码,但是我们只想校验我们自己提交的代码,这个时候就可以使用 husky。

最有效的解决方案就是将 Lint 校验放到本地,常见做法是使用 husky 或者 pre-commit 在本地提交之前先做一次 Lint 校验。

项目在 .husky 内部定义了相应的 hooks

如何跳过某一个检查

# 加上 --no-verify即可跳过git hook校验(--no-verify 简写为 -n)
+git commit -m "xxx" --no-verify
+

lint-staged

用于自动修复提交文件风格问题

lint-staged 配置位于项目 .husky 目录下 lintstagedrc.js

module.exports = {
+  // 对指定格式文件 在提交的时候执行相应的修复命令
+  '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
+  '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'],
+  'package.json': ['prettier --write'],
+  '*.vue': ['eslint --fix', 'stylelint --fix', 'prettier --write', 'git add .'],
+  '*.{scss,less,styl,css,html}': ['stylelint --fix', 'prettier --write', 'git add .'],
+  '*.md': ['prettier --write'],
+};
+
+ + + + \ No newline at end of file diff --git a/donate/donate.html b/donate/donate.html new file mode 100644 index 00000000..6c7e0f8b --- /dev/null +++ b/donate/donate.html @@ -0,0 +1,31 @@ + + + + + + 捐赠 | vue-element-plus-admin + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/group/group.html b/group/group.html new file mode 100644 index 00000000..bd78f417 --- /dev/null +++ b/group/group.html @@ -0,0 +1,31 @@ + + + + + + 技术交流群 | vue-element-plus-admin + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/guide/auth.html b/guide/auth.html new file mode 100644 index 00000000..86f509d8 --- /dev/null +++ b/guide/auth.html @@ -0,0 +1,188 @@ + + + + + + 权限 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

权限

项目中集成了 2 种权限处理方式:

  1. 第一种是由前端来控制菜单,即服务端只返回有权限的 keys,由前端自行去匹配
  2. 第二种是通过服务端返回的路由数据结构来动态生成路由表

目前项目中提供了测试的帐号:

admin/admin

前端控制权限

实现原理: 在前端固定写死路由的权限,指定路由有哪些权限可以查看。只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内。在登陆后或者其他方式获取对应的路由 keys 后,遍历路由表去匹配 keys,过滤生成可以访问的路由表,再通过 router.addRoutes 添加到路由实例,实现权限的过滤。

缺点: 权限相对不自由,因为路由表的控制在前端,不管是要排序还是修改,都需要前端去修改,服务端只提供有权限的路由 keys

后台动态获取

实现原理: 是通过接口动态生成路由表,且遵循一定的数据结构返回。前端根据需要处理该数据为可识别的结构,再通过 router.addRoutes 添加到路由实例,实现权限的动态生成。

优点: 所有的菜单控制都是通过服务端的接口返回,前端只负责渲染,后期维护成本降低,优先推荐此方式。

实现

  1. src/store/modules/permission.tsgenerateRoutes() 进行更改。

接收的 type 参数,目前只是针对于本项目的模拟情况,如果不需要或者不适用,可自行改动。

generateRoutes(
+  type: 'server' | 'frontEnd' | 'static',
+  routers?: AppCustomRouteRecordRaw[] | string[]
+): Promise<unknown> {
+  return new Promise<void>((resolve) => {
+    let routerMap: AppRouteRecordRaw[] = []
+    if (type === 'server') {
+      // 模拟后端过滤菜单
+      routerMap = generateRoutesByServer(routers as AppCustomRouteRecordRaw[])
+    } else if (type === 'frontEnd') {
+      // 模拟前端过滤菜单
+      routerMap = generateRoutesByFrontEnd(cloneDeep(asyncRouterMap), routers as string[])
+    } else {
+      // 直接读取静态路由表
+      routerMap = cloneDeep(asyncRouterMap)
+    }
+    // 动态路由,404一定要放到最后面
+    this.addRouters = routerMap.concat([
+      {
+        path: '/:path(.*)*',
+        redirect: '/404',
+        name: '404Page',
+        meta: {
+          hidden: true,
+          breadcrumb: false
+        }
+      }
+    ])
+    // 渲染菜单的所有路由
+    this.routers = cloneDeep(constantRouterMap).concat(routerMap)
+    resolve()
+  })
+}
+

前端控制实现

  1. src/utils/routerHelper.tsgenerateRoutesByFrontEnd () 进行更改。目前本项目的前端权限控制,是根据 path 是否相同来进行过滤演示的,如果不符合需求,需要手动更改以下判断逻辑。
// 前端控制路由生成
+export const generateRoutesByFrontEnd  = (
+  routes: AppRouteRecordRaw[],
+  keys: string[],
+  basePath = '/'
+): AppRouteRecordRaw[] => {
+  const res: AppRouteRecordRaw[] = [];
+
+  for (const route of routes) {
+    const meta = route.meta as RouteMeta;
+    // skip some route
+    if (meta.hidden && !meta.showMainRoute) {
+      continue;
+    }
+
+    let data: Nullable<AppRouteRecordRaw> = null;
+
+    let onlyOneChild: Nullable<string> = null;
+    if (route.children && route.children.length === 1 && !meta.alwaysShow) {
+      onlyOneChild = (
+        isUrl(route.children[0].path)
+          ? route.children[0].path
+          : pathResolve(pathResolve(basePath, route.path), route.children[0].path)
+      ) as string;
+    }
+
+    // 开发者可以根据实际情况进行扩展
+    for (const item of keys) {
+      // 通过路径去匹配
+      if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
+        data = Object.assign({}, route);
+      } else {
+        const routePath = pathResolve(basePath, onlyOneChild || route.path);
+        if (routePath === item || meta.followRoute === item) {
+          data = Object.assign({}, route);
+        }
+      }
+    }
+
+    // recursive child routes
+    if (route.children && data) {
+      data.children = generateRoutesByFrontEnd (route.children, keys, pathResolve(basePath, data.path));
+    }
+    if (data) {
+      res.push(data as AppRouteRecordRaw);
+    }
+  }
+  return res;
+};
+

后台动态获取

  1. src/utils/routerHelper.tsgenerateRoutesByServer () 进行更改。
// 后端控制路由生成
+export const generateRoutesByServer  = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
+  const res: AppRouteRecordRaw[] = [];
+
+  for (const route of routes) {
+    const data: AppRouteRecordRaw = {
+      path: route.path,
+      name: route.name,
+      redirect: route.redirect,
+      meta: route.meta,
+    };
+    if (route.component) {
+      const comModule =
+        modules[`../${route.component}.vue`] || modules[`../${route.component}.tsx`];
+      const component = route.component as string;
+      if (!comModule && !component.includes('#')) {
+        console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`);
+      } else {
+        // 动态加载路由文件,可根据实际情况进行自定义逻辑
+        data.component =
+          component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule;
+      }
+    }
+    // recursive child routes
+    if (route.children) {
+      data.children = generateRoutesByServer (route.children);
+    }
+    res.push(data as AppRouteRecordRaw);
+  }
+  return res;
+};
+

公用部分修改

  1. src/views/Login/components/LoginForm.vuegetRole() 进行更改。

需要开发者自行根据需求进行代码变更。

// 获取角色信息
+const getRole = async () => {
+  const formData = await getFormData<UserType>()
+  const params = {
+    roleName: formData.username
+  }
+  const res =
+    appStore.getDynamicRouter && appStore.getServerDynamicRouter
+      ? await getAdminRoleApi(params)
+      : await getTestRoleApi(params)
+  if (res) {
+    const routers = res.data || []
+    setStorage('roleRouters', routers)
+    appStore.getDynamicRouter && appStore.getServerDynamicRouter
+      ? await permissionStore.generateRoutes('server', routers).catch(() => {})
+      : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
+
+    permissionStore.getAddRouters.forEach((route) => {
+      addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
+    })
+    permissionStore.setIsAddRouters(true)
+    push({ path: redirect.value || permissionStore.addRouters[0].path })
+  }
+};
+
  1. src/permission.ts,以下这种情况,是考虑到手动刷新,所以需要获取到缓存中的动态菜单重新渲染。
// 开发者可根据实际情况进行修改
+const roleRouters = getStorage('roleRouters') || []
+
+// 是否使用动态路由
+if (appStore.getDynamicRouter) {
+  appStore.serverDynamicRouter
+    ? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
+    : await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
+  } else {
+  await permissionStore.generateRoutes('static')
+}
+
+permissionStore.getAddRouters.forEach((route) => {
+  router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
+})
+const redirectPath = from.query.redirect || to.path
+const redirect = decodeURIComponent(redirectPath as string)
+const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
+permissionStore.setIsAddRouters(true)
+next(nextData)
+

静态路由(无权限)

有时候,我们并不需要动态路由,那么可以在 src/config/app.ts 中把 dynamicRouter 设置为 false,这样我们取得都是项目中的静态路由表了。

内部逻辑已经处理了静态路由的部分,所以可以无需关心其他。

+ + + + \ No newline at end of file diff --git a/guide/component.html b/guide/component.html new file mode 100644 index 00000000..8b2dca0b --- /dev/null +++ b/guide/component.html @@ -0,0 +1,73 @@ + + + + + + 组件注册 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

组件注册

按需引入

项目目前的组件注册机制是按需注册,是在需要用到的页面才引入。

<script setup lang="ts">
+import { ElBacktop } from 'element-plus'
+import { useDesign } from '@/hooks/web/useDesign'
+
+const { getPrefixCls, variables } = useDesign()
+
+const prefixCls = getPrefixCls('backtop')
+</script>
+
+<template>
+  <ElBacktop
+    :class="`${prefixCls}-backtop`"
+    :target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"
+  />
+</template>
+
+

tsx 文件注册

tsx 文件内不能使用全局注册组件,需要手动引入组件使用。

全局注册

如果觉得按需引入太麻烦,可以进行全局注册,在src/components/index.ts,添加需要注册的组件。

目前只有 Icon 组件进行了全局注册。

import type { App } from 'vue'
+import { Icon } from './Icon'
+
+export const setupGlobCom = (app: App<Element>): void => {
+  app.component('Icon', Icon)
+}
+
+

如果 element-plus 的组件需要全局注册,在 src/plugins/elementPlus/index.ts 添加需要注册的组件。

目前 element-plus 中只有 ElLoadingElScrollbar 进行了全局注册。

import type { App } from 'vue'
+
+// 需要全局引入一些组件,如ElScrollbar,不然一些下拉项样式有问题
+import { ElLoading, ElScrollbar } from 'element-plus'
+
+const plugins = [ElLoading]
+
+const components = [ElScrollbar]
+
+export const setupElementPlus = (app: App) => {
+  plugins.forEach((plugin) => {
+    app.use(plugin)
+  })
+
+  components.forEach((component) => {
+    app.component(component.name, component)
+  })
+}
+
+
+ + + + \ No newline at end of file diff --git a/guide/deploy.html b/guide/deploy.html new file mode 100644 index 00000000..a6cc2e7d --- /dev/null +++ b/guide/deploy.html @@ -0,0 +1,43 @@ + + + + + + 构建&部署 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

构建&部署

前言

由于是展示项目,所以打包后相对较大,如果项目中没有用到的插件,可以删除对应的文件或者路由,不引用即可,没有引用就不会打包。

构建

项目开发完成之后,执行以下命令进行构建

  • 开发环境 pnpm run build:dev ===> dist-dev
  • 测试环境 pnpm run build:test ===> dist-test
  • 生产环境 pnpm run build:pro ===> dist-pro

构建打包成功之后,会在根目录生成 dist-* 文件夹,里面就是构建打包好的文件。

预览

发布之前可以在本地进行预览

不能直接打开构建后的 html 文件

使用项目自定的命令进行预览(推荐)

# 先打包在进行预览
+
+# 预览开发环境
+pnpm run serve:dev
+
+# 预览测试环境
+pnpm run serve:test
+
+# 预览生产环境
+pnpm run serve:pro
+

部署

注意

项目默认是在生产环境开启 Mock,这样做非常不好,只是为了演示环境有数据,不建议在生产环境使用 Mock,而应该使用真实的后台接口。

发布

简单的部署只需要将最终生成的静态文件,dist-* 文件夹的静态文件发布到你的 cdn 或者静态服务器即可。

部署时可能会发现资源路径不对,只需要修改对应的.env.xxx文件即可。

# 根据自己路径来配置更改
+VITE_BASE_PATH = /dist-dev/
+
+ + + + \ No newline at end of file diff --git a/guide/design.html b/guide/design.html new file mode 100644 index 00000000..49971f68 --- /dev/null +++ b/guide/design.html @@ -0,0 +1,38 @@ + + + + + + 样式 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

样式

介绍

主要介绍如何在项目中使用和规划样式文件。

默认使用 less 作为预处理语言,建议在使用前或者遇到疑问时学习一下 Less 的相关特性。

项目中使用的通用样式,都存放于 src/style/ 下面。

.
+├── index.less # 入口
+├── theme.less # 主题相关
+├── var.css  # css变量
+└── variables.module.less # less变量
+
+

全局注入

variables.module.less 这个文件会被全局注入到所有文件,所以在页面内可以直接使用变量而不需要手动引入。

var.css 则是注入到根元素,所以在每个地方也都能用到。

unocss

项目中使用了 unocss,具体参见文件使用说明。

可能没有用到人会觉得用起来很不习惯,但就个人而言,用起来还是挺香的。减少了很多不必要的麻烦

语法如下:

<div class="relative w-full h-full px-4"></div>
+
+ + + + \ No newline at end of file diff --git a/guide/fqa.html b/guide/fqa.html new file mode 100644 index 00000000..8e9a8ba6 --- /dev/null +++ b/guide/fqa.html @@ -0,0 +1,32 @@ + + + + + + 前言 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

前言

提示

列举了一些常见的问题。有问题可以先来这里寻找,看是否有相关解答,没有的话可以上 issue 中提问或者搜索

  • 在进行开发时,先着重看下项目指南,把大体文档都过一遍,虽然文档写的可能不能满足所有人的需要,但好歹是有,毕竟项目框架上手有难度。
  • 一些非框架内的问题,可以上百度或者谷歌上搜索。
  • 实在无法解决的,可以上交流群里面提问,相互讨论,毕竟群里面的大佬还是挺多的。
  • 如果文档有落后或者不明确的,可以提 issue ,慢慢完善,毕竟文档的积累完善并不是靠一个人能完成的。

关于修改了 store 的默认值,无法应用问题

因为项目中有的 Store 默认开始了持久化,所以不管你修没修改默认值,都会优先默认取缓存中的值,所以如果修改完默认值之后,还请手动清除下浏览器的 localStorage ,默认值就会生效了。

控制台路由警告问题

本地运行之后,会出现路由警告

[Vue Router warn]: No match found for location with path "/authorization/menu"
+

这个无需关心,是vue-router的问题,项目打包上线后是不会有次警告,所以该问题可以忽略。

本地启动首次加载慢

请自行去百度下 vite 快是怎么个快法,本地运行启动,都是按需加载,一次性加载了几十个资源,当然会比较慢,有了缓存之后非首次就会实现秒开了。

目前项目中已经对于启动时间进行了优化,本地默认加载了全部的 element-plus 的样式文件,会多多少少减少请求资源数量。

启动快慢还是得根据当前文件引用的资源数量来决定。

路由切换页面刷新的问题

这是因为你在该路由中使用了第三方模块,这个模块是没有预加载的,所以需要重新去加载这个模块,然后就会出现 page reload,极大的影响了开发体验,所以可以在 vite.config.ts 中去配置预加载列表:optimizeDeps.include,这样在服务启动的使用,会先把这些模块给预先加载打包。

依赖安装问题

  • 如果依赖安装不了或者启动报错可以先尝试 删除 pnpm-locknode_modules,然后重新运行 pnpm i
  • 可以尝试配置国内镜像安装
  • 请确保项目路径没有特殊字符如:中文、韩文、日文以及空格
  • 请确保 node.js 版本大于等于 18
  • 请确保包管理器使用的是 pnpm

打包文件过大

由于完整版引入了许多第三方模块,所以打包体积会比较大,可以自行删除不需要的第三方模块,或者使用精简版(mini分支)来进行开发。

合理的进行拆包,目前项目中对一些比较大的第三方模块进行了拆包处理。

部署上线运行启动慢

  • 请检查打包体积是否合理
  • 请确保网络正常
  • 可以开启cdn缓存
  • 可以开启http2
  • 开启gzip

菜单定制化

菜单是根据路由配置来生成,请先看下已有的路由配置是否可以满足你的需要,如果不满足,可以自行去定制化。可以查看路由相关文档

组件使用问题

在使用组件的时候,遇到问题,可以先看下对应的在线例子,看是否有对应的代码,基本上覆盖了95%的使用方式,或者查看对应的组件文档。

编辑器代码报错

项目中大部分使用了 tsx ,所以原先 template 的一些代码规范就不适用了,如 v-if 得使用 {判断条件 ? 成立 : 不成立} 来进行显示隐藏,可以查阅下相关文档。

并且请确保如果要使用 tsx 语法, script 是否声明了 lang="tsx"

添加路由之后,页面无法展示

如果是在项目中直接添加静态路由,需要确保 appStore 中的 dynamicRouterserverDynamicRouterfalse,并且手动清除下浏览器的 localStorage

添加新的 vue 文件后,编辑器类型报错

这是 Volar 插件的问题,一般重启下编辑器即可生效。

如何启用非在线图标

设置 VITE_USE_ONLINE_ICON=false ,可能在有的版本设置之后会无效,是因为有BUG,可以复制最新版本的 uno.config.tsIcon.vue 的最新代码。

+ + + + \ No newline at end of file diff --git a/guide/index.html b/guide/index.html new file mode 100644 index 00000000..fe8aec96 --- /dev/null +++ b/guide/index.html @@ -0,0 +1,85 @@ + + + + + + 开始 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

开始

本文将快速的帮助你从头运行并启动项目。

环境准备

本地环境需要安装 PnpmNode.jsGit

为什么使用 Pnpm,而不是用其他包管理器,大家可以搜索一下,这里就不做过多的阐述了。

注意

  • Node.js 版本要求14.x以上,这里推荐 16.x 及以上。

工具配置

如果你使用的 IDE 是vscode的话,可以安装以下工具来提高开发效率及代码格式化:

代码获取

注意

注意存放代码的目录及所有父级目录不能存在中文、韩文、日文以及空格,否则安装依赖后启动会出错。

从 GitHub 获取代码

# clone 代码
+git clone https://github.com/kailong321200875/vue-element-plus-admin.git
+
+

从 Gitee 获取代码

git clone https://gitee.com/kailong110120130/vue-element-plus-admin.git
+

代码同步

不用担心 Gitee 代码库和 Github 代码库不同步,每次版本提交发布,都会及时同步到 Gitee 上。

安装

安装 Node.js

如果您电脑未安装Node.js,请安装它,推荐 18.x 及以上

验证

# 验证 npm 是否安装成功
+npm -v
+
+# 验证 node 是否安装成功
+node -v
+

如果你需要同时存在多个 node 版本,可以使用 Nvm 或者其他工具进行 Node.js 进行版本管理。

安装依赖

Pnpm 安装

推荐使用 Pnpm进行依赖安装(若其他包管理器安装不了需要自行处理)。

如果未安装 Pnpm,可以用下面命令来进行全局安装

# 全局安装 pnpm
+npm i -g pnpm
+
+# 验证
+pnpm -v
+

安装依赖

在项目根目录下,打开命令窗口执行,耐心等待安装完成即可

# 安装依赖
+pnpm i
+

安装依赖时 husky 安装失败

请查看你的源码是否从 Github 或者 Gitee 直接下载的,直接下载是没有 .git 文件夹的,而 husky 需要依赖 git 才能安装。此时需使用 git init 初始化项目,再尝试重新安装即可。

当依赖安装完成后,执行以下命令即可启动项目:

pnpm run dev
+

npm script

"scripts": {
+  # 安装依赖
+  "i": "pnpm install",
+  # 本地开发环境运行
+  "dev": "vite --mode base",
+  # typeScript 检测
+  "ts:check": "vue-tsc --noEmit",
+  # 打包生产环境
+  "build:pro": "vite build --mode pro",
+  # 打包开发环境
+  "build:dev": "npm run ts:check && vite build --mode dev",
+  # 打包测试环境
+  "build:test": "npm run ts:check && vite build --mode test",
+  # 本地预览 已打包的生产环境项目包
+  "serve:pro": "vite preview --mode pro",
+  # 本地预览 已打包的开发环境项目包
+  "serve:dev": "vite preview --mode dev",
+  # 本地预览 已打包的测试环境项目包
+  "serve:test": "vite preview --mode test",
+  # 检测可更新依赖
+  "npm:check": "npx npm-check-updates",
+  # 删除 node_modules
+  "clean": "npx rimraf node_modules",
+  # 删除 缓存
+  "clean:cache": "npx rimraf node_modules/.cache",
+  # eslint 检测
+  "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
+  # eslint 格式化
+  "lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"",
+  # stylelint 格式化
+  "lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
+  "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
+  "lint:pretty": "pretty-quick --staged",
+  "postinstall": "husky install",
+  # 快速生成统一规范的模块
+  "p": "plop"
+},
+
+ + + + \ No newline at end of file diff --git a/guide/introduction.html b/guide/introduction.html new file mode 100644 index 00000000..72a0f986 --- /dev/null +++ b/guide/introduction.html @@ -0,0 +1,83 @@ + + + + + + 介绍 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

介绍

注意

  • 如果需要 v1 版本的文档,请到 v1 分支进行 clone ,目前文档仅支持 v2 版本

简介

vue-element-plus-admin 是一个基于 element-plus 免费开源的中后台模版。使用了最新的 Vue3ViteTypescript等主流技术开发,开箱即用的中后台前端解决方案,可以用来作为项目的启动模版,也可用于学习参考。并且时刻关注着最新技术动向,尽可能的第一时间更新。

vue-element-plus-admin 的定位是后台集成方案,因为集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。

如需要基础模版,请切换到 mini 分支,mini 只简单集成了一些如:布局、动态菜单等常用布局功能,更适合开发者进行二次开发。

需要掌握的基础知识

本项目需要一定前端基础知识,请确保掌握 Vue 的基础知识,以便能处理一些常见的问题。

为了能快速上手本项目,请先大致浏览一遍文档及在线示例。

建议在开发前先学一下以下内容,提前了解和学习这些知识,会对项目理解非常有帮助:

目录结构

.
+├── .github # github workflows 相关
+├── .husky # husky 配置
+├── .vscode # vscode 配置
+├── mock # 自定义 mock 数据及配置
+├── public # 静态资源
+├── src # 项目代码
+│   ├── api # api接口管理
+|   |── axios # axios配置
+│   ├── assets # 静态资源
+│   ├── components # 公用组件
+│   ├── constants # 存放常量
+│   ├── hooks # 常用hooks
+│   ├── layout # 布局组件
+│   ├── locales # 语言文件
+│   ├── plugins # 外部插件
+│   ├── router # 路由配置
+│   ├── store # 状态管理
+│   ├── styles # 全局样式
+│   ├── utils # 全局工具类
+│   ├── views # 路由页面
+│   ├── App.vue # 入口vue文件
+│   ├── main.ts # 主入口文件
+│   └── permission.ts # 路由拦截
+├── types # 全局类型
+├── .env.base # 本地开发环境 环境变量配置
+├── .env.dev # 打包到开发环境 环境变量配置
+├── .env.gitee # 针对 gitee 的环境变量 可忽略
+├── .env.pro # 打包到生产环境 环境变量配置
+├── .env.test # 打包到测试环境 环境变量配置
+├── .eslintignore # eslint 跳过检测配置
+├── .eslintrc.js # eslint 配置
+├── .gitignore # git 跳过配置
+├── .prettierignore # prettier 跳过检测配置
+├── .stylelintignore # stylelint 跳过检测配置
+├── .versionrc 自动生成版本号及更新记录配置
+├── CHANGELOG.md # 更新记录
+├── commitlint.config.js # git commit 提交规范配置
+├── index.html # 入口页面
+├── package.json
+├── .postcssrc.js # postcss 配置
+├── prettier.config.js # prettier 配置
+├── README.md # 英文 README
+├── README.zh-CN.md # 中文 README
+├── stylelint.config.js # stylelint 配置
+├── tsconfig.json # typescript 配置
+├── vite.config.ts # vite 配置
+└── uno.config.ts # unocss 配置
+

浏览器支持

本地开发推荐使用Chrome 最新版浏览器。

由于 Vue 3 不再支持 IE11,本项目也不支持 IE。

IEIE EdgeEdgeFirefoxFirefoxChromeChromeSafariSafari
not supportlast 2 versionslast 2 versionslast 2 versionslast 2 versions

IDE推荐

+ + + + \ No newline at end of file diff --git a/guide/mock.html b/guide/mock.html new file mode 100644 index 00000000..97c61820 --- /dev/null +++ b/guide/mock.html @@ -0,0 +1,78 @@ + + + + + + 数据mock&联调 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

数据mock&联调

开发环境

如果前端应用和后端接口服务器没有运行在同一个主机上,你需要在开发环境下将接口请求代理到接口服务器。

如果是同一个主机,可以直接请求具体的接口地址。

跨域设置

vite.config.ts 配置文件中,提供了 server 的 proxy 功能,用于代理 API 请求。

server: {
+  proxy: {
+    "/api":{
+      target: 'http://localhost:3000',
+      changeOrigin: true,
+      ws: true,
+      rewrite: (path) => path.replace(new RegExp(`^/api`), ''),
+    }
+  },
+},
+

配置接口前缀,可以在对应的 env 文件中,修改 VITE_API_BASE_PATH 的值

注意

该配置只能作用于 本地开发环境。

从浏览器控制台的 Network 看,请求是 http://localhost:3000/api/xxx,这是因为 proxy 配置不会改变本地请求的 url。

接口请求

在本项目中,所有的接口数据都是使用 Mock 模拟

接口统一存放于 src/api/ 下面管理

以获取列表接口为例:

src/api/ 内新建模块文件,其中参数与返回值最好定义一下类型,方便校验。虽然麻烦,但是后续维护字段很方便。

提示

类型定义文件可以抽取出去统一管理,具体参考项目

import request from '@/axios'
+import type { TableData } from './types'
+
+export const getTableListApi = (params: any) => {
+  return request.get({ url: '/example/list', params })
+}
+
+export const getTreeTableListApi = (params: any) => {
+  return request.get({ url: '/example/treeList', params })
+}
+
+export const saveTableApi = (data: Partial<TableData>): Promise<IResponse> => {
+  return request.post({ url: '/example/save', data })
+}
+
+export const getTableDetApi = (id: string): Promise<IResponse<TableData>> => {
+  return request.get({ url: '/example/detail', params: { id } })
+}
+
+export const delTableListApi = (ids: string[] | number[]): Promise<IResponse> => {
+  return request.post({ url: '/example/delete', data: { ids } })
+}
+
+

axios 配置

axios 请求封装存放于 src/axios 中。

全局 axios 配置说明

axios 全局配置放在 src/constants 中。

注意

更改之后,将影响所有的请求。

/**
+ * 请求成功状态码
+ */
+export const SUCCESS_CODE = 0
+
+/**
+ * 请求contentType
+ */
+export const CONTENT_TYPE = 'application/json'
+
+/**
+ * 请求超时时间
+ */
+export const REQUEST_TIMEOUT = 60000
+

Mock 服务

Mock 数据是前端开发过程中必不可少的一环,是分离前后端开发的关键链路。通过预先跟服务器端约定好的接口,模拟请求数据甚至逻辑,能够让前端开发独立自主,不会被服务端的开发进程所阻塞。

本项目使用 vite-mock-plugin 来进行 mock 数据处理。项目内 mock 服务分本地和线上

本地 Mock

本地 mock 采用 Node.js 中间件进行参数拦截(不采用 mock.js 的原因是本地开发看不到请求参数和响应结果)。

如何新增 mock 接口

如果你想添加 mock 数据,只要在根目录下找到 mock 文件,添加对应的接口,对其进行拦截和模拟数据。

在 mock 文件夹内新建文件

TIP

文件新增后会自动更新,不需要手动重启,可以在代码控制台查看日志信息 mock 文件夹内会自动注册

TIP

mock 的值可以直接使用 mock.js 的语法。

接口有了,如何去掉 mock

可以在对应的 env 文件中设置 VITE_USE_MOCKfalse ,如果想要更彻底一点,可以在vite.config.ts中删除 viteMockServe 对应的代码。

线上 mock

由于该项目是一个展示类项目,线上也是用 mock 数据,所以在打包后同时也集成了 mock。通常项目线上一般为正式接口。

项目线上 mock 采用的是 mock.js 进行 mock 数据模拟。

+ + + + \ No newline at end of file diff --git a/guide/router.html b/guide/router.html new file mode 100644 index 00000000..268d3385 --- /dev/null +++ b/guide/router.html @@ -0,0 +1,179 @@ + + + + + + 路由 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

路由

项目路由配置存放于 src/router/index.ts 中。

为了方便阅读和查找,目前项目中并没有去对路由进行拆分,而是统一写在了一起,如果需要拆分,可自行更改。

因为路由是生成菜单关键,所以本项目中对路由提供了以下配置,方便开发者进行定制。

配置

/**
+* redirect: noredirect        当设置 noredirect 的时候该路由在面包屑导航中不可被点击
+* name:'router-name'          设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
+* meta : {
+    hidden: true              当设置 true 的时候该路由不会再侧边栏出现 如404,login等页面(默认 false)
+
+    alwaysShow: true          当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式,
+                              只有一个时,会将那个子路由当做根路由显示在侧边栏,
+                              若你想不管路由下面的 children 声明的个数都显示你的根路由,
+                              你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,
+                              一直显示根路由(默认 false)
+
+    title: 'title'            设置该路由在侧边栏和面包屑中展示的名字
+
+    icon: 'svg-name'          设置该路由的图标
+
+    noCache: true             如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
+
+    breadcrumb: false         如果设置为false,则不会在breadcrumb面包屑中显示(默认 true)
+
+    affix: true               如果设置为true,则会一直固定在tag项中(默认 false)
+
+    noTagsView: true          如果设置为true,则不会出现在tag中(默认 false)
+
+    activeMenu: '/dashboard'  显示高亮的路由路径
+
+    canTo: true               设置为true即使hidden为true,也依然可以进行路由跳转(默认 false)
+
+    permission: ['edit','add', 'delete']    设置该路由的权限
+  }
+**/
+

如何添加新配置

如果本项目中的路由配置项,满足不了你当前的开发工作,可以自行添加新的属性。

注意

所有的路由项配置,都必须放在 meta 中。

types/router.d.ts 中添加对应的类型,之后就可以在路由中添加你需要的配置项了。

declare module 'vue-router' {
+  interface RouteMeta extends Record<string | number | symbol, unknown> {
+    hidden?: boolean
+    alwaysShow?: boolean
+    title?: string
+    icon?: string
+    noCache?: boolean
+    breadcrumb?: boolean
+    affix?: boolean
+    activeMenu?: string
+    noTagsView?: boolean
+    canTo?: boolean
+    permission?: string[]
+
+    // 添加新的配置类型
+    ...
+  }
+}
+
+

多级路由

注意事项

  • 整个项目所有路由 name 不能重复
  • 所有的多级路由最终都会转成二级路由,所以不能内嵌子路由
  • 除了 layout 对应的 path 前面需要加 /,其余子路由都不要以/开头

示例

{
+  path: '/level',
+  component: Layout,
+  redirect: '/level/menu1/menu1-1/menu1-1-1',
+  name: 'Level',
+  meta: {
+    title: t('router.level'),
+    icon: 'carbon:skill-level-advanced'
+  },
+  children: [
+    {
+      path: 'menu1',
+      name: 'Menu1',
+      component: getParentLayout(),
+      redirect: '/level/menu1/menu1-1/menu1-1-1',
+      meta: {
+        title: t('router.menu1')
+      },
+      children: [
+        {
+          path: 'menu1-1',
+          name: 'Menu11',
+          component: getParentLayout(),
+          redirect: '/level/menu1/menu1-1/menu1-1-1',
+          meta: {
+            title: t('router.menu11'),
+            alwaysShow: true
+          },
+          children: [
+            {
+              path: 'menu1-1-1',
+              name: 'Menu111',
+              component: () => import('@/views/Level/Menu111.vue'),
+              meta: {
+                title: t('router.menu111')
+              }
+            }
+          ]
+        },
+        {
+          path: 'menu1-2',
+          name: 'Menu12',
+          component: () => import('@/views/Level/Menu12.vue'),
+          meta: {
+            title: t('router.menu12')
+          }
+        }
+      ]
+    },
+    {
+      path: 'menu2',
+      name: 'Menu2Demo',
+      component: () => import('@/views/Level/Menu2.vue'),
+      meta: {
+        title: t('router.menu2')
+      }
+    }
+  ]
+}
+
+

外链

只需要将 path 设置为需要跳转的HTTP 地址即可。

{
+  path: '/external-link',
+  component: Layout,
+  meta: {
+    name: 'ExternalLink'
+  },
+  children: [
+    {
+      path: 'https://github.com/kailong321200875/vue-element-plus-admin-doc',
+      meta: { name: 'Link', title: '文档' }
+    }
+  ]
+}
+

图标

这里的 icon 配置,会同步到 菜单(icon 的值可以查看此处)。

多标签页

标签页使用的是 keep-aliverouter-view 实现,实现切换 tab 后还能保存切换之前的状态。

如何开启页面缓存

开启缓存有 2 个条件

  1. 路由设置 name,且不能重复
  2. 路由对应的组件加上 name,与路由设置的 name 保持一致
{
+  path: 'menu2',
+  name: 'Menu2',
+  component: () => import('@/views/Level/Menu2.vue'),
+  meta: {
+    title: t('router.menu2')
+  }
+}
+
+// /@/views/Level/Menu2.vue
+<script setup lang="ts">
+defineOptions({
+  name: 'Menu2'
+})
+</script>
+
+

注意

keep-alive 生效的前提是:需要将路由的 name 属性及对应的页面的 name 设置成一样。因为:

include - 字符串或正则表达式,只有名称匹配的组件会被缓存

如何让某个页面不缓存

可以将 noCache 配置成 true 即可关闭缓存或者组件不添加 name 属性。

{
+  path: 'workplace',
+  component: () => import('@/views/Dashboard/Workplace.vue'),
+  name: 'Workplace',
+  meta: {
+    title: t('router.workplace'),
+    noCache: true
+  }
+}
+

默认跳转地址

目前项目中,登录进来,默认是进入到当前第一个能找到的路由页面。

后续会考虑弄成一个配置项出来。

+ + + + \ No newline at end of file diff --git a/guide/settings.html b/guide/settings.html new file mode 100644 index 00000000..2b5a96f5 --- /dev/null +++ b/guide/settings.html @@ -0,0 +1,222 @@ + + + + + + 项目配置项 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

项目配置项

本文将介绍一些常用的项目配置,方便开发者可以根据需求进行定制化改造。

环境变量配置

项目的环境变量配置位于项目根目录下的,这里主要配置四个个环境变量,分别为:

在开发调试的时候,会读取 .env.base 里面的数据。其他环境亦是如此,根据打包命令的不同,来读取不同的环境变量。

也许你会疑惑,为什么会有多个环境变量?

生产环境 为例,当我们执行 pnpm run build:pro 时,输出的包是用于线上环境的,所以代码都应该是压缩,我们需要删除掉代码中的 console.logdegubber,保证打包后代码的整洁度和不可见性。而其他环境,所以应该保留 console.logdegubber 用于调试,这样才能快速定位到问题所在。

所以环境变量的作用就是为了,在不同环境下有不同的表现。

提示

  • 只有以 VITE_ 开头的变量会被嵌入到项目中,你可以项目代码中这样访问它们:
console.log(import.meta.env.VITE_APP_TITLE)
+

配置项说明

.env.base

本地开发环境适用

# 环境
+NODE_ENV = development
+
+# 接口前缀
+VITE_API_BASEPATH = base
+
+# 打包路径
+VITE_BASE_PATH = /
+
+# 标题
+VITE_APP_TITLE = ElementAdmin
+

.env.dev

开发环境适用

# 环境
+NODE_ENV = production
+
+# 接口前缀
+VITE_API_BASEPATH = dev
+
+# 打包路径
+VITE_BASE_PATH = /dist-dev/
+
+# 是否删除debugger
+VITE_DROP_DEBUGGER = false
+
+# 是否删除console.log
+VITE_DROP_CONSOLE = false
+
+# 是否sourcemap
+VITE_SOURCEMAP = true
+
+# 输出路径
+VITE_OUT_DIR = dist-dev
+
+# 标题
+VITE_APP_TITLE = ElementAdmin
+
+

.env.test

测试环境适用

# 环境
+NODE_ENV = production
+
+# 接口前缀
+VITE_API_BASEPATH = test
+
+# 打包路径
+VITE_BASE_PATH = /dist-test/
+
+# 是否删除debugger
+VITE_DROP_DEBUGGER = false
+
+# 是否删除console.log
+VITE_DROP_CONSOLE = false
+
+# 是否sourcemap
+VITE_SOURCEMAP = true
+
+# 输出路径
+VITE_OUT_DIR = dist-test
+
+

.env.pro

生产环境适用

# 环境
+NODE_ENV = production
+
+# 接口前缀
+VITE_API_BASEPATH = pro
+
+# 打包路径
+VITE_BASE_PATH = /
+
+# 是否删除debugger
+VITE_DROP_DEBUGGER = true
+
+# 是否删除console.log
+VITE_DROP_CONSOLE = true
+
+# 是否sourcemap
+VITE_SOURCEMAP = false
+
+# 输出路径
+VITE_OUT_DIR = dist-pro
+
+# 标题
+VITE_APP_TITLE = ElementAdmin
+
+

项目及主题配置

提示

项目配置文件用于配置项目内展示的内容、布局、主题色等效果。

配置文件路径

src/store/modules/app.ts

说明

修改完之后,会添加到全局的状态管理中,方便其他地方使用。

export const appModules: AppState = {
+  sizeMap: ['default', 'large', 'small'],
+  mobile: false, // 是否是移动端
+  title: import.meta.env.VITE_APP_TITLE as string, // 标题
+  pageLoading: false, // 路由跳转loading
+
+  breadcrumb: true, // 面包屑
+  breadcrumbIcon: true, // 面包屑图标
+  collapse: false, // 折叠菜单
+  hamburger: true, // 折叠图标
+  screenfull: true, // 全屏图标
+  size: true, // 尺寸图标
+  locale: true, // 多语言图标
+  tagsView: true, // 标签页
+  logo: true, // logo
+  fixedHeader: true, // 固定toolheader
+  footer: true, // 显示页脚
+  greyMode: false, // 是否开始灰色模式,用于特殊悼念日
+
+  layout: wsCache.get('layout') || 'classic', // layout布局
+  isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
+  currentSize: wsCache.get('default') || 'default', // 组件尺寸
+  theme: wsCache.get('theme') || {
+    // 主题色
+    elColorPrimary: '#409eff',
+    // 左侧菜单边框颜色
+    leftMenuBorderColor: 'inherit',
+    // 左侧菜单背景颜色
+    leftMenuBgColor: '#001529',
+    // 左侧菜单浅色背景颜色
+    leftMenuBgLightColor: '#0f2438',
+    // 左侧菜单选中背景颜色
+    leftMenuBgActiveColor: 'var(--el-color-primary)',
+    // 左侧菜单收起选中背景颜色
+    leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
+    // 左侧菜单字体颜色
+    leftMenuTextColor: '#bfcbd9',
+    // 左侧菜单选中字体颜色
+    leftMenuTextActiveColor: '#fff',
+    // logo字体颜色
+    logoTitleTextColor: '#fff',
+    // logo边框颜色
+    logoBorderColor: 'inherit',
+    // 头部背景颜色
+    topHeaderBgColor: '#fff',
+    // 头部字体颜色
+    topHeaderTextColor: 'inherit',
+    // 头部悬停颜色
+    topHeaderHoverColor: '#f6f6f6',
+    // 头部边框颜色
+    topToolBorderColor: '#eee'
+  }
+}
+

如何添加新属性

如果想要添加新的全局配置属性,需要在 src/store/modules/app.tsAppState 添加对应的类型,并在 appModules 对象中,赋予新属性的默认值。

多语言配置

用于配置多语言信息

src/store/modules/locale.ts 内配置

import { useCache } from '@/hooks/web/useCache'
+import zhCn from 'element-plus/lib/locale/lang/zh-cn'
+import en from 'element-plus/lib/locale/lang/en'
+
+const { wsCache } = useCache()
+
+export const elLocaleMap = {
+  'zh-CN': zhCn,
+  en: en
+}
+export interface LocaleState {
+  currentLocale: LocaleDropdownType
+  localeMap: LocaleDropdownType[]
+}
+
+export const localeModules: LocaleState = {
+  currentLocale: {
+    lang: wsCache.get('lang') || 'zh-CN',
+    elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN']
+  },
+  // 多语言
+  localeMap: [
+    {
+      lang: 'zh-CN',
+      name: '简体中文'
+    },
+    {
+      lang: 'en',
+      name: 'English'
+    }
+  ]
+}
+
+

样式配置

css 前缀设置

用于修改项目内组件及 element-plus 组件的 class 前缀。

由于 element-plus 的组件还没有全部采用动态配置前缀,所以目前还是使用 el 前缀。

// 命名空间
+@namespace: v;
+// el命名空间
+@elNamespace: el;
+
+// 导出变量
+:export {
+  namespace: @namespace;
+  elNamespace: @elNamespace;
+}
+
+

前缀使用

在 css 内

<style lang="less" scoped>
+  /* namespace已经全局注入,不需要额外在引入 */
+  @prefix-cls: ~'@{namespace}-app';
+
+  .@{prefix-cls} {
+    width: 100%;
+  }
+</style>
+

在 vue/ts 内

import { useDesign } from '/@/hooks/web/useDesign'
+
+const { prefixCls } = useDesign('app')
+
+// prefixCls => v-app
+
+ + + + \ No newline at end of file diff --git a/guide/version.html b/guide/version.html new file mode 100644 index 00000000..5af93e76 --- /dev/null +++ b/guide/version.html @@ -0,0 +1,31 @@ + + + + + + 插件 | vue-element-plus-admin + + + + + + + + + + + + + + + + +

插件

windiCss 替换为 unocss

由于 WindiCss 不再维护,所以换成了 unocss, 两者在用法上保持了大部分的一致性,但还是有些地方有特别的差异性,对于 v1 版本需要升级到 unocss 话,需要有一定的改造成本。

所以建议 v1 还是继续使用 WindiCss

布局

v2 版本还是保留了四种布局风格,只是在细节上的把控会比 v1 好,主要体现在一些边框重叠的优化上。

typescript 类型

v2 版本升级了 typescript5,在用法上基本上没有区别,只是针对了项目中的一些类型的规范进行了更改,使项目的代码更规范化。

组件

v2 版本最主要的更新,就是组件上的更新

主要体现在了 FormTableSearchDescriptions 的重构上。

在 V1 版本中,以上四个组件在使用上有许多不足的地方,灵活度不够,扩展性不强而被诟病。

所以在 v2 版本中,以上四个组件,schema 全部采用了 tsx 的书写方式,如果定制化比较多的话,tsx 会比 template 更有优势。

同时,以上四个组件支持嵌套绑定,如 Form 的数据绑定,v1 版本只支持一层嵌套,比较局限,在 v2 版本中,支持 xxx.xxx 的绑定方式。

如果用法比较简单的话,也是支持 template ,不过这里还是推荐使用 tsx ,避免之后扩展带来的负担。

在线例子

v2 版本丰富了在线例子,如果 权限管理,后续也会继续持续更新更多的例子来让各位客官可以更快速的了解和使用。

v1 如何升级到 v2

注意

如果 v1 版本已经项目落地,或者已经使用了一段时间,建议还是继续使用 v1 版本,刚开始使用的话,可以直接使用 v2 版本

由于两个版本的不兼容,这里是不推荐进行升级。

+ + + + \ No newline at end of file diff --git a/hooks/useClipboard.html b/hooks/useClipboard.html new file mode 100644 index 00000000..648f3ca8 --- /dev/null +++ b/hooks/useClipboard.html @@ -0,0 +1,40 @@ + + + + + + useClipboard | vue-element-plus-admin + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hooks/useCrudSchemas.html b/hooks/useCrudSchemas.html new file mode 100644 index 00000000..aa198183 --- /dev/null +++ b/hooks/useCrudSchemas.html @@ -0,0 +1,154 @@ + + + + + + useCrudSchemas | vue-element-plus-admin + + + + + + + + + + + + + + + + +

useCrudSchemas

统一生成 SearchFormDescriptionsTable 组件所需要的数据结构。

由于以上四个组件都需要 Sechema 或者 columns 的字段,如果每个组件都写一遍的话,会造成大量重复代码,所以提供 useCrudSchemas 来进行统一的数据生成。

useCrudSchemas 位于 src/hooks/web/useCrudSchemas.ts

用法

TIP

如果不需要某个字段,如 formSchema 不需要 fieldindex 的字段,可以使用 form: { hidden: true } 进行过滤,其他组件同理。

Search 是基于 Form 进行二次封装的,所以 Search 支持的参数 Form 也都支持。

searchform 字段,可以传入 dictName 来获取全局的字典数据,也可以传入 api 来获取接口数据,如果使用 api ,需要主动 return 数据。

如果想看更复杂点的例子,请在线预览

<script setup lang="ts">
+import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
+
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    field: 'index',
+    label: t('tableDemo.index'),
+    type: 'index',
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    }
+  },
+  {
+    field: 'title',
+    label: t('tableDemo.title'),
+    search: {
+      show: true
+    },
+    form: {
+      colProps: {
+        span: 24
+      }
+    },
+    detail: {
+      span: 24
+    }
+  },
+  {
+    field: 'author',
+    label: t('tableDemo.author')
+  },
+  {
+    field: 'display_time',
+    label: t('tableDemo.displayTime'),
+    form: {
+      component: 'DatePicker',
+      componentProps: {
+        type: 'datetime',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss'
+      }
+    }
+  },
+  {
+    field: 'importance',
+    label: t('tableDemo.importance'),
+    formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
+      return h(
+        ElTag,
+        {
+          type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
+        },
+        () =>
+          cellValue === 1
+            ? t('tableDemo.important')
+            : cellValue === 2
+            ? t('tableDemo.good')
+            : t('tableDemo.commonly')
+      )
+    },
+    form: {
+      component: 'Select',
+      componentProps: {
+        options: [
+          {
+            label: '重要',
+            value: 3
+          },
+          {
+            label: '良好',
+            value: 2
+          },
+          {
+            label: '一般',
+            value: 1
+          }
+        ]
+      }
+    }
+  },
+  {
+    field: 'pageviews',
+    label: t('tableDemo.pageviews'),
+    form: {
+      component: 'InputNumber',
+      value: 0
+    }
+  },
+  {
+    field: 'content',
+    label: t('exampleDemo.content'),
+    table: {
+      hidden: true
+    },
+    form: {
+      component: 'Editor',
+      colProps: {
+        span: 24
+      }
+    },
+    detail: {
+      span: 24
+    }
+  },
+  {
+    field: 'action',
+    width: '260px',
+    label: t('tableDemo.action'),
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    }
+  }
+])
+
+const { allSchemas } = useCrudSchemas(crudSchemas)
+</script>
+
+

参数介绍

const { allSchemas } = useCrudSchemas(crudSchemas)
+

allSchemas

allSchemas 存放着四个组件所需要的数据结果

allSchemas.fromSchema

Form 组件的 Sechema

allSchemas.searchSchema

Search 组件的 Sechema

allSchemas.detailSchema

Descriptions 组件的 Sechema

allSchemas.tableColumns

Table 组件的 columns

CrudSchema

属性说明类型可选值默认值
search用于设置 searchSchemaCrudSearchParams--
table用于设置 tableColumnsCrudTableParams--
form用于设置 fromSchemaCrudFormParams--
detail用于设置 DescriptionsSchemaCrudDescriptionsParams--
children如果是 Table 组件,则可能会有多表头的情况存在CrudSchema[]--
+ + + + \ No newline at end of file diff --git a/hooks/useNetwork.html b/hooks/useNetwork.html new file mode 100644 index 00000000..7557dff6 --- /dev/null +++ b/hooks/useNetwork.html @@ -0,0 +1,40 @@ + + + + + + useNetwork | vue-element-plus-admin + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hooks/useStorage.html b/hooks/useStorage.html new file mode 100644 index 00000000..0d974d15 --- /dev/null +++ b/hooks/useStorage.html @@ -0,0 +1,46 @@ + + + + + + useStorage(2.1.0+) | vue-element-plus-admin + + + + + + + + + + + + + + + + +

useStorage(2.1.0+)

用于操作 localStorage 和 sessionStorage

useStorage 位于 src/hooks/web/useStorage.ts

默认使用 sessionStorage,如需要使用 localStorage ,只需要传入 localStorage 即可,如:useStorage('localStorage')

支持非字符串类型存取值

用法

<script setup lang="ts">
+import { useStorage } from '@/hooks/web/useStorage'
+
+const { setStorage, getStorage, removeStorage, clear } = useStorage()
+
+setStorage('key', { name: 'Jok' })
+
+getStorage('key')
+
+removeStorage('key')
+
+clear()
+</script>
+
+

参数介绍

const { setStorage, getStorage, removeStorage, clear } = useStorage('localStorage')
+

setStorage

setStorage 存储数据

getStorage

getStorage 获取某个存储数据

removeStorage

removeStorage 清除某个存储数据

clear

clear 清除所有缓存数据,如果需要排除某些数据,可以传入 excludes 来排除,如:clear(['key']),这样 key 就不会被清除

+ + + + \ No newline at end of file diff --git a/hooks/useTagsView.html b/hooks/useTagsView.html new file mode 100644 index 00000000..1713401d --- /dev/null +++ b/hooks/useTagsView.html @@ -0,0 +1,96 @@ + + + + + + useTagsView(2.1.0+) | vue-element-plus-admin + + + + + + + + + + + + + + + + +

useTagsView(2.1.0+)

操作标签页

useTagsView 位于 src/hooks/web/useTagsView.ts

用法

<script setup lang="ts">
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { ElButton } from 'element-plus'
+import { useTagsView } from '@/hooks/web/useTagsView'
+import { useRouter } from 'vue-router'
+
+const { push } = useRouter()
+
+const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage, setTitle } =
+  useTagsView()
+
+const closeAllTabs = () => {
+  closeAll(() => {
+    push('/dashboard/analysis')
+  })
+}
+
+const closeLeftTabs = () => {
+  closeLeft()
+}
+
+const closeRightTabs = () => {
+  closeRight()
+}
+
+const closeOtherTabs = () => {
+  closeOther()
+}
+
+const refresh = () => {
+  refreshPage()
+}
+
+const closeCurrentTab = () => {
+  closeCurrent(undefined, () => {
+    push('/dashboard/analysis')
+  })
+}
+
+const setTabTitle = () => {
+  setTitle(new Date().getTime().toString())
+}
+
+const setAnalysisTitle = () => {
+  setTitle(`分析页-${new Date().getTime().toString()}`, '/dashboard/analysis')
+}
+</script>
+
+<template>
+  <ContentWrap title="useTagsView">
+    <ElButton type="primary" @click="closeAllTabs"> 关闭所有标签页 </ElButton>
+    <ElButton type="primary" @click="closeLeftTabs"> 关闭左侧标签页 </ElButton>
+    <ElButton type="primary" @click="closeRightTabs"> 关闭右侧标签页 </ElButton>
+    <ElButton type="primary" @click="closeOtherTabs"> 关闭其他标签页 </ElButton>
+    <ElButton type="primary" @click="closeCurrentTab"> 关闭当前标签页 </ElButton>
+    <ElButton type="primary" @click="refresh"> 刷新当前标签页 </ElButton>
+    <ElButton type="primary" @click="setTabTitle"> 修改当前标题 </ElButton>
+    <ElButton type="primary" @click="setAnalysisTitle"> 修改分析页标题 </ElButton>
+  </ContentWrap>
+</template>
+
+</script>
+
+

参数介绍

const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage, setTitle } = useTagsView()
+

closeAll

closeAll 用于关闭所有标签页

closeLeft

closeLeft 用于关闭当前左侧标签页

closeRight

closeRight 用于关闭当前右侧标签页

closeOther

closeOther 用于关闭除当前标签页外的所有标签页

closeCurrent

closeCurrent 用于关闭除当前标签页

refreshPage

refreshPage 用于刷新当前标签页

setTitle

setTitle(title: string, path: string) 用于设置某个标签页的标签,接收 标题和一个完整的path路径

+ + + + \ No newline at end of file diff --git a/hooks/useWatermark.html b/hooks/useWatermark.html new file mode 100644 index 00000000..633154f5 --- /dev/null +++ b/hooks/useWatermark.html @@ -0,0 +1,47 @@ + + + + + + useWatermark | vue-element-plus-admin + + + + + + + + + + + + + + + + +

useWatermark

为元素设置水印

useWatermark 位于 src/hooks/web/useWatermark.ts

用法

<script setup lang="ts">
+import { useWatermark } from '@/hooks/web/useWatermark'
+import { onBeforeUnmount } from 'vue'
+
+const { setWatermark, clear } = useWatermark()
+
+const { t } = useI18n()
+
+setWatermark('ElementPlusAdmin')
+
+onBeforeUnmount(() => {
+  clear()
+})
+</script>
+
+

参数介绍

const { setWatermark, clear } = useWatermark()
+

setWatermark

setWatermark 用于设置水印文案,接收一个 string 类型的参数

clear

clear 用于清除水印

+ + + + \ No newline at end of file diff --git a/images/i18n.png b/images/i18n.png new file mode 100644 index 00000000..72e6d3ad Binary files /dev/null and b/images/i18n.png differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..8f007155 --- /dev/null +++ b/index.html @@ -0,0 +1,31 @@ + + + + + + vue-element-plus-admin + + + + + + + + + + + + + + + + +

vue-element-plus-admin

一套基于vue3、element-plus、typesScript、vite的后台集成方案

最新技术栈

基于Vue3、Vite、TypeScript等最新技术栈开发

组件封装

对常用功能进行组件化封装,统一维护,满足基础工作需求

丰富的示例

常见的Web端插件示例实现

主题配置

丰富的主题配置及黑暗主题适配

权限管理

完善的前后端权限管理方案

持续更新

持续关注最新的技术方向,保证第一时间更新

MIT Licensed | Copyright © 2021-present Archer

+ + + + \ No newline at end of file diff --git a/logo.png b/logo.png new file mode 100644 index 00000000..7e5002b3 Binary files /dev/null and b/logo.png differ