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..1ad2fd0e --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +doc.vvbin.cn diff --git a/README.html b/README.html new file mode 100644 index 00000000..1aa07c2d --- /dev/null +++ b/README.html @@ -0,0 +1,39 @@ + + + + + + vue-vben-admin-doc | Vben Admin + + + + + + + + + + + + + + + + +

vue-vben-admin-doc

如何本地开发

# 克隆本仓库
+$ git clone git@github.com:vbenjs/vue-vben-admin-doc.git
+
+# 或者使用 yarn
+$ yarn install
+
+# 启动开发服务器
+$ yarn dev
+
+ + + + \ No newline at end of file diff --git a/assets/Home.00e49e13.js b/assets/Home.00e49e13.js new file mode 100644 index 00000000..9069641b --- /dev/null +++ b/assets/Home.00e49e13.js @@ -0,0 +1 @@ +import{e,u as a,f as t,g as s,h as l,o as i,c as o,b as r,i 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.8cddb23b.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"},b={class:"container"},A={class:"features"},L={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",b,[r("div",A,[(i(!0),o(d,null,m(l(u),((e,a)=>(i(),o("section",{key:a,class:"feature"},[e.title?(i(),o("h2",L,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.b7eec73c.js b/assets/README.md.b7eec73c.js new file mode 100644 index 00000000..3a8e4a91 --- /dev/null +++ b/assets/README.md.b7eec73c.js @@ -0,0 +1 @@ +import{o as n,c as a,a as e}from"./app.8cddb23b.js";const s='{"title":"vue-vben-admin-doc","description":"","frontmatter":{},"headers":[{"level":2,"title":"如何本地开发","slug":"如何本地开发"}],"relativePath":"README.md","lastUpdated":1697592578589}',t={},o=e('

vue-vben-admin-doc

如何本地开发

# 克隆本仓库\n$ git clone git@github.com:vbenjs/vue-vben-admin-doc.git\n\n# 或者使用 yarn\n$ yarn install\n\n# 启动开发服务器\n$ yarn dev\n
',3);t.render=function(e,s,t,c,d,i){return n(),a("div",null,[o])};export default t;export{s as __pageData}; diff --git a/assets/README.md.b7eec73c.lean.js b/assets/README.md.b7eec73c.lean.js new file mode 100644 index 00000000..c22695af --- /dev/null +++ b/assets/README.md.b7eec73c.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as e}from"./app.8cddb23b.js";const s='{"title":"vue-vben-admin-doc","description":"","frontmatter":{},"headers":[{"level":2,"title":"如何本地开发","slug":"如何本地开发"}],"relativePath":"README.md","lastUpdated":1697592578589}',t={},o=e('',3);t.render=function(e,s,t,c,d,i){return n(),a("div",null,[o])};export default t;export{s as __pageData}; diff --git a/assets/app.8cddb23b.js b/assets/app.8cddb23b.js new file mode 100644 index 00000000..2975d210 --- /dev/null +++ b/assets/app.8cddb23b.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 m(e){let t="";if(A(e))t=e;else if(T(e))for(let n=0;nnull==e?"":R(e)?JSON.stringify(e,b,2):String(e),b=(e,t)=>P(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:M(t)?{[`Set(${t.size})`]:[...t.values()]}:!R(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,P=e=>"[object Map]"===U(e),M=e=>"[object Set]"===U(e),j=e=>"function"==typeof e,A=e=>"string"==typeof e,I=e=>"symbol"==typeof e,R=e=>null!==e&&"object"==typeof e,F=e=>R(e)&&j(e.then)&&j(e.catch),N=Object.prototype.toString,U=e=>N.call(e),V=e=>"[object Object]"===U(e),B=e=>A(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))},H=/-(\w)/g,W=D((e=>e.replace(H,((e,t)=>t?t.toUpperCase():"")))),q=/\B([A-Z])/g,K=D((e=>e.replace(q,"-$1").toLowerCase())),G=D((e=>e.charAt(0).toUpperCase()+e.slice(1))),J=D((e=>e?`on${G(e)}`:"")),Y=(e,t)=>e!==t&&(e==e||t==t),Q=(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 t.scheduler?void 0: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)),P(e)&&c(s.get(re)));break;case"delete":T(e)||(c(s.get(oe)),P(e)&&c(s.get(re)));break;case"set":P(e)&&c(s.get(oe))}i.forEach((e=>{e.options.scheduler?e.options.scheduler(e):e()}))}const ve=a("__proto__,__v_isRef,__isVue"),me=new Set(Object.getOwnPropertyNames(Symbol).map((e=>Symbol[e])).filter(I)),ge=ke(),be=ke(!1,!0),ye=ke(!0),xe=ke(!0,!0),_e={};function ke(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?Xe:Qe:t?Ye:Je).get(n))return n;const l=T(n);if(!e&&l&&L(_e,o))return Reflect.get(_e,o,r);const s=Reflect.get(n,o,r);if(I(o)?me.has(o):ve(o))return s;if(e||fe(n,0,o),t)return s;if(at(s)){return!l||!B(o)?s.value:s}return R(s)?e?tt(s):et(s):s}}["includes","indexOf","lastIndexOf"].forEach((e=>{const t=Array.prototype[e];_e[e]=function(...e){const n=st(this);for(let t=0,r=this.length;t{const t=Array.prototype[e];_e[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=>R(e)?et(e):e,Oe=e=>R(e)?tt(e):e,Le=e=>e,Te=e=>Reflect.getPrototypeOf(e);function Pe(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 0}function Me(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 je(e,t=!1){return e=e.__v_raw,!t&&fe(st(e),0,oe),Reflect.get(e,"size",e)}function Ae(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 Ie(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 Re(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=P(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 Pe(this,e)},get size(){return je(this)},has:Me,add:Ae,set:Ie,delete:Re,clear:Fe,forEach:Ne(!1,!1)},ze={get(e){return Pe(this,e,!1,!0)},get size(){return je(this)},has:Me,add:Ae,set:Ie,delete:Re,clear:Fe,forEach:Ne(!1,!0)},De={get(e){return Pe(this,e,!0)},get size(){return je(this,!0)},has(e){return Me.call(this,e,!0)},add:Ve("add"),set:Ve("set"),delete:Ve("delete"),clear:Ve("clear"),forEach:Ne(!0,!1)},He={get(e){return Pe(this,e,!0,!0)},get size(){return je(this,!0)},has(e){return Me.call(this,e,!0)},add:Ve("add"),set:Ve("set"),delete:Ve("delete"),clear:Ve("clear"),forEach:Ne(!0,!0)};function We(e,t){const n=t?e?He: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),He[e]=Ue(e,!0,!0)}));const qe={get:We(!1,!1)},Ke={get:We(!1,!0)},Ge={get:We(!0,!1)},Je=new WeakMap,Ye=new WeakMap,Qe=new WeakMap,Xe=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,Ge,Qe)}function nt(e,t,n,o,r){if(!R(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 X(e,"__v_skip",!0),e}const ct=e=>R(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=!1){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 vt(e){const t=T(e)?new Array(e.length):{};for(const n in e)t[n]=gt(e,n);return t}class mt{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 mt(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){_t(l,t,n)}return r}function xt(e,t,n,o){if(j(e)){const r=yt(e,t,n,o);return r&&F(r)&&r.catch((e=>{_t(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(){kt||wt||(wt=!0,jt=Mt.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(At=t,Et=[...new Set($t)],$t.length=0,Ot=0;OtBt(e)-Bt(t))),Pt=0;Ptnull==e.id?1/0:e.id;function zt(e){wt=!1,kt=!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(W(t))];!c&&l&&(c=o[i=J(K(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={})[i]=!0;xt(a,e,6,r)}}function Ht(e,t,n=!1){if(!t.deopt&&void 0!==e.__emits)return e.__emits;const o=e.emits;let r={},l=!1;if(!j(e)){const o=e=>{const n=Ht(e,t,!0);n&&(l=!0,$(r,n))};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}return o||l?(T(o)?o.forEach((e=>r[e]=null)):$(r,o),e.__emits=r):e.__emits=null}function Wt(e,t){return!(!e||!C(t))&&(t=t.slice(2).replace(/Once$/,""),L(e,t[0].toLowerCase()+t.slice(1))||L(e,K(t))||L(e,t))}let qt=0;const Kt=e=>qt+=e;function Gt(e,t,n={},o,r){let l=e[t];qt++,mo();const s=l&&Jt(l(n)),i=bo(ao,{key:n.key||`_${t}`},s||(o?o():[]),s&&1===e._?64:-2);return!r&&i.scopeId&&(i.slotScopeIds=[i.scopeId+"-s"]),qt--,i}function Jt(e){return e.some((e=>!yo(e)||e.type!==po&&!(e.type===ao&&!Jt(e.children))))?e:null}let Yt=null,Qt=null;function Xt(e){const t=Yt;return Yt=e,Qt=e&&e.type.__scopeId||null,t}function Zt(e){Qt=e}function en(){Qt=null}const tn=e=>nn;function nn(e,t=Yt){if(!t)return e;const n=(...n)=>{qt||mo(!0);const o=Xt(t),r=e(...n);return Xt(o),qt||go(),r};return n._c=!0,n}function on(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}=e;let v;const m=Xt(e);try{let e;if(4&n.shapeFlag){const t=r||o;v=Lo(u.call(t,t,d,l,f,p,h)),e=c}else{const n=t;0,v=Lo(n.length>1?n(l,{attrs:c,slots:i,emit:a}):n(l,null)),e=t.props?c:ln(c)}let m=v;if(!1!==t.inheritAttrs&&e){const t=Object.keys(e),{shapeFlag:n}=m;t.length&&(1&n||6&n)&&(s&&t.some(S)&&(e=sn(e,s)),m=So(m,e))}n.dirs&&(m.dirs=m.dirs?m.dirs.concat(n.dirs):n.dirs),n.transition&&(m.transition=n.transition),v=m}catch(g){ho.length=0,_t(g,e,1),v=Co(po)}return Xt(m),v}function rn(e){let t;for(let n=0;n{let t;for(const n in e)("class"===n||"style"===n||C(n))&&((t||(t={}))[n]=e[n]);return t},sn=(e,t)=>{const n={};for(const o in e)S(o)&&o.slice(9)in t||(n[o]=e[o]);return n};function cn(e,t,n){const o=Object.keys(t);if(o.length!==Object.keys(e).length)return!0;for(let r=0;r{s=!0;const[n,o]=hn(e,t,!0);$(r,n),o&&l.push(...o)};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}if(!o&&!s)return e.__props=x;if(T(o))for(let i=0;i-1,n[1]=o<0||t-1||L(n,"default"))&&l.push(e)}}}return e.__props=[r,l]}function vn(e){return"$"!==e[0]}function mn(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:""}function gn(e,t){return mn(e)===mn(t)}function bn(e,t){return T(t)?t.findIndex((t=>gn(t,e))):j(t)&&gn(t,e)?0:-1}function yn(e,t,n=Go,o=!1){if(n){const r=n[e]||(n[e]=[]),l=t.__weh||(t.__weh=(...o)=>{if(n.isUnmounted)return;de(),Yo(n);const r=xt(t,n,e,o);return Yo(null),pe(),r});return o?r.unshift(l):r.push(l),l}}const xn=e=>(t,n=Go)=>!Xo&&yn(e,t,n),_n=xn("bm"),kn=xn("m"),wn=xn("bu"),Cn=xn("u"),Sn=xn("bum"),$n=xn("um"),En=xn("rtg"),On=xn("rtc");const Ln={};function Tn(e,t,n){return Pn(e,t,n)}function Pn(e,t,{immediate:n,deep:o,flush:r,onTrack:l,onTrigger:s}=y,i=Go){let c,a,u=!1;if(at(e)?(c=()=>e.value,u=!!e._shallow):ot(e)?(c=()=>e,o=!0):c=T(e)?()=>e.map((e=>at(e)?e.value:ot(e)?jn(e):j(e)?yt(e,i,2,[i&&i.proxy]):void 0)):j(e)?t?()=>yt(e,i,2,[i&&i.proxy]):()=>{if(!i||!i.isUnmounted)return a&&a(),xt(e,i,3,[d])}:_,t&&o){const e=c;c=()=>jn(e())}let d=e=>{a=v.options.onStop=()=>{yt(e,i,4)}},p=T(e)?[]:Ln;const f=()=>{if(v.active)if(t){const e=v();(o||u||Y(e,p))&&(a&&a(),xt(t,i,3,[e,p===Ln?void 0:p,d]),p=e)}else v()};let h;f.allowRecurse=!!t,h="sync"===r?f:"post"===r?()=>to(f,i&&i.suspense):()=>{!i||i.isMounted?function(e){Nt(e,Et,$t,Ot)}(f):f()};const v=le(c,{lazy:!0,onTrack:l,onTrigger:s,scheduler:h});return tr(v,i),t?n?f():p=v():"post"===r?to(v,i&&i.suspense):v(),()=>{se(v),i&&E(i.effects,v)}}function Mn(e,t,n){const o=this.proxy;return Pn(A(e)?()=>o[e]:e.bind(o),t.bind(o),n,this)}function jn(e,t=new Set){if(!R(e)||t.has(e))return e;if(t.add(e),at(e))jn(e.value,t);else if(T(e))for(let n=0;n{jn(e,t)}));else for(const n in e)jn(e[n],t);return e}const An=e=>e.type.__isKeepAlive;function In(e,t,n=Go){const o=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}e()});if(yn(t,o,n),n){let e=n.parent;for(;e&&e.parent;)An(e.parent.vnode)&&Rn(o,t,n,e),e=e.parent}}function Rn(e,t,n,o){const r=yn(t,e,o,!0);$n((()=>{E(o[t],r)}),n)}const Fn=e=>"_"===e[0]||"$stable"===e,Nn=e=>T(e)?e.map(Lo):[Lo(e)],Un=(e,t,n)=>nn((e=>Nn(t(e))),n),Vn=(e,t)=>{const n=e._ctx;for(const o in e){if(Fn(o))continue;const r=e[o];if(j(r))t[o]=Un(0,r,n);else if(null!=r){const e=Nn(r);t[o]=()=>e}}},Bn=(e,t)=>{const n=Nn(t);e.slots.default=()=>n};function zn(e,t){if(null===Yt)return e;const n=Yt.proxy,o=e.dirs||(e.dirs=[]);for(let r=0;r(l.has(e)||(e&&j(e.install)?(l.add(e),e.install(i,...t)):j(e)&&(l.add(e),e(i,...t))),i),mixin:e=>(r.mixins.includes(e)||(r.mixins.push(e),(e.props||e.emits)&&(r.deopt=!0)),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=Co(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 Kn=!1;const Gn=e=>/svg/.test(e.namespaceURI)&&"foreignObject"!==e.tagName,Jn=e=>8===e.nodeType;function Yn(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,v=!1)=>{const m=Jn(n)&&"["===n.data,g=()=>f(n,o,s,i,c,m),{type:b,ref:y,shapeFlag:x}=o,_=n.nodeType;o.el=n;let k=null;switch(b){case uo:3!==_?k=g():(n.data!==o.children&&(Kn=!0,n.data=o.children),k=r(n));break;case po:k=8!==_||m?g():r(n);break;case fo:if(1===_){k=n;const e=!o.children.length;for(let t=0;t{t(o,e,null,s,i,Gn(e),v)},u=o.type.__asyncLoader;u?u().then(a):a(),k=m?h(n):r(n)}else 64&x?k=8!==_?g():o.type.hydrate(n,o,s,i,c,v,e,d):128&x&&(k=o.type.hydrate(n,o,s,i,Gn(l(n)),c,v,e,a))}return null!=y&&no(y,null,i,o),k},u=(e,t,n,r,l,i)=>{i=i||!!t.dynamicChildren;const{props:c,patchFlag:a,shapeFlag:u,dirs:p}=t;if(-1!==a){if(p&&Dn(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)&&ro(f,n,t),p&&Dn(t,null,n,"beforeMount"),((f=c&&c.onVnodeMounted)||p)&&un((()=>{f&&ro(f,n,t),p&&Dn(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;){Kn=!0;const e=o;o=o.nextSibling,s(e)}}else 8&u&&e.textContent!==t.children&&(Kn=!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&&Jn(f)&&"]"===f.data?r(t.anchor=f):(Kn=!0,i(t.anchor=c("]"),p,f),f)},f=(e,t,o,i,c,a)=>{if(Kn=!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,Gn(d),c),u},h=e=>{let t=0;for(;e;)if((e=r(e))&&Jn(e)&&("["===e.data&&t++,"]"===e.data)){if(0===t)return r(e);t--}return e};return[(e,t)=>{Kn=!1,a(t.firstChild,e,null,null,null),Vt(),Kn&&console.error("Hydration completed but contains mismatches.")},a]}function Qn(e){return j(e)?{setup:e,name:e.name}:e}function Xn(e){j(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 Qn({__asyncLoader:d,name:"AsyncComponentWrapper",setup(){const e=Go;if(c)return()=>Zn(c,e);const t=t=>{a=null,_t(t,e,13,!o)};if(s&&e.suspense)return d().then((t=>()=>Zn(t,e))).catch((e=>(t(e),()=>o?Co(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})).catch((e=>{t(e),u.value=e})),()=>i.value&&c?Zn(c,e):u.value&&o?Co(o,{error:u.value}):n&&!p.value?Co(n):void 0}})}function Zn(e,{vnode:{ref:t,props:n,children:o}}){const r=Co(e,n,o);return r.ref=t,r}const eo={scheduler:Rt,allowRecurse:!0},to=un,no=(e,t,n,o)=>{if(T(e))return void e.forEach(((e,r)=>no(e,t&&(T(t)?t[r]:t),n,o)));let r;if(o){if(o.type.__asyncLoader)return;r=4&o.shapeFlag?o.component.exposed||o.component.proxy:o.el}else r=null;const{i:l,r:s}=e,i=t&&t.r,c=l.refs===y?l.refs={}:l.refs,a=l.setupState;if(null!=i&&i!==s&&(A(i)?(c[i]=null,L(a,i)&&(a[i]=null)):at(i)&&(i.value=null)),A(s)){const e=()=>{c[s]=r,L(a,s)&&(a[s]=r)};r?(e.id=-1,to(e,n)):e()}else if(at(s)){const e=()=>{s.value=r};r?(e.id=-1,to(e,n)):e()}else j(s)&&yt(s,l,12,[r,c])};function oo(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=_,cloneNode:h,insertStaticContent:v}=e,m=(e,t,n,o=null,r=null,l=null,s=!1,i=null,c=!1)=>{e&&!xo(e,t)&&(o=te(e),G(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 uo:g(e,t,n,o);break;case po:b(e,t,n,o);break;case fo:null==e&&k(t,n,o,s);break;case ao:A(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?I(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&&no(u,e&&e.ref,l,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},k=(e,t,n,o)=>{[e.el,e.anchor]=v(e.children,t,n,o)},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):P(e,t,r,l,s,i,c)},E=(e,t,o,l,i,c,a,d)=>{let p,f;const{type:v,props:m,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,m&&m.is,m),8&g?u(p,e.children):16&g&&T(e.children,p,null,l,i,c&&"foreignObject"!==v,a,d||!!e.dynamicChildren),x&&Dn(e,null,l,"created"),m){for(const t in m)z(t)||r(p,t,null,m[t],c,e.children,l,i,ee);(f=m.onVnodeBeforeMount)&&ro(f,l,e)}O(p,e,e.scopeId,a,l)}x&&Dn(e,null,l,"beforeMount");const _=(!i||i&&!i.pendingBranch)&&b&&!b.persisted;_&&b.beforeEnter(p),n(p,t,o),((f=m&&m.onVnodeMounted)||_||x)&&to((()=>{f&&ro(f,l,e),_&&b.enter(p),x&&Dn(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,v=t.props||y;let m;if((m=v.onVnodeBeforeUpdate)&&ro(m,n,t,e),f&&Dn(t,e,n,"beforeUpdate"),d>0){if(16&d)j(a,t,h,v,n,o,s);else if(2&d&&h.class!==v.class&&r(a,"class",null,v.class,s),4&d&&r(a,"style",h.style,v.style,s),8&d){const i=t.dynamicProps;for(let t=0;t{m&&ro(m,n,t,e),f&&Dn(t,e,n,"updated")}),o)},M=(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)}},A=(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:v}=t;f>0&&(u=!0),v&&(a=a?a.concat(v):v),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?(M(e.dynamicChildren,h,o,l,s,c,a),(null!=t.key||l&&t===l.subTree)&&lo(e,t,!0)):B(e,t,o,p,l,s,c,a,u)},I=(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):R(t,n,o,r,l,s,c):N(e,t,c)},R=(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)||qo,l={uid:Ko++,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:hn(o,r),emitsOptions:Ht(o,r),emit:null,emitted:null,propsDefaults:y,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};return l.ctx={_:l},l.root=t?t.root:l,l.emit=Dt.bind(null,l),l}(e,o,r);if(An(e)&&(i.ctx.renderer=oe),function(e,t=!1){Xo=t;const{props:n,children:o}=e.vnode,r=Qo(e);dn(e,n,r,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=t,X(t,"_",n)):Vn(t,e.slots={})}else e.slots={},t&&Bn(e,t);X(e.slots,_o,1)})(e,o);const l=r?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Ho);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;Go=e,de();const r=yt(o,e,0,[e.props,n]);if(pe(),Go=null,F(r)){if(t)return r.then((t=>{Zo(e,t)})).catch((t=>{_t(t,e,0)}));e.asyncDep=r}else Zo(e,r)}else er(e)}(e,t):void 0;Xo=!1}(i),i.asyncDep){if(r&&r.registerDep(i,U),!e.el){const e=i.subTree=Co(po);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||cn(o,s,a):!!s);if(1024&c)return!0;if(16&c)return o?cn(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&&Q(o),(t=n.props&&n.props.onVnodeBeforeUpdate)&&ro(t,c,n,a);const p=on(e),f=e.subTree;e.subTree=p,m(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&&to(i,r),(t=n.props&&n.props.onVnodeUpdated)&&to((()=>{ro(t,c,n,a)}),r)}else{let s;const{el:i,props:c}=t,{bm:a,m:u,parent:d}=e;a&&Q(a),(s=c&&c.onVnodeBeforeMount)&&ro(s,d,t);const p=e.subTree=on(e);if(i&&ie?ie(t.el,p,e,r,null):(m(null,p,n,o,e,r,l),t.el=p.el),u&&to(u,r),s=c&&c.onVnodeMounted){const e=t;to((()=>{ro(s,d,e)}),r)}const{a:f}=e;f&&256&t.shapeFlag&&to(f,r),e.isMounted=!0,t=n=o=null}}),eo)},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;if(!(o||s>0)||16&s){let o;pn(e,t,r,l);for(const l in i)t&&(L(t,l)||(o=K(l))!==l&&L(t,o))||(c?!n||void 0===n[l]&&void 0===n[o]||(r[l]=fn(c,t||y,l,void 0,e)):delete r[l]);if(l!==i)for(const e in l)t&&L(t,e)||delete l[e]}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,Vn(t,r)),s=t}else t&&(Bn(e,t),s={default:1});if(l)for(const i in r)Fn(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 H(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?H(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)},H=(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?To(t[a]):Lo(t[a]);if(!xo(o,u))break;m(o,u,n,null,r,l,s,i,c),a++}for(;a<=d&&a<=p;){const o=e[d],a=t[p]=c?To(t[p]):Lo(t[p]);if(!xo(o,a))break;m(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;)G(e[a],r,l,!0),a++;else{const f=a,h=a,v=new Map;for(a=h;a<=p;a++){const e=t[a]=c?To(t[a]):Lo(t[a]);null!=e.key&&v.set(e.key,a)}let g,b=0;const y=p-h+1;let _=!1,k=0;const w=new Array(y);for(a=0;a=y){G(o,r,l,!0);continue}let u;if(null!=o.key)u=v.get(o.key);else for(g=h;g<=p;g++)if(0===w[g-h]&&xo(o,t[g])){u=g;break}void 0===u?G(o,r,l,!0):(w[u-h]=a+1,u>=k?k=u:_=!0,m(o,t[u],n,null,r,l,s,i,c),b++)}const C=_?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===ao){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)},G=(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&&no(i,null,n,null),256&u)return void t.ctx.deactivate(e);const f=1&u&&p;let h;if((h=s&&s.onVnodeBeforeUnmount)&&ro(h,t,e),6&u)Z(e.component,n,o);else{if(128&u)return void e.suspense.unmount(n,o);f&&Dn(e,null,t,"beforeUnmount"),64&u?e.type.remove(e,t,n,r,oe,o):a&&(l!==ao||d>0&&64&d)?ee(a,t,n,!1,!0):(l===ao&&(128&d||256&d)||!r&&16&u)&&ee(c,t,n),o&&J(e)}((h=s&&s.onVnodeUnmounted)||f)&&to((()=>{h&&ro(h,t,e),f&&Dn(e,null,t,"unmounted")}),n)},J=e=>{const{type:t,el:n,anchor:r,transition:l}=e;if(t===ao)return void Y(n,r);if(t===fo)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&&Q(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&&G(t._vnode,null,null,!0):m(t._vnode||null,e,t,null,null,null,n),Vt(),t._vnode=e},oe={p:m,um:G,m:q,r:J,mt:R,mc:T,pc:B,pbc:M,n:te,o:e};let re,ie;t&&([re,ie]=t(oe));return{render:ne,hydrate:re,createApp:qn(ne,re)}}(e,Yn)}function ro(e,t,n,o=null){xt(e,t,7,[n,o])}function lo(e,t,n=!1){const o=e.children,r=t.children;if(T(o)&&T(r))for(let l=0;lnull!=e?e:null,wo=({ref:e})=>null!=e?A(e)||at(e)||j(e)?{i:Yt,r:e}:e:null,Co=function(e,t=null,n=null,o=0,r=null,l=!1){e&&e!==io||(e=po);if(yo(e)){const o=So(e,t,!0);return n&&Po(o,n),o}s=e,j(s)&&"__vccOpts"in s&&(e=e.__vccOpts);var s;if(t){(lt(t)||_o in t)&&(t=$({},t));let{class:e,style:n}=t;e&&!A(e)&&(t.class=m(e)),R(n)&&(lt(n)&&!T(n)&&(n=$({},n)),t.style=p(n))}const i=A(e)?1:(e=>e.__isSuspense)(e)?128:(e=>e.__isTeleport)(e)?64:R(e)?4:j(e)?2:0,c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&ko(t),ref:t&&wo(t),scopeId:Qt,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};if(Po(c,n),128&i){const{content:e,fallback:t}=function(e){const{shapeFlag:t,children:n}=e;let o,r;return 32&t?(o=an(n.default),r=an(n.fallback)):(o=an(n),r=Lo(null)),{content:o,fallback:r}}(c);c.ssContent=e,c.ssFallback=t}!l&&vo&&(o>0||6&i)&&32!==o&&vo.push(c);return c};function So(e,t,n=!1){const{props:o,ref:r,patchFlag:l,children:s}=e,i=t?Mo(o||{},t):o;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:i,key:i&&ko(i),ref:t&&t.ref?n&&r?T(r)?r.concat(wo(t)):[r,wo(t)]:wo(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!==ao?-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&&So(e.ssContent),ssFallback:e.ssFallback&&So(e.ssFallback),el:e.el,anchor:e.anchor}}function $o(e=" ",t=0){return Co(uo,null,e,t)}function Eo(e,t){const n=Co(fo,null,e);return n.staticCount=t,n}function Oo(e="",t=!1){return t?(mo(),bo(po,null,e)):Co(po,null,e)}function Lo(e){return null==e||"boolean"==typeof e?Co(po):T(e)?Co(ao,null,e):"object"==typeof e?null===e.el?e:So(e):Co(uo,null,String(e))}function To(e){return null===e.el?e:So(e)}function Po(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&&Kt(1),Po(e,n()),n._c&&Kt(-1)))}{n=32;const o=t._;o||_o in t?3===o&&Yt&&(1024&Yt.vnode.patchFlag?(t._=2,e.patchFlag|=1024):t._=1):t._ctx=Yt}}else j(t)?(t={default:t,_ctx:Yt},n=32):(t=String(t),64&o?(n=16,t=[$o(t)]):n=8);e.children=t,e.shapeFlag|=n}function Mo(...e){const t=$({},e[0]);for(let n=1;n1)return n&&j(t)?t():t}}let Ao=!0;function Io(e,t,n=[],o=[],r=[],l=!1){const{mixins:s,extends:i,data:c,computed:a,methods:u,watch:d,provide:p,inject:f,components:h,directives:v,beforeMount:m,mounted:g,beforeUpdate:b,updated:x,activated:k,deactivated:w,beforeDestroy:C,beforeUnmount:S,destroyed:E,unmounted:O,render:L,renderTracked:P,renderTriggered:M,errorCaptured:A,expose:I}=t,F=e.proxy,N=e.ctx,U=e.appContext.mixins;if(l&&L&&e.render===_&&(e.render=L),l||(Ao=!1,Ro("beforeCreate","bc",t,e,U),Ao=!0,No(e,U,n,o,r)),i&&Io(e,i,n,o,r,!0),s&&No(e,s,n,o,r),f)if(T(f))for(let y=0;yUo(e,t,F))),c&&Uo(e,c,F)),a)for(const y in a){const e=a[y],t=or({get:j(e)?e.bind(F,F):j(e.get)?e.get.bind(F,F):_,set:!j(e)&&j(e.set)?e.set.bind(F):_});Object.defineProperty(N,y,{enumerable:!0,configurable:!0,get:()=>t.value,set:e=>t.value=e})}var V;if(d&&o.push(d),!l&&o.length&&o.forEach((e=>{for(const t in e)Vo(e[t],N,F,t)})),p&&r.push(p),!l&&r.length&&r.forEach((e=>{const t=j(e)?e.call(F):e;Reflect.ownKeys(t).forEach((e=>{!function(e,t){if(Go){let n=Go.provides;const o=Go.parent&&Go.parent.provides;o===n&&(n=Go.provides=Object.create(o)),n[e]=t}}(e,t[e])}))})),l&&(h&&$(e.components||(e.components=$({},e.type.components)),h),v&&$(e.directives||(e.directives=$({},e.type.directives)),v)),l||Ro("created","c",t,e,U),m&&_n(m.bind(F)),g&&kn(g.bind(F)),b&&wn(b.bind(F)),x&&Cn(x.bind(F)),k&&In(k.bind(F),"a",V),w&&function(e,t){In(e,"da",t)}(w.bind(F)),A&&((e,t=Go)=>{yn("ec",e,t)})(A.bind(F)),P&&On(P.bind(F)),M&&En(M.bind(F)),S&&Sn(S.bind(F)),O&&$n(O.bind(F)),T(I)&&!l)if(I.length){const t=e.exposed||(e.exposed=ht({}));I.forEach((e=>{t[e]=gt(F,e)}))}else e.exposed||(e.exposed=y)}function Ro(e,t,n,o,r){for(let l=0;l{let t=e;for(let e=0;en[o];if(A(e)){const n=t[e];j(n)&&Tn(r,n)}else if(j(e))Tn(r,e.bind(n));else if(R(e))if(T(e))e.forEach((e=>Vo(e,t,n,o)));else{const o=j(e.handler)?e.handler.bind(n):t[e.handler];j(o)&&Tn(r,o,e)}}function Bo(e,t,n){const o=n.appContext.config.optionMergeStrategies,{mixins:r,extends:l}=t;l&&Bo(e,l,n),r&&r.forEach((t=>Bo(e,t,n)));for(const s in t)o&&L(o,s)?e[s]=o[s](e[s],t[s],n.proxy,s):e[s]=t[s]}const zo=e=>e?Qo(e)?e.exposed?e.exposed:e.proxy:zo(e.parent):null,Do=$(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=>zo(e.parent),$root:e=>zo(e.root),$emit:e=>e.emit,$options:e=>function(e){const t=e.type,{__merged:n,mixins:o,extends:r}=t;if(n)return n;const l=e.appContext.mixins;if(!l.length&&!o&&!r)return t;const s={};return l.forEach((t=>Bo(s,t,e))),Bo(s,t,e),t.__merged=s}(e),$forceUpdate:e=>()=>Rt(e.update),$nextTick:e=>It.bind(e.proxy),$watch:e=>Mn.bind(e)}),Ho={get({_:e},t){const{ctx:n,setupState:o,data:r,props:l,accessCache:s,type:i,appContext:c}=e;if("__v_skip"===t)return!0;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];Ao&&(s[t]=4)}}const u=Do[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(Do,s)||L(r.config.globalProperties,s)}},Wo=$({},Ho,{get(e,t){if(t!==Symbol.unscopables)return Ho.get(e,t,e)},has:(e,t)=>"_"!==t[0]&&!u(t)}),qo=Hn();let Ko=0;let Go=null;const Jo=()=>Go||Yt,Yo=e=>{Go=e};function Qo(e){return 4&e.vnode.shapeFlag}let Xo=!1;function Zo(e,t,n){j(t)?e.render=t:R(t)&&(e.setupState=ht(t)),er(e)}function er(e,t){const n=e.type;e.render||(e.render=n.render||_,e.render._rc&&(e.withProxy=new Proxy(e.ctx,Wo))),Go=e,de(),Io(e,n),pe(),Go=null}function tr(e,t=Go){t&&(t.effects||(t.effects=[])).push(e)}function nr(e){return j(e)&&e.displayName||e.name}function or(e){const t=function(e){let t,n;return j(e)?(t=e,n=_):(t=e.get,n=e.set),new bt(t,n,j(e)||!e.set)}(e);return tr(t.effect),t}function rr(e,t,n){const o=arguments.length;return 2===o?R(t)&&!T(t)?yo(t)?Co(e,null,[t]):Co(e,t):Co(e,null,t):(o>3?n=Array.prototype.slice.call(arguments,2):3===o&&yo(n)&&(n=[n]),Co(e,t,n))}function lr(e,t){let n;if(T(e)||A(e)){n=new Array(e.length);for(let o=0,r=e.length;o{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,o)=>{const r=t?cr.createElementNS(ir,e):cr.createElement(e,n?{is:n}:void 0);return"select"===e&&o&&null!=o.multiple&&r.setAttribute("multiple",o.multiple),r},createText:e=>cr.createTextNode(e),createComment:e=>cr.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>cr.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){const r=o?ur||(ur=cr.createElementNS(ir,"svg")):ar||(ar=cr.createElement("div"));r.innerHTML=e;const l=r.firstChild;let s=l,i=s;for(;s;)i=s,dr.insert(s,t,n),s=r.firstChild;return[l,i]}};const pr=/\s*!important$/;function fr(e,t,n){if(T(n))n.forEach((n=>fr(e,t,n)));else if(t.startsWith("--"))e.setProperty(t,n);else{const o=function(e,t){const n=vr[t];if(n)return n;let o=W(t);if("filter"!==o&&o in e)return vr[t]=o;o=G(o);for(let r=0;rdocument.createEvent("Event").timeStamp&&(gr=()=>performance.now());const e=navigator.userAgent.match(/firefox\/(\d+)/i);br=!!(e&&Number(e[1])<=53)}let yr=0;const xr=Promise.resolve(),_r=()=>{yr=0};function kr(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(wr.test(e)){let n;for(t={};n=e.match(wr);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[K(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||gr();(br||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=(()=>yr||(xr.then(_r),yr=gr()))(),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 wr=/(?:Once|Passive|Capture)$/;const Cr=/^on[a-z]/;const Sr={beforeMount(e,{value:t},{transition:n}){e._vod="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):$r(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),$r(e,!0),o.enter(e)):o.leave(e,(()=>{$r(e,!1)})):$r(e,t))},beforeUnmount(e,{value:t}){$r(e,t)}};function $r(e,t){e.style.display=t?e._vod:"none"}const Er=$({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(A(n)){if(t!==n){const t=o.display;o.cssText=n,"_vod"in e&&(o.display=t)}}else{for(const e in n)fr(o,e,n[e]);if(t&&!A(t))for(const e in t)null==n[e]&&fr(o,e,"")}else e.removeAttribute("style")}(e,n,o);break;default:C(t)?S(t)||kr(e,t,0,o,s):function(e,t,n,o){if(o)return"innerHTML"===t||!!(t in e&&Cr.test(t)&&j(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(Cr.test(t)&&A(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){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){}}else{e._value=n;const t=null==n?"":n;e.value!==t&&(e.value=t)}}(e,t,o,l,s,i,c):("true-value"===t?e._trueValue=o:"false-value"===t&&(e._falseValue=o),function(e,t,n,o){if(o&&t.startsWith("xlink:"))null==n?e.removeAttributeNS(mr,t.slice(6,t.length)):e.setAttributeNS(mr,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},dr);let Or,Lr=!1;const Tr=(...e)=>{const t=(Or=Lr?Or:oo(Er),Lr=!0,Or).createApp(...e),{mount:n}=t;return t.mount=e=>{const t=function(e){if(A(e)){return document.querySelector(e)}return e}(e);if(t)return n(t,!0,t instanceof SVGElement)},t};const Pr="undefined"!=typeof window;function Mr(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 jr(e,t){t=function(e,t){if(!Pr)return t;const n=e.base,o=n.endsWith("/")?n.slice(0,-1):n;return t.slice(o.length)}(e,t);const n=Mr(e.locales||{},t)||{},o=Mr(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 Ar(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function Ir(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t.endsWith("/")&&(t+="index"),Pr){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 Rr=Symbol();function Fr(){return function(){const e=jo(Rr);if(!e)throw new Error("useRouter() is called without provider.");return e}().route}function Nr(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 Ur=Qn({name:"VitePressContent",setup(){const e=Fr();return()=>e.component?rr(e.component):null}}),Vr=Qn({setup(e,{slots:t}){const n=ut(!1);return kn((()=>{n.value=!0})),()=>n.value&&t.default?t.default():null}});const Br=ut((zr='{"lang":"zh-CN","title":"Vben Admin","description":"一个开箱即用的前端框架","base":"/","head":[["meta",{"name":"author","content":"Vbenjs Team"}],["meta",{"name":"keywords","content":"vben, vitejs, vite, ant-design-vue, 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 vben admin docs"}],["link",{"rel":"icon","href":"/favicon.ico"}]],"themeConfig":{"repo":"vbenjs/vue-vben-admin","docsRepo":"vbenjs/vue-vben-admin-doc","logo":"/logo.png","docsBranch":"main","editLinks":true,"editLinkText":"为此页提供修改建议","nav":[{"text":"指南","link":"/guide/","items":[{"text":"指南","link":"/guide/introduction"},{"text":"深入","link":"/dep/icon"},{"text":"其他","link":"/other/faq"}]},{"text":"组件","link":"/components/","items":[{"text":"介绍","link":"/components/introduction"},{"text":"全局组件","link":"/components/glob/button"},{"text":"常用组件","link":"/components/basic"},{"text":"函数式组件","link":"/components/functional/context-menu"}]},{"text":"相关链接","items":[{"text":"完整版预览","link":"https://vben.vvbin.cn"},{"text":"完整版源码","link":"https://github.com/vbenjs/vue-vben-admin"},{"text":"精简版分支","link":"https://github.com/vbenjs/vue-vben-admin/tree/thin"},{"text":"文档源码","link":"https://github.com/vbenjs/vue-vben-admin-doc"},{"text":"更新日志","link":"https://github.com/vbenjs/vue-vben-admin/blob/main/CHANGELOG.md"}]},{"text":"社区","items":[{"text":"QQ频道【vben admin】","link":"https://pd.qq.com/s/ahgh62mni"},{"text":"KOOK(新)","link":"https://kaihei.co/6ZPFKi"},{"text":"Discord Chat","link":"https://discord.gg/VU62jTecad"}]},{"text":"vben3","items":[{"text":"仓库(beta-1)","link":"https://github.com/jinmao88/vben3"},{"text":"文档","link":"https://vbenjs.github.io/vben3-doc/"}]}],"sidebar":{"/components/":[{"text":"组件","children":[{"text":"前言","link":"/components/introduction"}]},{"text":"全局组件","children":[{"text":"Button","link":"/components/glob/button"}]},{"text":"常用组件","children":[{"text":"Basic","link":"/components/basic"},{"text":"Page","link":"/components/page"},{"text":"Icon","link":"/components/icon"},{"text":"Authority","link":"/components/auth"},{"text":"Form","link":"/components/form"},{"text":"Table","link":"/components/table"},{"text":"PopConfirmButton","link":"/components/pop-confirm-button"},{"text":"CollapseContainer","link":"/components/collapse-container"},{"text":"ScrollContainer","link":"/components/scroll-container"},{"text":"LazyContainer","link":"/components/lazy-container"},{"text":"CodeEditor","link":"/components/code-editor"},{"text":"JsonPreview","link":"/components/json-preview"},{"text":"CountDown","link":"/components/count-down"},{"text":"ClickOutSide","link":"/components/click-out-side"},{"text":"CountTo","link":"/components/count-to"},{"text":"Cropper","link":"/components/cropper"},{"text":"Description","link":"/components/desc"},{"text":"Drawer","link":"/components/drawer"},{"text":"Modal","link":"/components/modal"},{"text":"FlowChart","link":"/components/flow-chart"},{"text":"Upload","link":"/components/upload"},{"text":"Tree","link":"/components/tree"},{"text":"Excel","link":"/components/excel"},{"text":"Qrcode","link":"/components/qrcode"},{"text":"Markdown","link":"/components/markdown"},{"text":"Loading","link":"/components/loading"},{"text":"Tinymce","link":"/components/tinymce"},{"text":"Time","link":"/components/time"},{"text":"StrengthMeter","link":"/components/strength-meter"},{"text":"Verify","link":"/components/verify"},{"text":"Transition","link":"/components/transition"},{"text":"VirtualScroll","link":"/components/virtual-scroll"}]},{"text":"函数式组件","children":[{"text":"ContextMenu","link":"/components/functional/context-menu"},{"text":"Loading","link":"/components/functional/loading"},{"text":"Preview","link":"/components/functional/preview"}]}],"/":[{"text":"指南","children":[{"text":"介绍","link":"/guide/introduction"},{"text":"开始","link":"/guide/"},{"text":"项目配置","link":"/guide/settings"},{"text":"路由","link":"/guide/router"},{"text":"菜单","link":"/guide/menu"},{"text":"权限","link":"/guide/auth"},{"text":"Mock&联调","link":"/guide/mock"},{"text":"组件注册","link":"/guide/component"},{"text":"样式","link":"/guide/design"},{"text":"外部模块","link":"/guide/lib"},{"text":"构建&部署","link":"/guide/deploy"},{"text":"Electron","link":"/guide/electron"}]},{"text":"深入","children":[{"text":"跨域处理","link":"/dep/cors"},{"text":"图标","link":"/dep/icon"},{"text":"国际化","link":"/dep/i18n"},{"text":"项目规范","link":"/dep/lint"},{"text":"黑暗主题","link":"/dep/dark"}]},{"text":"其他","children":[{"text":"常见问题","link":"/other/faq"},{"text":"常见疑点","link":"/other/doubt"},{"text":"测试服务","link":"/other/server"},{"text":"相关项目","link":"/other/project"}]}]}},"locales":{},"customData":{}}',tt(JSON.parse(zr))));var zr;function Dr(){return Br}function Hr(e){const t=e||Fr();return or((()=>jr(Br.value,t.path)))}function Wr(e){const t=e||Fr();return or((()=>t.data))}function qr(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;Pn((()=>{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 Kr;const Gr={};function Jr(){const e=Wr();return or((()=>e.value.frontmatter))}Zt("data-v-1be840de");const Yr=Co("p",{class:"title"},"Debug",-1),Qr={class:"block"},Xr={class:"block"},Zr={class:"block"};en();Qn({expose:[],setup(e){const t=ut(null),n=ut(!1);return Tn(n,(e=>{!1===e&&(t.value.scrollTop=0)})),(e,o)=>(mo(),bo("div",{class:["debug",{open:n.value}],ref:t,onClick:o[1]||(o[1]=e=>n.value=!n.value)},[Yr,Co("pre",Qr,"$page "+g(e.$page),1),Co("pre",Xr,"$siteByRoute "+g(e.$siteByRoute),1),Co("pre",Zr,"$site "+g(e.$site),1)],2))}}).__scopeId="data-v-1be840de";const el=/#.*$/,tl=/(index)?\.(md|html)$/,nl=/\/$/,ol=/^[a-z]+:/i;function rl(e){return Array.isArray(e)}function ll(e){return ol.test(e)}function sl(e){return decodeURI(e).replace(el,"").replace(tl,"")}function il(e){return/^\//.test(e)?e:`/${e}`}function cl(e){return e.replace(/(index)?(\.(md|html))?$/,"")||"/"}function al(e,t){if(function(e){return!1===e||"auto"===e||rl(e)}(e))return e;t=il(t);for(const n in e)if(t.startsWith(il(n)))return e[n];return"auto"}function ul(e){return e.reduce(((e,t)=>(t.link&&e.push({text:t.text,link:cl(t.link)}),function(e){return void 0!==e.children}(t)&&(e=[...e,...ul(t.children)]),e)),[])}const dl={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"},pl=Co("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 fl={render:function(e,t){return mo(),bo("svg",dl,[pl])}};const hl={},vl=tn()(((e,t)=>(mo(),bo("a",{class:"nav-bar-title",href:e.$withBase(e.$localePath),"aria-label":`${e.$siteByRoute.title}, back to home`},[e.$themeConfig.logo?(mo(),bo("img",{key:0,class:"logo",src:e.$withBase(e.$themeConfig.logo),alt:"Logo"},null,8,["src"])):Oo("v-if",!0),$o(" "+g(e.$site.title),1)],8,["href","aria-label"]))));function ml(e){const t=Fr(),{withBase:n}=function(){const e=Dr();return{withBase:function(t){return Ar(e.value.base,t)}}}(),o=ll(e.value.link);return{props:or((()=>{const r=gl(`/${t.data.relativePath}`);let l=!1;if(e.value.activeMatch)l=new RegExp(e.value.activeMatch).test(r);else{const t=gl(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 gl(e){return e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\.(html|md)$/,"").replace(/\/index$/,"/")}hl.render=vl,hl.__scopeId="data-v-ffb90d4a";const bl={},yl={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"},xl=Co("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),_l=Co("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);bl.render=function(e,t){return mo(),bo("svg",yl,[xl,_l])},Zt("data-v-c272f228");const kl={class:"nav-link"};en();var wl=Qn({expose:[],props:{item:{type:null,required:!0}},setup(e){const t=vt(e),{props:n,isExternal:o}=ml(t.item);return(t,r)=>(mo(),bo("div",kl,[Co("a",Mo({class:"item"},pt(n)),[$o(g(e.item.text)+" ",1),pt(o)?(mo(),bo(bl,{key:0})):Oo("v-if",!0)],16)]))}});wl.__scopeId="data-v-c272f228",Zt("data-v-7b16fcd4");const Cl={class:"nav-dropdown-link-item"},Sl=Co("span",{class:"arrow"},null,-1),$l={class:"text"},El={class:"icon"};en();var Ol=Qn({expose:[],props:{item:{type:null,required:!0}},setup(e){const t=vt(e),{props:n,isExternal:o}=ml(t.item);return(t,r)=>(mo(),bo("div",Cl,[Co("a",Mo({class:"item"},pt(n)),[Sl,Co("span",$l,g(e.item.text),1),Co("span",El,[pt(o)?(mo(),bo(bl,{key:0})):Oo("v-if",!0)])],16)]))}});Ol.__scopeId="data-v-7b16fcd4",Zt("data-v-312de885");const Ll={class:"button-text"},Tl={class:"dialog"};en();var Pl=Qn({expose:[],props:{item:{type:null,required:!0}},setup(e){const t=Fr(),n=ut(!1);function o(){n.value=!n.value}return Tn((()=>t.path),(()=>{n.value=!1})),(t,r)=>(mo(),bo("div",{class:["nav-dropdown-link",{open:n.value}]},[Co("button",{class:"button","aria-label":e.item.ariaLabel,onClick:o},[Co("span",Ll,g(e.item.text),1),Co("span",{class:["button-arrow",n.value?"down":"right"]},null,2)],8,["aria-label"]),Co("ul",Tl,[(mo(!0),bo(ao,null,lr(e.item.items,(e=>(mo(),bo("li",{key:e.text,class:"dialog-item"},[Co(Ol,{item:e},null,8,["item"])])))),128))])],2))}});Pl.__scopeId="data-v-312de885",Zt("data-v-1e870408");const Ml={key:0,class:"nav-links"},jl={key:1,class:"item"};en();var Al=Qn({expose:[],setup(e){const t=Hr(),n=function(){const e=Fr(),t=Dr();return or((()=>{const n=t.value.themeConfig.locales;if(!n)return null;const o=Object.keys(n);if(o.length<=1)return null;const r=Pr?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=or((()=>r.value||repo.value)),r=or((()=>t.value.themeConfig.nav));return(e,t)=>pt(o)?(mo(),bo("nav",Ml,[pt(r)?(mo(!0),bo(ao,{key:0},lr(pt(r),(e=>(mo(),bo("div",{key:e.text,class:"item"},[e.items?(mo(),bo(Pl,{key:0,item:e},null,8,["item"])):(mo(),bo(wl,{key:1,item:e},null,8,["item"]))])))),128)):Oo("v-if",!0),pt(n)?(mo(),bo("div",jl,[Co(Pl,{item:pt(n)},null,8,["item"])])):Oo("v-if",!0),Oo('
\n \n
')])):Oo("v-if",!0)}});Al.__scopeId="data-v-1e870408";var Il={emits:["toggle"]};const Rl=Co("svg",{class:"icon",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",role:"img",viewBox:"0 0 448 512"},[Co("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);Il.render=function(e,t,n,o,r,l){return mo(),bo("div",{class:"sidebar-button",onClick:t[1]||(t[1]=t=>e.$emit("toggle"))},[Rl])};const Fl={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"},Nl=Co("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 Ul={render:function(e,t){return mo(),bo("svg",Fl,[Nl])}};const Vl={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"},Bl=Co("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 zl={render:function(e,t){return mo(),bo("svg",Vl,[Bl])}}; +/*! ***************************************************************************** +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 Dl="undefined"!=typeof window,Hl=()=>{};const Wl=e=>e();function ql(e,t,n={}){const{eventFilter:o=Wl}=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 Kl(e){Jo()&&$n(e)}const Gl=Dl?window:void 0;const Jl={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&&"null"!==e?e:null,write:e=>String(e)},string:{read:e=>null!=e?e:null,write:e=>String(e)}};function Yl(e,t,n=(null==Gl?void 0:Gl.localStorage),o={}){var r;const{flush:l="pre",deep:s=!0,listenToStorageChanges:i=!0,window:c=Gl,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:Jl[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=Gl):[t,n,o,r]=e,!t)return Hl;let l=Hl;const s=Tn((()=>pt(t)),(e=>{l(),e&&(e.addEventListener(n,o,r),l=()=>{e.removeEventListener(n,o,r),l=Hl})}),{immediate:!0,flush:"post"}),i=()=>{s(),l()};Kl(i)}(c,"storage",f),ql(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 Ql(e){return function(e,t={}){const{window:n=Gl}=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),Kl((()=>{"removeEventListener"in o?o.removeEventListener("change",l):o.removeListener(l)})),r}("(prefers-color-scheme: dark)",e)}var Xl,Zl;(Zl=Xl||(Xl={})).UP="UP",Zl.RIGHT="RIGHT",Zl.DOWN="DOWN",Zl.LEFT="LEFT",Zl.NONE="NONE";const es=function(e={}){const{selector:t="html",attribute:n="class",valueDark:o="dark",valueLight:r="",window:l=Gl,storage:s=(null==Gl?void 0:Gl.localStorage),storageKey:i="vueuse-color-scheme",listenToStorageChanges:c=!0}=e,a=Ql({window:l}),u=null==i?ut("auto"):Yl(i,"auto",s,{window:l,listenToStorageChanges:c}),d=or({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 Tn(d,p,{flush:"post"}),function(e,t=!0){Jo()?kn(e):t?e():It(e)}((()=>p(d.value))),d}({storageKey:"vben-admin-color-scheme",valueLight:"light"});var ts=Qn({expose:[],setup(e){const t=function(e=!1){if(at(e))return t=>{e.value="boolean"==typeof t?t:!e.value};{const t=ut(e),n=e=>{t.value="boolean"==typeof e?e:!t.value};return[t,n]}}(es);return(e,n)=>{const o=Ul,r=zl;return mo(),bo("button",{class:"nav-btn","aria-label":"Toggle Theme",onClick:n[1]||(n[1]=(...e)=>pt(t)&&pt(t)(...e))},[zn(Co(o,null,null,512),[[Sr,pt(es)]]),zn(Co(r,null,null,512),[[Sr,!pt(es)]])])}}});const ns=["GitHub","GitLab","Bitbucket"].map((e=>[e,new RegExp(e,"i")]));function os(){const e=Hr();return or((()=>{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=ns.find((([e,t])=>t.test(n[0])));if(o&&o[0])return o[0];return"Source"}(o,t.repoLabel),link:o}}))}Zt("data-v-2332dbaa");const rs={class:"nav-bar"},ls=Co("div",{class:"flex-grow"},null,-1),ss={class:"nav"},is={class:"nav-icons"},cs={class:"item"},as={key:0,class:"item"},us={class:"nav-btn",href:"https://github.com/vbenjs/vue-vben-admin",target:"_blank","aria-label":"View GitHub Repo"};en();var ds=Qn({expose:[],emits:["toggle"],setup(e){const t=os();return(e,n)=>{const o=fl;return mo(),bo("header",rs,[Co(Il,{onToggle:n[1]||(n[1]=t=>e.$emit("toggle"))}),Co(hl),ls,Co("div",ss,[Co(Al)]),Co("div",is,[Co("div",cs,[Co(ts)]),pt(t)?(mo(),bo("div",as,[Co("a",us,[Co(o)])])):Oo("v-if",!0)]),Gt(e.$slots,"search",{},void 0,!0)])}}});function ps(){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")}kn((()=>{o(),window.addEventListener("scroll",n)})),Cn((()=>{r(decodeURIComponent(location.hash))})),$n((()=>{window.removeEventListener("scroll",n)}))}function fs(e){const t=document.querySelector(".nav-bar").offsetHeight;return e.parentElement.offsetTop-t-15}function hs(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}ds.__scopeId="data-v-2332dbaa";const ms=e=>{const t=Fr(),n=Dr(),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&&sl(`/${e.data.relativePath}`)===sl(t)}(t,e.item.link),c=gs(i,s,o);return rr("li",{class:"sidebar-link"},[rr(l?"a":"p",{class:{"sidebar-link-item":!0,active:i},href:l},r),c])};function gs(e,t,n){return t&&t.length>0?rr("ul",{class:"sidebar-links"},t.map((e=>rr(ms,{item:e})))):e&&n?gs(!1,function(e){return bs(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 bs(e){return e.map((e=>({text:e.title,link:`#${e.slug}`,children:e.children?bs(e.children):void 0})))}const ys={key:0,class:"sidebar-links"};var xs=Qn({expose:[],setup(e){const t=function(){const e=Fr(),t=Hr();return ps(),or((()=>{const n=e.data.headers,o=e.data.frontmatter.sidebar,r=e.data.frontmatter.sidebarDepth;if(!1===o)return[];if("auto"===o)return vs(n,r);const l=al(t.value.themeConfig.sidebar,e.data.relativePath);return!1===l?[]:"auto"===l?vs(n,r):l}))}();return(e,n)=>pt(t).length>0?(mo(),bo("ul",ys,[(mo(!0),bo(ao,null,lr(pt(t),(e=>(mo(),bo(pt(ms),{key:e.text,item:e},null,8,["item"])))),128))])):Oo("v-if",!0)}}),_s=Qn({expose:[],props:{open:{type:Boolean,required:!0}},setup:e=>(t,n)=>(mo(),bo(ao,null,[Co("aside",{class:["sidebar",{open:e.open}]},[Co(Al,{class:"nav"}),Gt(t.$slots,"sidebar-top",{},void 0,!0),Co(xs),Gt(t.$slots,"sidebar-bottom",{},void 0,!0)],2),Oo(" ")],2112))});_s.__scopeId="data-v-4668b452";const ks=/bitbucket.org/;function ws(){const e=Hr(),t=Wr();return{url:or((()=>{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 ks.test(e)?function(e,t,n,o,r){return(ll(t)?t:e).replace(nl,"")+`/src/${o}/`+(n?n.replace(nl,"")+"/":"")+r+`?mode=edit&spa=0&at=${o}&fileviewer=file-view-default`}(e,t,n,o,r):function(e,t,n,o,r){return(ll(t)?t:`https://github.com/${t}`).replace(nl,"")+`/edit/${o}/`+(n?n.replace(nl,"")+"/":"")+r}(0,t,n,o,r)}(o,s,r,l,i):null})),text:or((()=>e.value.themeConfig.editLinkText||"Edit this page"))}}Zt("data-v-045573c2");const Cs={class:"edit-link"};en();var Ss=Qn({expose:[],setup(e){const{url:t,text:n}=ws();return(e,o)=>(mo(),bo("div",Cs,[pt(t)?(mo(),bo("a",{key:0,class:"link",href:pt(t),target:"_blank",rel:"noopener noreferrer"},[$o(g(pt(n))+" ",1),Co(bl,{class:"icon"})],8,["href"])):Oo("v-if",!0)]))}});Ss.__scopeId="data-v-045573c2",Zt("data-v-03e55a27");const $s={key:0,class:"last-updated"},Es={class:"prefix"},Os={class:"datetime"};en();var Ls=Qn({expose:[],setup(e){const t=Hr(),n=Wr(),o=or((()=>{const e=t.value.themeConfig.lastUpdated;return void 0!==e&&!1!==e})),r=or((()=>{const e=t.value.themeConfig.lastUpdated;return!0===e?"Last Updated":e})),l=ut("");return kn((()=>{l.value=new Date(n.value.lastUpdated).toLocaleString("en-US")})),(e,t)=>pt(o)?(mo(),bo("p",$s,[Co("span",Es,g(pt(r))+":",1),Co("span",Os,g(l.value),1)])):Oo("v-if",!0)}});Ls.__scopeId="data-v-03e55a27",Zt("data-v-22e60b1a");const Ts={class:"page-footer"},Ps={class:"edit"},Ms={class:"updated"};en();var js=Qn({expose:[],setup:e=>(e,t)=>(mo(),bo("footer",Ts,[Co("div",Ps,[Co(Ss)]),Co("div",Ms,[Co(Ls)])]))});function As(){const e=Hr(),t=Wr(),n=or((()=>cl(il(t.value.relativePath)))),o=or((()=>{const t=al(e.value.themeConfig.sidebar,n.value);return rl(t)?ul(t):[]})),r=or((()=>o.value.findIndex((e=>e.link===n.value)))),l=or((()=>{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=or((()=>!!l.value||!!s.value));return{next:l,prev:s,hasLinks:i}}js.__scopeId="data-v-22e60b1a";const Is={},Rs={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},Fs=Co("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);Is.render=function(e,t){return mo(),bo("svg",Rs,[Fs])};const Ns={},Us={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},Vs=Co("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);Ns.render=function(e,t){return mo(),bo("svg",Us,[Vs])},Zt("data-v-0facf926");const Bs={key:0,class:"next-and-prev-link"},zs={class:"container"},Ds={class:"prev"},Hs={class:"text"},Ws={class:"next"},qs={class:"text"};en();var Ks=Qn({expose:[],setup(e){const{hasLinks:t,prev:n,next:o}=As();return(e,r)=>pt(t)?(mo(),bo("div",Bs,[Co("div",zs,[Co("div",Ds,[pt(n)?(mo(),bo("a",{key:0,class:"link",href:e.$withBase(pt(n).link)},[Co(Is,{class:"icon icon-prev"}),Co("span",Hs,g(pt(n).text),1)],8,["href"])):Oo("v-if",!0)]),Co("div",Ws,[pt(o)?(mo(),bo("a",{key:0,class:"link",href:e.$withBase(pt(o).link)},[Co("span",qs,g(pt(o).text),1),Co(Ns,{class:"icon icon-next"})],8,["href"])):Oo("v-if",!0)])])])):Oo("v-if",!0)}});Ks.__scopeId="data-v-0facf926",Zt("data-v-7abc59e6");const Gs={class:"page"},Js={class:"container"},Ys={class:"content"};en();var Qs=Qn({expose:[],setup:e=>(e,t)=>{const n=so("Content");return mo(),bo("main",Gs,[Co("div",Js,[Gt(e.$slots,"top",{},void 0,!0),Co("div",Ys,[Co(n)]),Co(js),Co(Ks),Gt(e.$slots,"bottom",{},void 0,!0)])])}});Qs.__scopeId="data-v-7abc59e6";const Xs={key:0,id:"ads-container"};var Zs=Qn({expose:[],setup(e){const t=Xn((()=>function(e,t){if(!t)return e();if(void 0===Kr){const e=document.createElement("link").relList;Kr=e&&e.supports&&e.supports("modulepreload")?"modulepreload":"preload"}return Promise.all(t.map((e=>{if(e in Gr)return;Gr[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":Kr,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.00e49e13.js")),void 0))),n=()=>null,o=n,r=n,l=n,s=Fr(),i=Dr(),c=Hr(),a=or((()=>i.value.themeConfig)),u=Wr(),d=or((()=>!!s.data.frontmatter.customLayout)),p=or((()=>!!s.data.frontmatter.home)),f=or((()=>{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),v=or((()=>{const{frontmatter:e}=s.data;if(e.home||!1===e.sidebar)return!1;const{themeConfig:t}=c.value;return!(rl(n=al(t.sidebar,s.data.relativePath))?0===n.length:!n);var n})),m=e=>{h.value="boolean"==typeof e?e:!h.value},g=m.bind(null,!1);Tn(s,g);const b=or((()=>[{"no-navbar":!f.value,"sidebar-open":h.value,"no-sidebar":!v.value}]));return(e,n)=>{const s=so("Content"),i=so("Debug");return mo(),bo(ao,null,[Co("div",{class:["theme",pt(b)]},[pt(f)?(mo(),bo(ds,{key:0,onToggle:m},{search:nn((()=>[Gt(e.$slots,"navbar-search",{},(()=>[pt(a).algolia?(mo(),bo(pt(l),{options:pt(a).algolia,multilang:!!pt(a).locales,key:pt(c).lang},null,8,["options","multilang"])):Oo("v-if",!0)]))])),_:1})):Oo("v-if",!0),Co(_s,{open:h.value},{"sidebar-top":nn((()=>[Gt(e.$slots,"sidebar-top")])),"sidebar-bottom":nn((()=>[Gt(e.$slots,"sidebar-bottom")])),_:1},8,["open"]),Oo(" TODO: make this button accessible "),Co("div",{class:"sidebar-mask",onClick:n[1]||(n[1]=e=>m(!1))}),pt(d)?(mo(),bo(s,{key:1})):pt(p)?(mo(),bo(pt(t),{key:2},{hero:nn((()=>[Gt(e.$slots,"home-hero")])),features:nn((()=>[Gt(e.$slots,"home-features")])),footer:nn((()=>[Gt(e.$slots,"home-footer")])),_:1})):(mo(),bo(Qs,{key:3},{top:nn((()=>[Gt(e.$slots,"page-top-ads",{},(()=>[pt(a).carbonAds&&pt(a).carbonAds.carbon?(mo(),bo("div",Xs,[Co(pt(o),{key:"carbon"+pt(u).relativePath,code:pt(a).carbonAds.carbon,placement:pt(a).carbonAds.placement},null,8,["code","placement"])])):Oo("v-if",!0)])),Gt(e.$slots,"page-top")])),bottom:nn((()=>[Gt(e.$slots,"page-bottom"),Gt(e.$slots,"page-bottom-ads",{},(()=>[pt(a).carbonAds&&pt(a).carbonAds.custom?(mo(),bo(pt(r),{key:"custom"+pt(u).relativePath,code:pt(a).carbonAds.custom,placement:pt(a).carbonAds.placement},null,8,["code","placement"])):Oo("v-if",!0)]))])),_:1}))],2),Co(i)],64)}}});const ei={class:"theme"},ti=Co("h1",null,"404",-1);var ni=Qn({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)=>(mo(),bo("div",ei,[ti,Co("blockquote",null,g(t[Math.floor(Math.random()*t.length)]),1),Co("a",{href:e.$site.base,"aria-label":"go to home"},"Take me home.",8,["href"])]))}});var oi=i({},{Layout:Zs,NotFound:ni});const ri=new Set,li=()=>document.createElement("link");let si;const ii=Pr&&(si=li())&&si.relList&&si.relList.supports&&si.relList.supports("prefetch")?e=>{const t=li();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 ci=oi.NotFound||(()=>"404 Not Found"),ai={name:"VitePressApp",setup(){const e=Hr();return kn((()=>{Tn((()=>e.value.lang),(e=>{document.documentElement.lang=e}),{immediate:!0})})),function(){if(!Pr)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(!ri.has(o)){ri.add(o);const e=Ir(o);ii(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):ri.add(r))}))}))};kn(o);const r=Fr();Tn((()=>r.path),o),$n((()=>{n&&n.disconnect()}))}(),()=>rr(oi.Layout)}};function ui(){const e=function(){let e,t=Pr;return function(e,t){const n=et({path:"/",component:null,data:null});function o(e=(Pr?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),Pr&&(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)),Pr&&It((()=>{if(s.hash&&!l){const e=document.querySelector(decodeURIComponent(s.hash));if(e)return void Nr(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 Pr&&(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),Nr(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=Ir(n);return t&&(e=o),(t||e===o)&&(o=o.replace(/\.js$/,".lean.js")),Pr?(t=!1,import(o)):require(o)}),ci)}(),t=Tr(ai);t.provide(Rr,e);const n=Hr(e.route),o=Wr(e.route);return Pr&&qr(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=>Ar(t.value.base,e)}})}(t,Br,n,o),function(e){e.component("Content",Ur),e.component("ClientOnly",Vr),e.component("Debug",(()=>null))}(t),oi.enhanceApp&&oi.enhanceApp({app:t,router:e,siteData:Br}),{app:t,router:e}}if(Pr){const{app:e,router:t}=ui();t.go().then((()=>{e.mount("#app")}))}export{ao as F,wl as _,Eo as a,Co as b,bo as c,ui as createApp,$o as d,Qn as e,Jr as f,or as g,pt as h,Oo as i,en as j,so as k,Gt as l,mo as o,Zt as p,lr as r,g as t,Hr as u,tn as w}; diff --git a/assets/components_auth.md.7bb93906.js b/assets/components_auth.md.7bb93906.js new file mode 100644 index 00000000..a6a563f2 --- /dev/null +++ b/assets/components_auth.md.7bb93906.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Authority","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/auth.md","lastUpdated":1697592578589}',p={},o=s('

Authority

用于项目权限的组件,一般用于按钮级等细粒度权限管理

Usage

<template>\n  <div>\n    <Authority :value="RoleEnum.ADMIN">\n      <a-button type="primary" block> 只有admin角色可见 </a-button>\n    </Authority>\n  </div>\n</template>\n<script>\n  import { Authority } from '/@/components/Authority';\n  import { defineComponent } from 'vue';\n  export default defineComponent({\n    components: { Authority },\n  });\n</script>\n

Props

属性类型默认值说明
valueRoleEnum,RoleEnum[],string,string[]-角色信息或者权限编码。会自动区分权限模式
',6);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_auth.md.7bb93906.lean.js b/assets/components_auth.md.7bb93906.lean.js new file mode 100644 index 00000000..3f331a73 --- /dev/null +++ b/assets/components_auth.md.7bb93906.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Authority","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/auth.md","lastUpdated":1697592578589}',p={},o=s('',6);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_basic.md.28757feb.js b/assets/components_basic.md.28757feb.js new file mode 100644 index 00000000..322f2d2a --- /dev/null +++ b/assets/components_basic.md.28757feb.js @@ -0,0 +1 @@ +import{o as a,c as s,a as n}from"./app.8cddb23b.js";const t='{"title":"Basic 基础组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"BasicTitle","slug":"basictitle"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":3,"title":"Slots","slug":"slots"},{"level":2,"title":"BasicArrow","slug":"basicarrow"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Props","slug":"props-1"},{"level":2,"title":"BasicHelp","slug":"basichelp"},{"level":3,"title":"Usage","slug":"usage-2"},{"level":3,"title":"Props","slug":"props-2"},{"level":3,"title":"Slots","slug":"slots-1"}],"relativePath":"components/basic.md","lastUpdated":1697592578589}',p={},e=n('

Basic 基础组件

一些比较基础的通用组件使用方式

BasicTitle

用于显示标题,可以显示帮助按钮及文本

Usage

<template>\n  <div>\n    <BasicTitle helpMessage="提示1">标题</BasicTitle>\n    <BasicTitle :helpMessage="['提示1', '提示2']">标题</BasicTitle>\n  </div>\n</template>\n<script>\n  import { BasicTitle } from '/@/components/Basic/index';\n  import { defineComponent } from 'vue';\n  export default defineComponent({\n    components: { BasicTitle },\n  });\n</script>\n

Props

属性类型默认值说明
helpMessagestring|string[]-标题右侧帮助按钮信息
spanbooleanfalse是否显示标题左侧蓝色色块
normalbooleanfalse将文字默认化,不加粗

Slots

名称说明
default标题文本

BasicArrow

带动画的箭头组件

Usage

<template>\n  <div>\n    <BasicArrow :expand="false" />\n  </div>\n</template>\n<script>\n  import { BasicArrow } from '/@/components/Basic/index';\n  import { defineComponent } from 'vue';\n  export default defineComponent({\n    components: { BasicArrow },\n  });\n</script>\n

Props

属性类型默认值说明
expandbooleanfalse箭头展开状态
topbooleanfalse箭头默认向上
bottombooleanfalse箭头默认向下
insetbooleanfalse取消 padding/margin,用于内嵌

BasicHelp

帮助按钮组件

Usage

<template>\n  <div>\n    <BasicHelp :text="['提示1', '提示2']" />\n    <BasicHelp text="提示" />\n  </div>\n</template>\n<script>\n  import { BasicHelp } from '/@/components/Basic/index';\n  import { defineComponent } from 'vue';\n  export default defineComponent({\n    components: { BasicHelp },\n  });\n</script>\n

Props

属性类型默认值可选值说明
fontSizestring14px-字体大小
colorstring#fff-颜色
textstring|string[]--文本列表
showIndexbooleantrue-是否显示序号,在 text 为 string[]情况下生效
maxWidthstring600px-最大宽度
placementstringright-显示方向,参考 Tooltip 组件

Slots

名称说明
default默认图标
',24);p.render=function(n,t,p,o,c,l){return a(),s("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/components_basic.md.28757feb.lean.js b/assets/components_basic.md.28757feb.lean.js new file mode 100644 index 00000000..f3386a90 --- /dev/null +++ b/assets/components_basic.md.28757feb.lean.js @@ -0,0 +1 @@ +import{o as a,c as s,a as n}from"./app.8cddb23b.js";const t='{"title":"Basic 基础组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"BasicTitle","slug":"basictitle"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":3,"title":"Slots","slug":"slots"},{"level":2,"title":"BasicArrow","slug":"basicarrow"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Props","slug":"props-1"},{"level":2,"title":"BasicHelp","slug":"basichelp"},{"level":3,"title":"Usage","slug":"usage-2"},{"level":3,"title":"Props","slug":"props-2"},{"level":3,"title":"Slots","slug":"slots-1"}],"relativePath":"components/basic.md","lastUpdated":1697592578589}',p={},e=n('',24);p.render=function(n,t,p,o,c,l){return a(),s("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/components_click-out-side.md.218c4280.js b/assets/components_click-out-side.md.218c4280.js new file mode 100644 index 00000000..893da16c --- /dev/null +++ b/assets/components_click-out-side.md.218c4280.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"ClickOutSide","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Events","slug":"events"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/click-out-side.md","lastUpdated":1697592578589}',p={},e=a('

ClickOutSide

用于监听包裹的元素点击外部触发事件

Usage

<template>\n  <div>\n    <ClickOutSide @clickOutside="() => (showRef = false)">\n      <div @click="() => (showRef = true)">\n        {{ showRef ? '鼠标点击那部(点击边框外可以恢复)' : '点击该区域状态(初始状态)' }}\n      </div>\n    </ClickOutSide>\n  </div>\n</template>\n<script>\n  import { defineComponent, ref } from 'vue';\n  import { ClickOutSide } from '/@/components/ClickOutSide/';\n  export default defineComponent({\n    components: { ClickOutSide },\n    setup() {\n      const showRef = ref(false);\n      return {\n        showRef,\n      };\n    },\n  });\n</script>\n

Events

事件回调参数说明
clickOutside()=>void点击包裹元素外部区域触发

Slots

名称说明
default被包裹的元素
',8);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/components_click-out-side.md.218c4280.lean.js b/assets/components_click-out-side.md.218c4280.lean.js new file mode 100644 index 00000000..b8f93452 --- /dev/null +++ b/assets/components_click-out-side.md.218c4280.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"ClickOutSide","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Events","slug":"events"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/click-out-side.md","lastUpdated":1697592578589}',p={},e=a('',8);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/components_code-editor.md.a00c679f.js b/assets/components_code-editor.md.a00c679f.js new file mode 100644 index 00000000..3378bef8 --- /dev/null +++ b/assets/components_code-editor.md.a00c679f.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"CodeEditor","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/code-editor.md","lastUpdated":1697592578589}',p={},o=s('

CodeEditor

代码编辑器

Usage

<template>\n  <CodeEditor v-model:value="value" :mode="modeValue" />\n</template>\n<script>\n  import { defineComponent, ref } from 'vue';\n  export default defineComponent({\n    components: { CodeEditor },\n    setup() {\n      const modeValue = ref('application/json');\n      return { value, modeValue };\n    },\n  });\n</script>\n

Props

属性类型默认值可选值说明
value(v-model:value)any--绑定值
modestringapplication/json'application/json','htmlmixed','javascript'代码类型
readonlyboolean--是否只读
',6);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_code-editor.md.a00c679f.lean.js b/assets/components_code-editor.md.a00c679f.lean.js new file mode 100644 index 00000000..76db058a --- /dev/null +++ b/assets/components_code-editor.md.a00c679f.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"CodeEditor","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/code-editor.md","lastUpdated":1697592578589}',p={},o=s('',6);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_collapse-container.md.d3614750.js b/assets/components_collapse-container.md.d3614750.js new file mode 100644 index 00000000..9bbce336 --- /dev/null +++ b/assets/components_collapse-container.md.d3614750.js @@ -0,0 +1 @@ +import{o as t,c as n,a}from"./app.8cddb23b.js";const s='{"title":"CollapseContainer","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/collapse-container.md","lastUpdated":1697592578589}',e={},p=a('

CollapseContainer

区域折叠卡片容器

Usage

<template>\n  <div>\n    <CollapseContainer> content </CollapseContainer>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { CollapseContainer } from '/@/components/Container/index';\n\n  export default defineComponent({\n    components: {\n      CollapseContainer,\n    },\n  });\n</script>\n

Props

属性类型默认值可选值说明
titlestring--标题
canExpanbooleantrue-是否可以展开,为true显示折叠按钮
helpMessagestring[],string--标题右侧温馨提醒
triggerWindowResizebooleanfalse-展开收缩的时候是否触发 window.resize
loadingbooleanfalse-显示加载骨架屏
lazyTimenumber0-延迟加载时间

Slots

名称说明
title自定义标题
action自定义右侧操作按钮
default默认区域
footer自定义底部区域
',8);e.render=function(a,s,e,o,c,d){return t(),n("div",null,[p])};export default e;export{s as __pageData}; diff --git a/assets/components_collapse-container.md.d3614750.lean.js b/assets/components_collapse-container.md.d3614750.lean.js new file mode 100644 index 00000000..5651618b --- /dev/null +++ b/assets/components_collapse-container.md.d3614750.lean.js @@ -0,0 +1 @@ +import{o as t,c as n,a}from"./app.8cddb23b.js";const s='{"title":"CollapseContainer","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/collapse-container.md","lastUpdated":1697592578589}',e={},p=a('',8);e.render=function(a,s,e,o,c,d){return t(),n("div",null,[p])};export default e;export{s as __pageData}; diff --git a/assets/components_count-down.md.9adb65a3.js b/assets/components_count-down.md.9adb65a3.js new file mode 100644 index 00000000..a37c86b9 --- /dev/null +++ b/assets/components_count-down.md.9adb65a3.js @@ -0,0 +1 @@ +import{o as n,c as t,a}from"./app.8cddb23b.js";const s='{"title":"CountDown","description":"","frontmatter":{},"headers":[{"level":2,"title":"CountButton","slug":"countbutton"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":2,"title":"CountDownInput","slug":"countdowninput"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Props","slug":"props-1"}],"relativePath":"components/count-down.md","lastUpdated":1697592578589}',p={},o=a('

CountDown

倒计时组件

CountButton

倒计时按钮组件

Usage

<template>\n  <CountButton />\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { CountButton } from '/@/components/CountDown';\n\n  export default defineComponent({\n    components: { CountButton },\n  });\n</script>\n

Props

属性类型默认值可选值说明
valueany--绑定值
countnumber60-倒计时时间
beforeStartFunc()=>promise--倒计时之前执行的函数,返回 true 才会开始执行

CountDownInput

倒计时输入框按钮组件

Usage

<template>\n  <CountdownInput />\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { CountdownInput } from '/@/components/CountDown';\n\n  export default defineComponent({\n    components: { CountdownInput },\n  });\n</script>\n

Props

属性类型默认值可选值说明
valueany--绑定值
sizestring'default', 'large', 'small'-输入框即按钮大小
countnumber60-倒计时时间
sendCodeApi()=>promise--倒计时之前执行的函数,返回 true 才会开始执行
',14);p.render=function(a,s,p,e,c,u){return n(),t("div",null,[o])};export default p;export{s as __pageData}; diff --git a/assets/components_count-down.md.9adb65a3.lean.js b/assets/components_count-down.md.9adb65a3.lean.js new file mode 100644 index 00000000..af0ef4f6 --- /dev/null +++ b/assets/components_count-down.md.9adb65a3.lean.js @@ -0,0 +1 @@ +import{o as n,c as t,a}from"./app.8cddb23b.js";const s='{"title":"CountDown","description":"","frontmatter":{},"headers":[{"level":2,"title":"CountButton","slug":"countbutton"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":2,"title":"CountDownInput","slug":"countdowninput"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Props","slug":"props-1"}],"relativePath":"components/count-down.md","lastUpdated":1697592578589}',p={},o=a('',14);p.render=function(a,s,p,e,c,u){return n(),t("div",null,[o])};export default p;export{s as __pageData}; diff --git a/assets/components_count-to.md.e7a76610.js b/assets/components_count-to.md.e7a76610.js new file mode 100644 index 00000000..fd13e5da --- /dev/null +++ b/assets/components_count-to.md.e7a76610.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.8cddb23b.js";const s='{"title":"CountTo","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Methods","slug":"methods"}],"relativePath":"components/count-to.md","lastUpdated":1697592578589}',o={},e=n('

CountTo

数字动画组件

该组件对 vue-countTo 进行了重构,改造成适配 vue3 语法的组件。

Usage

<template>\n  <CountTo prefix="$" :color="'#409EFF'" :startVal="1" :endVal="200000" :duration="8000" />\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { CountTo } from '/@/components/CountTo/index';\n\n  export default defineComponent({\n    components: {\n      CountTo,\n    },\n  });\n</script>\n

Props

属性类型默认值说明
startValnumber0起始值
endValnumber2021结束值
durationnumber1500动画持续时间
autoplaybooleantrue自动执行
prefixstring-前缀
suffixstring-后缀
separatorstring,分隔符
colorstring-字体颜色
useEasingbooleantrue是否开启动画
transitionstringlinear动画效果
decimalsnumber0保留小数点位数

Methods

名称回调参数说明
start()=>void开始执行动画
reset()=>void重置
',9);o.render=function(n,s,o,p,d,c){return t(),a("div",null,[e])};export default o;export{s as __pageData}; diff --git a/assets/components_count-to.md.e7a76610.lean.js b/assets/components_count-to.md.e7a76610.lean.js new file mode 100644 index 00000000..55973bac --- /dev/null +++ b/assets/components_count-to.md.e7a76610.lean.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.8cddb23b.js";const s='{"title":"CountTo","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Methods","slug":"methods"}],"relativePath":"components/count-to.md","lastUpdated":1697592578589}',o={},e=n('',9);o.render=function(n,s,o,p,d,c){return t(),a("div",null,[e])};export default o;export{s as __pageData}; diff --git a/assets/components_cropper.md.a5f71a93.js b/assets/components_cropper.md.a5f71a93.js new file mode 100644 index 00000000..8eac3941 --- /dev/null +++ b/assets/components_cropper.md.a5f71a93.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Cropper","description":"","frontmatter":{},"headers":[{"level":2,"title":"CropperImage","slug":"cropperimage"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":2,"title":"CropperAvatar","slug":"cropperavatar"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Props","slug":"props-1"},{"level":3,"title":"Events","slug":"events"},{"level":3,"title":"Methods","slug":"methods"}],"relativePath":"components/cropper.md","lastUpdated":1697592578589}',p={},o=s('

Cropper

图片裁剪组件

CropperImage

图片裁剪组件

Usage

<template>\n  <CropperImage ref="refCropper" :src="img" @cropend="handleCropend" style="width: 40vw" />\n</template>\n<script lang="ts">\n  import { defineComponent, ref } from 'vue';\n  import { CropperImage } from '/@/components/Cropper';\n  import img from '/@/assets/images/header.jpg';\n\n  export default defineComponent({\n    components: {\n      CropperImage,\n    },\n    setup() {\n      const info = ref('');\n      const cropperImg = ref('');\n\n      function handleCropend({ imgBase64, imgInfo }) {\n        info.value = imgInfo;\n        cropperImg.value = imgBase64;\n      }\n\n      return {\n        img,\n        info,\n        cropperImg,\n        handleCropend,\n      };\n    },\n  });\n</script>\n

Props

属性类型默认值说明
srcstring-图片源
altstring-图片 alt
circledbooleanfalse圆形裁剪框
realTimePreviewbooleantrue实时触发预览
heightstring360px高度
crossoriginstring-crossorigin
imageStyleobject``图片样式
optionsobjectDefaultOptionscorpperjs 配置项

DefaultOptions

{\n  aspectRatio: 1,\n  zoomable: true,\n  zoomOnTouch: true,\n  zoomOnWheel: true,\n  cropBoxMovable: true,\n  cropBoxResizable: true,\n  toggleDragModeOnDblclick: true,\n  autoCrop: true,\n  background: true,\n  highlight: true,\n  center: true,\n  responsive: true,\n  restore: true,\n  checkCrossOrigin: true,\n  checkOrientation: true,\n  scalable: true,\n  modal: true,\n  guides: true,\n  movable: true,\n  rotatable: true,\n}\n

CropperAvatar

头像裁剪组件

Usage

<template>\n  <CropperAvatar :uploadApi="uploadApi" />\n</template>\n<script lang="ts">\n  import { defineComponent, ref } from 'vue';\n  import { CropperAvatar } from '/@/components/Cropper';\n  import { uploadApi } from '/@/api/sys/upload';\n\n  export default defineComponent({\n    components: {\n      CropperAvatar,\n    },\n  });\n</script>\n

Props

属性类型默认值说明版本
widthstring,number200px图片源
uploadApi({ file: Blob, name: string }) => Promise<void>-图片上传接口
valueString-当前头像地址(v-model)2.5.3
showBtnBooleantrue是否显示按钮2.5.3
btnTextString-按钮文案2.5.3
btnPropsButtonProps-按钮的其它属性2.5.3

Events

名称参数说明版本
changevalue: String当头像上传完成时触发2.5.3

Methods

名称定义说明版本
openModal()=>void打开上传Modal2.5.3
closeModal()=>void关闭上传Modal2.5.3
',20);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_cropper.md.a5f71a93.lean.js b/assets/components_cropper.md.a5f71a93.lean.js new file mode 100644 index 00000000..55cf3832 --- /dev/null +++ b/assets/components_cropper.md.a5f71a93.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Cropper","description":"","frontmatter":{},"headers":[{"level":2,"title":"CropperImage","slug":"cropperimage"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":2,"title":"CropperAvatar","slug":"cropperavatar"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Props","slug":"props-1"},{"level":3,"title":"Events","slug":"events"},{"level":3,"title":"Methods","slug":"methods"}],"relativePath":"components/cropper.md","lastUpdated":1697592578589}',p={},o=s('',20);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_desc.md.c82924aa.js b/assets/components_desc.md.c82924aa.js new file mode 100644 index 00000000..0b2683d0 --- /dev/null +++ b/assets/components_desc.md.c82924aa.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"Description 详情组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"useDescription","slug":"usedescription"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"DescItem","slug":"descitem"}],"relativePath":"components/desc.md","lastUpdated":1697592578589}',p={},o=a('

Description 详情组件

antv 的 Descriptions 组件进行封装

Usage

<template>\n  <div class="p-4">\n    <Description\n      title="基础示例"\n      :collapseOptions="{ canExpand: true, helpMessage: 'help me' }"\n      :column="3"\n      :data="mockData"\n      :schema="schema"\n    />\n    <Description @register="register" class="mt-4" />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { Alert } from 'ant-design-vue';\n  import { Description, DescItem, useDescription } from '/@/components/Description/index';\n  const mockData: any = {\n    username: 'test',\n    nickName: 'VB',\n    age: 123,\n    phone: '15695909xxx',\n    email: '190848757@qq.com',\n    addr: '厦门市思明区',\n    sex: '男',\n    certy: '3504256199xxxxxxxxx',\n    tag: 'orange',\n  };\n  const schema: DescItem[] = [\n    {\n      field: 'username',\n      label: '用户名',\n    },\n    {\n      field: 'nickName',\n      label: '昵称',\n      render: (curVal, data) => {\n        return `${data.username}-${curVal}`;\n      },\n    },\n    {\n      field: 'phone',\n      label: '联系电话',\n    },\n    {\n      field: 'email',\n      label: '邮箱',\n    },\n    {\n      field: 'addr',\n      label: '地址',\n    },\n  ];\n  export default defineComponent({\n    components: { Description, Alert },\n    setup() {\n      const [register] = useDescription({\n        title: 'useDescription',\n        data: mockData,\n        schema: schema,\n      });\n      return { mockData, schema, register };\n    },\n  });\n</script>\n

useDescription

参考以上示例

const [register] = useDescription(Props);\n

Props

温馨提醒

除以下参数外,官方文档内的 props 也都支持,具体可以参考 antv Description

属性类型默认值可选值说明
titlestring--标题
sizestringsmall-大小
borderedbooleantrue-是否展示边框
columnNumber, Object{ xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }-一行的 DescriptionItems 数量
useCollapseboolean--是否包裹 CollapseContainer 组件
collapseOptionsObject--CollapseContainer 组件属性
schemaDescItem[]--详情项配置,见下方 DescItem 配置
dataobject--数据源

DescItem

属性类型默认值可选值说明
fieldstring--字段名
labelstring--标签名
labelMinWidthnumber--label 最小宽度
contentMinWidthnumber--content 最小宽度
labelStyleany--label 样式
spannumber--和并列数量
show(data)=>boolean--动态判断当前组件是否显示
render(val: string, data: any)=>VNode,undefined,Element,string,number--自定义渲染 content
',12);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_desc.md.c82924aa.lean.js b/assets/components_desc.md.c82924aa.lean.js new file mode 100644 index 00000000..272578d1 --- /dev/null +++ b/assets/components_desc.md.c82924aa.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"Description 详情组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"useDescription","slug":"usedescription"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"DescItem","slug":"descitem"}],"relativePath":"components/desc.md","lastUpdated":1697592578589}',p={},o=a('',12);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_drawer.md.6f60c980.js b/assets/components_drawer.md.6f60c980.js new file mode 100644 index 00000000..8340eab8 --- /dev/null +++ b/assets/components_drawer.md.6f60c980.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Drawer 抽屉组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"useDrawer","slug":"usedrawer"},{"level":2,"title":"useDrawerInner","slug":"usedrawerinner"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"}],"relativePath":"components/drawer.md","lastUpdated":1697592578589}',p={},o=s('

Drawer 抽屉组件

antv 的 drawer 组件进行封装,扩展拖拽,全屏,自适应高度等功能。

Usage

由于 drawer 内部代码一般独立成单独文件,推荐独立成组件来进行开发,所以示例都是以独立的文件来进行说明

独立组件代码,用于写组件内部的内容

<template>\n  <BasicDrawer v-bind="$attrs" title="Drawer Title" width="50%"> Drawer Info. </BasicDrawer>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicDrawer } from '/@/components/Drawer';\n  export default defineComponent({\n    components: { BasicDrawer },\n  });\n</script>\n

页面引用弹窗

<template>\n  <div>\n    <Drawer @register="register" />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { Alert } from 'ant-design-vue';\n  import { useDrawer } from '/@/components/Drawer';\n  import Drawer from './Drawer.vue';\n\n  export default defineComponent({\n    components: { Drawer },\n    setup() {\n      const [register, { openDrawer }] = useDrawer();\n      return {\n        register,\n        openDrawer,\n      };\n    },\n  });\n</script>\n

useDrawer

useDrawer 用于操作组件

const [register, { openDrawer, setDrawerProps }] = useDrawer();\n

register

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

原理其实很简单,就是 vue 的组件子传父通信,内部通过 emit("register",instance) 实现。

同时,独立出去的组件需要将 attrs 绑定到 Drawer 的上面。

<BasicDrawer v-bind="$attrs"> Drawer Info. </BasicDrawer>\n

openDrawer

用于打开/关闭弹窗

// true/false: 打开关闭弹窗\n// data: 传递到子组件的数据\nopenDrawer(true, data);\n

closeDrawer

用于关闭弹窗

closeDrawer();\n

setDrawerProps

用于更改 drawer 的 props 参数因为 drawer 内容独立成组件,如果在外部页面需要更改 props 可能比较麻烦,所以提供 setDrawerProps 方便更改内部 drawer 的 props

Props 内容可以见下方

setDrawerProps(props);\n

useDrawerInner

用于独立的 Drawer 内部调用

<template>\n  <BasicDrawer v-bind="$attrs" @register="register" title="Drawer Title" width="50%">\n    Drawer Info.\n    <a-button type="primary" @click="closeDrawer">内部关闭drawer</a-button>\n  </BasicDrawer>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';\n  export default defineComponent({\n    components: { BasicDrawer },\n    setup() {\n      const [register, { closeDrawer }] = useDrawerInner();\n      return { register, closeDrawer };\n    },\n  });\n</script>\n

useModalInner用于操作独立组件

const [register, { closeModal, setModalProps }] = useModalInner(callback);\n

callback

type: (data:any)=>void

回调函数用于接收 openDrawer 第二个参数传递的值

openDrawer((data: any) => {\n  console.log(data);\n});\n

closeDrawer

用于关闭抽屉

closeDrawer();\n

changeOkLoading

用于修改确认按钮的 loading 状态

// true or false\nchangeOkLoading(true);\n

changeLoading

用于修改 modal 的 loading 状态

// true or false\nchangeLoading(true);\n

setDrawerProps

用于更改 drawer 的 props 参数因为 modal 内容独立成组件,如果在外部页面需要更改 props 可能比较麻烦,所以提供setDrawerProps 方便更改内部 drawer 的 props

Props 内容可以见下方

Props

温馨提醒

除以下参数外,官方文档内的 props 也都支持,具体可以参考 antv drawer

属性类型默认值可选值说明
isDetailbooleanfalse-是否为详情模式
loadingbooleanfalse-loading 状态
loadingTextstring``-loading 文本 s
showDetailBackbooleantrue-isDetail=true 状态下是否显示返回按钮
closeFunc() => Promise<boolean>--自定义关闭函数,返回true关闭,否则不关闭
showFooterboolean--是否显示底部
footerHeightnumber60-底部区域高度

Events

事件回调参数说明
close(e)=>void点击关闭回调
visible-change(visible:boolean)=>void弹窗打开关闭时触发
ok(e)=>void点击确定回调
',52);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_drawer.md.6f60c980.lean.js b/assets/components_drawer.md.6f60c980.lean.js new file mode 100644 index 00000000..c9dc2ccb --- /dev/null +++ b/assets/components_drawer.md.6f60c980.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Drawer 抽屉组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"useDrawer","slug":"usedrawer"},{"level":2,"title":"useDrawerInner","slug":"usedrawerinner"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"}],"relativePath":"components/drawer.md","lastUpdated":1697592578589}',p={},o=s('',52);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_excel.md.b9baf91f.js b/assets/components_excel.md.b9baf91f.js new file mode 100644 index 00000000..e9189512 --- /dev/null +++ b/assets/components_excel.md.b9baf91f.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"Excel 组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"Import","slug":"import"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Events","slug":"events"},{"level":3,"title":"ExcelData","slug":"exceldata"},{"level":2,"title":"Export","slug":"export"},{"level":3,"title":"数组格式数据导出","slug":"数组格式数据导出"},{"level":3,"title":"自定义导出格式","slug":"自定义导出格式"},{"level":3,"title":"json 格式导出","slug":"json-格式导出"},{"level":2,"title":"Function","slug":"function"},{"level":3,"title":"JsonToSheet Type","slug":"jsontosheet-type"},{"level":3,"title":"AoAToSheet Type","slug":"aoatosheet-type"}],"relativePath":"components/excel.md","lastUpdated":1697592578589}',p={},e=a('

Excel 组件

excel 导入导出操作

项目中使用到的是 XLSX,具体文档可以参考XLSX 文档

Import

Usage

<template>\n  <ImpExcel @success="loadDataSuccess">\n    <a-button class="m-3">导入Excel</a-button>\n  </ImpExcel>\n</template>\n<script lang="ts">\n  import { defineComponent, ref } from 'vue';\n  import { ImpExcel, ExcelData } from '/@/components/Excel';\n  export default defineComponent({\n    components: { ImpExcel },\n    setup() {\n      function loadDataSuccess(excelDataList: ExcelData[]) {\n        tableListRef.value = [];\n        console.log(excelDataList);\n        for (const excelData of excelDataList) {\n          const {\n            header,\n            results,\n            meta: { sheetName },\n          } = excelData;\n          const columns: BasicColumn[] = [];\n          for (const title of header) {\n            columns.push({ title, dataIndex: title });\n          }\n          tableListRef.value.push({ title: sheetName, dataSource: results, columns });\n        }\n      }\n      return {\n        loadDataSuccess,\n      };\n    },\n  });\n</script>\n

Events

事件回调参数说明
success(res:ExcelData)=>void导入成功回调
error()=>void导出错误

ExcelData

属性类型默认值说明
header:string[];table 表头
results:T[];table 数据
meta:{ sheetName: string };table title

Export

具体详情可以参考完整版示例

import { jsonToSheetXlsx, aoaToSheetXlsx } from '/@/components/Excel';\n

数组格式数据导出

import { aoaToSheetXlsx } from '/@/components/Excel';\n// 保证data顺序与header一致\naoaToSheetXlsx({\n  data: [],\n  header: [],\n  filename: '二维数组方式导出excel.xlsx',\n});\n

自定义导出格式

import { jsonToSheetXlsx } from '/@/components/Excel';\n\njsonToSheetXlsx({\n  data,\n  filename,\n  write2excelOpts: {\n    // 可以是 xlsx/html/csv/txt\n    bookType,\n  },\n});\n

json 格式导出

import { jsonToSheetXlsx } from '/@/components/Excel';\n\njsonToSheetXlsx({\n  data,\n  filename: '使用key作为默认头部.xlsx',\n});\n\njsonToSheetXlsx({\n  data,\n  header: {\n    id: 'ID',\n    name: '姓名',\n    age: '年龄',\n    no: '编号',\n    address: '地址',\n    beginTime: '开始时间',\n    endTime: '结束时间',\n  },\n  filename: '自定义头部.xlsx',\n  json2sheetOpts: {\n    // 指定顺序\n    header: ['name', 'id'],\n  },\n});\n

Function

方法回调参数返回值说明
jsonToSheetXlsxFunction(JsonToSheet)json 格式数据,导出到 excel
aoaToSheetXlsxFunction(AoAToSheet)数组格式,导出到 excel

JsonToSheet Type

属性类型默认值说明
dataT[]JSON 对象数组
header?:T;表头未设置则取 JSON 对象的 key 作为 header
filename?:stringexcel-list.xlsx导出的文件名
json2sheetOpts?:JSON2SheetOpts调用 XLSX.utils.json_to_sheet 的可选参数
write2excelOpts?:WritingOptions{ bookType: 'xlsx' }调用 XLSX.writeFile 的可选参数,具体参 XLSX 文档

AoAToSheet Type

属性类型默认值说明
dataT[][];二维数组
header?:T;表头 ;未设置则没有表头
filename?:string;excel-list.xlsx导出的文件名
write2excelOpts?:WritingOptions;{ bookType: 'xlsx' }调用 XLSX.writeFile 的可选参数
',25);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/components_excel.md.b9baf91f.lean.js b/assets/components_excel.md.b9baf91f.lean.js new file mode 100644 index 00000000..6709c27b --- /dev/null +++ b/assets/components_excel.md.b9baf91f.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"Excel 组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"Import","slug":"import"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Events","slug":"events"},{"level":3,"title":"ExcelData","slug":"exceldata"},{"level":2,"title":"Export","slug":"export"},{"level":3,"title":"数组格式数据导出","slug":"数组格式数据导出"},{"level":3,"title":"自定义导出格式","slug":"自定义导出格式"},{"level":3,"title":"json 格式导出","slug":"json-格式导出"},{"level":2,"title":"Function","slug":"function"},{"level":3,"title":"JsonToSheet Type","slug":"jsontosheet-type"},{"level":3,"title":"AoAToSheet Type","slug":"aoatosheet-type"}],"relativePath":"components/excel.md","lastUpdated":1697592578589}',p={},e=a('',25);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/components_flow-chart.md.387f4a76.js b/assets/components_flow-chart.md.387f4a76.js new file mode 100644 index 00000000..0c891d40 --- /dev/null +++ b/assets/components_flow-chart.md.387f4a76.js @@ -0,0 +1 @@ +import{o as a,c as n,a as t}from"./app.8cddb23b.js";const s='{"title":"FlowChart","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/flow-chart.md","lastUpdated":1697592578589}',p={},o=t('

FlowChart

流程图组件,基于 didi/LogicFlow 的简单封装。详细配置请参考文档 FlowChart

Usage

<template>\n  <FlowChart :data="demoData" />\n</template>\n\n<script lang="ts">\n  import { FlowChart } from '/@/components/FlowChart';\n  import { PageWrapper } from '/@/components/Page';\n\n  import demoData from './dataTurbo.json';\n  export default {\n    components: { FlowChart, PageWrapper },\n    setup() {\n      return { demoData };\n    },\n  };\n</script>\n

Props

属性类型默认值可选值说明
flowOptionsobject--FlowCharts 配置项
dataobject--流程数据
toolbarbooleantrue-是否显示工具栏
patternItems[]--左侧拖拽列表数据
',6);p.render=function(t,s,p,e,c,l){return a(),n("div",null,[o])};export default p;export{s as __pageData}; diff --git a/assets/components_flow-chart.md.387f4a76.lean.js b/assets/components_flow-chart.md.387f4a76.lean.js new file mode 100644 index 00000000..1c95fc38 --- /dev/null +++ b/assets/components_flow-chart.md.387f4a76.lean.js @@ -0,0 +1 @@ +import{o as a,c as n,a as t}from"./app.8cddb23b.js";const s='{"title":"FlowChart","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/flow-chart.md","lastUpdated":1697592578589}',p={},o=t('',6);p.render=function(t,s,p,e,c,l){return a(),n("div",null,[o])};export default p;export{s as __pageData}; diff --git a/assets/components_form.md.3e11ed4d.js b/assets/components_form.md.3e11ed4d.js new file mode 100644 index 00000000..2d7d87c3 --- /dev/null +++ b/assets/components_form.md.3e11ed4d.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"Form 表单组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":3,"title":"useForm 方式","slug":"useform-方式"},{"level":3,"title":"template 方式","slug":"template-方式"},{"level":2,"title":"useForm","slug":"useform"},{"level":3,"title":"示例","slug":"示例"},{"level":3,"title":"参数介绍","slug":"参数介绍"},{"level":3,"title":"Methods","slug":"methods"},{"level":2,"title":"Props","slug":"props"},{"level":3,"title":"ColEx","slug":"colex"},{"level":3,"title":"ActionButtonOption","slug":"actionbuttonoption"},{"level":3,"title":"fieldMapToTime","slug":"fieldmaptotime"},{"level":3,"title":"FormSchema","slug":"formschema"},{"level":3,"title":"Divider schema 说明","slug":"divider-schema-说明"},{"level":2,"title":"自行添加需要的组件类型","slug":"自行添加需要的组件类型"},{"level":3,"title":"方式 1","slug":"方式-1"},{"level":3,"title":"方式 2","slug":"方式-2"},{"level":3,"title":"render","slug":"render"},{"level":3,"title":"slot","slug":"slot"},{"level":3,"title":"ifShow/show/dynamicDisabled","slug":"ifshow-show-dynamicdisabled"},{"level":2,"title":"Slots","slug":"slots"},{"level":2,"title":"ApiSelect","slug":"apiselect"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Props","slug":"props-1"},{"level":2,"title":"ApiTreeSelect","slug":"apitreeselect"},{"level":3,"title":"Props","slug":"props-2"},{"level":2,"title":"RadioButtonGroup","slug":"radiobuttongroup"},{"level":3,"title":"Usage","slug":"usage-2"},{"level":3,"title":"Props","slug":"props-3"}],"relativePath":"components/form.md","lastUpdated":1697592578589}',p={},o=a('

Form 表单组件

antv 的 form 组件进行封装,扩展一些常用的功能

如果文档内没有,可以尝试在在线示例内寻找

Usage

useForm 方式

下面是一个使用简单表单的示例,只有一个输入框

<template>\n  <div class="m-4">\n    <BasicForm\n      :labelWidth="100"\n      :schemas="schemas"\n      :actionColOptions="{ span: 24 }"\n      @submit="handleSubmit"\n    />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicForm, FormSchema } from '/@/components/Form';\n  import { CollapseContainer } from '/@/components/Container';\n  import { useMessage } from '/@/hooks/web/useMessage';\n  const schemas: FormSchema[] = [\n    {\n      field: 'field',\n      component: 'Input',\n      label: '字段1',\n      colProps: {\n        span: 8,\n      },\n      defaultValue: '1',\n      componentProps: {\n        placeholder: '自定义placeholder',\n        onChange: (e) => {\n          console.log(e);\n        },\n      },\n    },\n  ];\n\n  export default defineComponent({\n    components: { BasicForm, CollapseContainer },\n    setup() {\n      const { createMessage } = useMessage();\n      return {\n        schemas,\n        handleSubmit: (values: any) => {\n          createMessage.success('click search,values:' + JSON.stringify(values));\n        },\n      };\n    },\n  });\n</script>\n

template 方式

所有可调用函数见下方 Methods 说明

<template>\n  <div class="m-4">\n    <BasicForm\n      :schemas="schemas"\n      ref="formElRef"\n      :labelWidth="100"\n      @submit="handleSubmit"\n      :actionColOptions="{ span: 24 }"\n    />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent, ref } from 'vue';\n  import { BasicForm, FormSchema, FormActionType, FormProps } from '/@/components/Form';\n  import { CollapseContainer } from '/@/components/Container';\n  const schemas: FormSchema[] = [];\n\n  export default defineComponent({\n    components: { BasicForm, CollapseContainer },\n    setup() {\n      const formElRef = ref<Nullable<FormActionType>>(null);\n      return {\n        formElRef,\n        schemas,\n        setProps(props: FormProps) {\n          const formEl = formElRef.value;\n          if (!formEl) return;\n          formEl.setProps(props);\n        },\n      };\n    },\n  });\n</script>\n

useForm

form 组件还提供了 useForm,方便调用函数内部方法

示例

<template>\n  <BasicForm @register="register" @submit="handleSubmit" />\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';\n  import { CollapseContainer } from '/@/components/Container/index';\n  import { useMessage } from '/@/hooks/web/useMessage';\n  const schemas: FormSchema[] = [\n    {\n      field: 'field1',\n      component: 'Input',\n      label: '字段1',\n      colProps: {\n        span: 8,\n      },\n      componentProps: {\n        placeholder: '自定义placeholder',\n        onChange: (e: any) => {\n          console.log(e);\n        },\n      },\n    },\n  ];\n\n  export default defineComponent({\n    components: { BasicForm, CollapseContainer },\n    setup() {\n      const { createMessage } = useMessage();\n      const [register, { setProps }] = useForm({\n        labelWidth: 120,\n        schemas,\n        actionColOptions: {\n          span: 24,\n        },\n      });\n      return {\n        register,\n        schemas,\n        handleSubmit: (values: any) => {\n          createMessage.success('click search,values:' + JSON.stringify(values));\n        },\n        setProps,\n      };\n    },\n  });\n</script>\n

参数介绍

const [register, methods] = useForm(props);\n

参数 props 内的值可以是 computed 或者 ref 类型

register

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

<template>\n  <BasicForm @register="register" @submit="handleSubmit" />\n</template>\n<script>\n  export default defineComponent({\n    components: { BasicForm },\n    setup() {\n      const [register] = useForm();\n      return {\n        register,\n      };\n    },\n  });\n</script>\n

Methods见下方说明

Methods

getFieldsValue

类型: () => Recordable;

说明: 获取表单值

setFieldsValue

类型: <T>(values: T) => Promise<void>

说明: 设置表单字段值

resetFields

类型: ()=> Promise<void>

说明: 重置表单值

validateFields

类型: (nameList?: NamePath[]) => Promise<any>

说明: 校验指定表单项

validate

类型: (nameList?: NamePath[]) => Promise<any>

说明: 校验整个表单

submit

类型: () => Promise<void>

说明: 提交表单

scrollToField

类型: (name: NamePath, options?: ScrollOptions) => Promise<void>

说明: 滚动到对应字段位置

clearValidate

类型: (name?: string | string[]) => Promise<void>

说明: 清空校验

setProps

TIP

设置表单的 props 可以直接在标签上传递,也可以使用 setProps,或者初始化直接写 useForm(props)

类型: (formProps: Partial<FormProps>) => Promise<void>

说明: 设置表单 Props

removeSchemaByField

类型: (field: string | string[]) => Promise<void>

说明: 根据 field 删除 Schema

appendSchemaByField

类型: ( schema: FormSchema, prefixField: string | undefined, first?: boolean | undefined ) => Promise<void>

说明: 插入到指定 filed 后面,如果没传指定 field,则插入到最后,当 first = true 时插入到第一个位置

updateSchema

类型: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>

说明: 更新表单的 schema, 只更新函数所传的参数

e.g

updateSchema({ field: 'filed', componentProps: { disabled: true } });\nupdateSchema([\n  { field: 'filed', componentProps: { disabled: true } },\n  { field: 'filed1', componentProps: { disabled: false } },\n]);\n

Props

温馨提醒

除以下参数外,官方文档内的 props 也都支持,具体可以参考 antv form

属性类型默认值可选值说明版本
schemasSchema[]--表单配置,见下方 FormSchema 配置
submitOnResetbooleanfalse-重置时是否提交表单
labelColPartial<ColEx>--整个表单通用 LabelCol 配置
wrapperColPartial<ColEx>--整个表单通用 wrapperCol 配置
baseColPropsPartial<ColEx>--配置所有选子项的 ColProps,不需要逐个配置,子项也可单独配置优先与全局
baseRowStyleobject--配置所有 Row 的 style 样式
labelWidthnumber , string--扩展 form 组件,增加 label 宽度,表单内所有组件适用,可以单独在某个项覆盖或者禁用
labelAlignstring-left,rightlabel 布局
mergeDynamicDataobject--额外传递到子组件的参数 values
autoFocusFirstItembooleanfalse-是否聚焦第一个输入框,只在第一个表单项为 input 的时候作用
compactbooleanfalsetrue/false紧凑类型表单,减少 margin-bottom
sizestringdefault'default' , 'small' , 'large'向表单内所有组件传递 size 参数,自定义组件需自行实现 size 接收
disabledbooleanfalsetrue/false向表单内所有组件传递 disabled 属性,自定义组件需自行实现 disabled 接收
autoSetPlaceHolderbooleantrue true/false自动设置表单内组件的 placeholder,自定义组件需自行实现
autoSubmitOnEnterbooleanfalse true/false在 input 中输入时按回车自动提交2.4.0
rulesMessageJoinLabelbooleanfalsetrue/false如果表单项有校验,会自动生成校验信息,该参数控制是否将字段中文名字拼接到自动生成的信息后方
showAdvancedButtonbooleanfalsetrue/false是否显示收起展开按钮
emptySpannumber , Partial<ColEx>0-空白行格,可以是数值或者 col 对象 数
autoAdvancedLinenumber3-如果 showAdvancedButton 为 true,超过指定行数行默认折叠
alwaysShowLinesnumber1-折叠时始终保持显示的行数2.7.1
showActionButtonGroupbooleantruetrue/false是否显示操作按钮(重置/提交)
actionColOptionsPartial<ColEx>--操作按钮外层 Col 组件配置,如果开启 showAdvancedButton,则不用设置,具体见下方 actionColOptions
showResetButtonbooleantrue-是否显示重置按钮
resetButtonOptionsobject-重置按钮配置见下方 ActionButtonOption
showSubmitButtonbooleantrue-是否显示提交按钮
submitButtonOptionsobject-确认按钮配置见下方 ActionButtonOption
resetFunc () => Promise<void>-重置表单行为前执行自定义重置按钮逻辑() => Promise<void>;
submitFunc () => Promise<void>-自定义提交按钮逻辑() => Promise<void>;
fieldMapToTime[string, [string, string], string?][]'timestamp' ,'timestampStartDay' ,momentjs 时间格式用于将表单内时间区域的应设成 2 个字段,见下方说明

ColEx

src/components/Form/src/types/index.ts

ActionButtonOption

BasicButtonProps

export interface ButtonProps extends BasicButtonProps {\n  text?: string;\n}\n

fieldMapToTime

将表单内时间区域的值映射成 2 个字段

如果表单内有时间区间组件,获取到的值是一个数组,但是往往我们传递到后台需要是 2 个字段

useForm({\n  fieldMapToTime: [\n    // data为时间组件在表单内的字段,startTime,endTime为转化后的开始时间与结束时间\n    // 'YYYY-MM-DD'为时间格式,参考moment\n    ['datetime', ['startTime', 'endTime'], 'YYYY-MM-DD'],\n    // 支持多个字段\n    ['datetime1', ['startTime1', 'endTime1'], 'YYYY-MM-DD HH:mm:ss'],\n  ],\n});\n\n// fieldMapToTime没写的时候表单获取到的值\n{\n  datetime: [Date(),Date()]\n}\n//  ['datetime', ['startTime', 'endTime'], 'YYYY-MM-DD'],等同于 dayjs(Date()).format('YYYY-MM-DD'). 之后\n{\n    startTime: '2020-08-12',\n    endTime: '2020-08-15',\n}\n\n// ['datetime', ['startTime', 'endTime'], 'timestamp'],等同于 dayjs(Date()).unix(). 之后\n{\n    startTime: 1597190400,\n    endTime: 1597449600,\n}\n\n// ['datetime', ['startTime', 'endTime'], 'timestampStartDay'],等同于 dayjs(Date()).startOf('day').unix(). 之后\n{\n    startTime: 1597190400,\n    endTime: 1597449600,\n}\n

FormSchema

属性类型默认值可选值说明
fieldstring--字段名
labelstring--标签名
subLabelstring--二级标签名灰色
suffixstring , number , ((values: RenderCallbackParams) => string / number);--组件后面的内容
changeEventstring--表单更新事件名称
helpMessagestring , string[]--标签名右侧温馨提示
helpComponentPropsHelpComponentProps--标签名右侧温馨提示组件 props,见下方 HelpComponentProps
labelWidthstring , number--覆盖统一设置的 labelWidth
disabledLabelWidthbooleanfalsetrue/false禁用 form 全局设置的 labelWidth,自己手动设置 labelCol 和 wrapperCol
componentstring--组件类型,见下方 ComponentType
componentPropsany,()=>{}--所渲染的组件的 props
rulesValidationRule[]--校验规则,见下方 ValidationRule
requiredboolean--简化 rules 配置,为 true 则转化成 [{required:true}]。2.4.0之前的版本只支持 string 类型的值
rulesMessageJoinLabelbooleanfalse-校验信息是否加入 label
itemPropsany--参考下方 FormItem
colPropsColEx--参考上方 actionColOptions
defaultValueobject--所渲渲染组件的初始值
render(renderCallbackParams: RenderCallbackParams) => VNode / VNode[] / string--自定义渲染组件
renderColContent(renderCallbackParams: RenderCallbackParams) => VNode / VNode[] / string--自定义渲染组件(需要自行包含 formItem)
renderComponentContent(renderCallbackParams: RenderCallbackParams) => any / string--自定义渲染组内部的 slot
slotstring--自定义 slot,渲染组件
colSlotstring--自定义 slot,渲染组件 (需要自行包含 formItem)
show boolean / ((renderCallbackParams: RenderCallbackParams) => boolean)--动态判断当前组件是否显示,css 控制,不会删除 dom
ifShow boolean / ((renderCallbackParams: RenderCallbackParams) => boolean)--动态判断当前组件是否显示,js 控制,会删除 dom
dynamicDisabledboolean / ((renderCallbackParams: RenderCallbackParams) => boolean) --动态判断当前组件是否禁用
dynamicRulesboolean / ((renderCallbackParams: RenderCallbackParams) => boolean)--动态判返当前组件你校验规则

RenderCallbackParams

export interface RenderCallbackParams {\n  schema: FormSchema;\n  values: any;\n  model: any;\n  field: string;\n}\n

componentProps

  • 当值为对象类型时,该对象将作为component所对应组件的的 props 传入组件

  • 当值为一个函数时候

参数有 4 个

schema: 表单的整个 schemas

formActionType: 操作表单的函数。与 useForm 返回的操作函数一致

formModel: 表单的双向绑定对象,这个值是响应式的。所以可以方便处理很多操作

tableAction: 操作表格的函数,与 useTable 返回的操作函数一致。注意该参数只在表格内开启搜索表单的时候有值,其余情况为null,

{\n  // 简单例子,值改变的时候操作表格或者修改表单内其他元素的值\n  component:'Input',\n  componentProps: ({ schema, tableAction, formActionType, formModel }) => {\n    return {\n      // xxxx props\n      onChange:e=>{\n        const {reload}=tableAction\n        reload()\n        // or\n        formModel.xxx='123'\n      }\n    };\n  };\n}\n

HelpComponentProps

export interface HelpComponentProps {\n  maxWidth: string;\n  // 是否显示序号\n  showIndex: boolean;\n  // 文本列表\n  text: any;\n  // 颜色\n  color: string;\n  // 字体大小\n  fontSize: string;\n  icon: string;\n  absolute: boolean;\n  // 定位\n  position: any;\n}\n

ComponentType

schema 内组件的可选类型

export type ComponentType =\n  | 'Input'\n  | 'InputGroup'\n  | 'InputPassword'\n  | 'InputSearch'\n  | 'InputTextArea'\n  | 'InputNumber'\n  | 'InputCountDown'\n  | 'Select'\n  | 'ApiSelect'\n  | 'TreeSelect'\n  | 'RadioButtonGroup'\n  | 'RadioGroup'\n  | 'Checkbox'\n  | 'CheckboxGroup'\n  | 'AutoComplete'\n  | 'Cascader'\n  | 'DatePicker'\n  | 'MonthPicker'\n  | 'RangePicker'\n  | 'WeekPicker'\n  | 'TimePicker'\n  | 'Switch'\n  | 'StrengthMeter'\n  | 'Upload'\n  | 'IconPicker'\n  | 'Render'\n  | 'Slider'\n  | 'Rate'\n  | 'Divider'; // v2.7.2新增\n

Divider schema 说明

Divider类型用于在schemas中占位,将会渲染成一个分割线(始终占一整行的版面),可以用于较长表单的版面分隔。请只将 Divider 类型的 schema 当作一个分割线,而不是一个常规的表单字段。

  • Divider仅在showAdvancedButton为 false 时才会显示(也就是说如果启用了表单收起和展开功能,Divider将不会显示)
  • Divider 使用schema中的label以及helpMessage来渲染分割线中的提示内容
  • Divider 可以使用componentProps来设置除type之外的 props
  • Divider 不会渲染AFormItem,因此schema中除labelcomponentPropshelpMessagehelpComponentProps以外的属性不会被用到

自行添加需要的组件类型

src/components/Form/src/componentMap.ts 内,添加需要的组件,并在上方 ComponentType 添加相应的类型 key

方式 1

这种写法适用与适用频率较高的组件

componentMap.set('componentName', 组件);\n\n// ComponentType\nexport type ComponentType = xxxx | 'componentName';\n

方式 2

使用 useComponentRegister 进行注册

这种写法只能在当前页使用,页面销毁之后会从 componentMap 删除相应的组件

import { useComponentRegister } from '@/components/form/index';\n\nimport { StrengthMeter } from '@/components/strength-meter/index';\n\nuseComponentRegister('StrengthMeter', StrengthMeter);\n

提示

方式 2 出现的原因是为了减少打包体积,如果某个组件体积很大,用方式 1 的话可能会使首屏体积增加

render

自定义渲染内容

<template>\n  <div class="m-4">\n    <BasicForm @register="register" @submit="handleSubmit" />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent, h } from 'vue';\n  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';\n  import { useMessage } from '/@/hooks/web/useMessage';\n  import { Input } from 'ant-design-vue';\n  const schemas: FormSchema[] = [\n    {\n      field: 'field1',\n      component: 'Input',\n      label: '字段1',\n      colProps: {\n        span: 8,\n      },\n      rules: [{ required: true }],\n      render: ({ model, field }) => {\n        return h(Input, {\n          placeholder: '请输入',\n          value: model[field],\n          onChange: (e: ChangeEvent) => {\n            model[field] = e.target.value;\n          },\n        });\n      },\n    },\n    {\n      field: 'field2',\n      component: 'Input',\n      label: '字段2',\n      colProps: {\n        span: 8,\n      },\n      rules: [{ required: true }],\n      renderComponentContent: () => {\n        return {\n          suffix: () => 'suffix',\n        };\n      },\n    },\n  ];\n  export default defineComponent({\n    components: { BasicForm },\n    setup() {\n      const { createMessage } = useMessage();\n      const [register, { setProps }] = useForm({\n        labelWidth: 120,\n        schemas,\n        actionColOptions: {\n          span: 24,\n        },\n      });\n      return {\n        register,\n        schemas,\n        handleSubmit: (values: any) => {\n          createMessage.success('click search,values:' + JSON.stringify(values));\n        },\n        setProps,\n      };\n    },\n  });\n</script>\n

slot

自定义渲染内容

提示

使用插槽自定义表单域时,请注意 antdv 有关 FormItem 的相关说明

<template>\n  <div class="m-4">\n    <BasicForm @register="register">\n      <template #customSlot="{ model, field }">\n        <a-input v-model:value="model[field]" />\n      </template>\n    </BasicForm>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'compatible-vue';\n  import { BasicForm, useForm } from '@/components/Form/index';\n  import { BasicModal } from '@/components/modal/index';\n  export default defineComponent({\n    name: 'FormDemo',\n    setup(props) {\n      const [register] = useForm({\n        labelWidth: 100,\n        actionColOptions: {\n          span: 24,\n        },\n        schemas: [\n          {\n            field: 'field1',\n            label: '字段1',\n            slot: 'customSlot',\n          },\n        ],\n      });\n      return {\n        register,\n      };\n    },\n  });\n</script>\n

ifShow/show/dynamicDisabled

自定义显示/禁用

<template>\n  <div class="m-4">\n    <BasicForm @register="register" />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';\n  const schemas: FormSchema[] = [\n    {\n      field: 'field1',\n      component: 'Input',\n      label: '字段1',\n      colProps: {\n        span: 8,\n      },\n      show: ({ values }) => {\n        return !!values.field5;\n      },\n    },\n    {\n      field: 'field2',\n      component: 'Input',\n      label: '字段2',\n      colProps: {\n        span: 8,\n      },\n      ifShow: ({ values }) => {\n        return !!values.field6;\n      },\n    },\n    {\n      field: 'field3',\n      component: 'DatePicker',\n      label: '字段3',\n      colProps: {\n        span: 8,\n      },\n      dynamicDisabled: ({ values }) => {\n        return !!values.field7;\n      },\n    },\n  ];\n\n  export default defineComponent({\n    components: { BasicForm },\n    setup() {\n      const [register, { setProps }] = useForm({\n        labelWidth: 120,\n        schemas,\n        actionColOptions: {\n          span: 24,\n        },\n      });\n      return {\n        register,\n        schemas,\n        setProps,\n      };\n    },\n  });\n</script>\n

antv form

Slots

名称说明
formFooter表单底部区域
formHeader表单顶部区域
resetBefore重置按钮前
submitBefore提交按钮前
advanceBefore展开按钮前
advanceAfter展开按钮后

ApiSelect

远程下拉加载组件,该组件可以用于学习参考如何自定义组件集成到 Form 组件内,将自定义组件交由 Form 去管理

Usage

const schemas: FormSchema[] = [\n  {\n    field: 'field',\n    component: 'ApiSelect',\n    label: '字段',\n  },\n];\n

Props

属性类型默认值说明
numberToStringbooleanfalse是否将number值转化为string
api()=>Promise<{ label: string; value: string; disabled?: boolean }[]>-数据接口,接受一个 Promise 对象
paramsobject-接口参数。此属性改变时会自动重新加载接口数据
resultFieldstring-接口返回的字段,如果接口返回数组,可以不填。支持x.x.x格式
labelFieldstringlabel下拉数组项内label显示文本的字段,支持x.x.x格式
valueFieldstringvalue下拉数组项内value实际值的字段,支持x.x.x格式
immediatebooleantrue是否立即请求接口,否则将在第一次点击时候触发请求

ApiTreeSelect

远程下拉树加载组件,和ApiSelect类似,2.6.1 以上版本

Props

属性类型默认值说明
api()=>Promise<{ label: string; value: string; children?: any[] }[]>-数据接口,接受一个 Promise 对象
paramsobject-接口参数。此属性改变时会自动重新加载接口数据
resultFieldstring-接口返回的字段,如果接口返回数组,可以不填。支持x.x.x格式
immediatebooleantrue是否立即请求接口。

RadioButtonGroup

Radio Button 风格的选择按钮

Usage

const schemas: FormSchema[] = [\n  {\n    field: 'field',\n    component: 'RadioButtonGroup',\n    label: '字段',\n  },\n];\n

Props

属性类型默认值说明
options{ label: string; value: string; disabled?: boolean }[]-数据字段
',133);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_form.md.3e11ed4d.lean.js b/assets/components_form.md.3e11ed4d.lean.js new file mode 100644 index 00000000..4cd061ab --- /dev/null +++ b/assets/components_form.md.3e11ed4d.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"Form 表单组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":3,"title":"useForm 方式","slug":"useform-方式"},{"level":3,"title":"template 方式","slug":"template-方式"},{"level":2,"title":"useForm","slug":"useform"},{"level":3,"title":"示例","slug":"示例"},{"level":3,"title":"参数介绍","slug":"参数介绍"},{"level":3,"title":"Methods","slug":"methods"},{"level":2,"title":"Props","slug":"props"},{"level":3,"title":"ColEx","slug":"colex"},{"level":3,"title":"ActionButtonOption","slug":"actionbuttonoption"},{"level":3,"title":"fieldMapToTime","slug":"fieldmaptotime"},{"level":3,"title":"FormSchema","slug":"formschema"},{"level":3,"title":"Divider schema 说明","slug":"divider-schema-说明"},{"level":2,"title":"自行添加需要的组件类型","slug":"自行添加需要的组件类型"},{"level":3,"title":"方式 1","slug":"方式-1"},{"level":3,"title":"方式 2","slug":"方式-2"},{"level":3,"title":"render","slug":"render"},{"level":3,"title":"slot","slug":"slot"},{"level":3,"title":"ifShow/show/dynamicDisabled","slug":"ifshow-show-dynamicdisabled"},{"level":2,"title":"Slots","slug":"slots"},{"level":2,"title":"ApiSelect","slug":"apiselect"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Props","slug":"props-1"},{"level":2,"title":"ApiTreeSelect","slug":"apitreeselect"},{"level":3,"title":"Props","slug":"props-2"},{"level":2,"title":"RadioButtonGroup","slug":"radiobuttongroup"},{"level":3,"title":"Usage","slug":"usage-2"},{"level":3,"title":"Props","slug":"props-3"}],"relativePath":"components/form.md","lastUpdated":1697592578589}',p={},o=a('',133);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_functional_context-menu.md.3bd48599.js b/assets/components_functional_context-menu.md.3bd48599.js new file mode 100644 index 00000000..bbd312c5 --- /dev/null +++ b/assets/components_functional_context-menu.md.3bd48599.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"ContextMenu","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"createContextMenu","slug":"createcontextmenu"}],"relativePath":"components/functional/context-menu.md","lastUpdated":1697592578589}',p={},o=a('

ContextMenu

函数式创建右键菜单组件, 只要能拿到 dom 的 event 对象就能为其创建右键菜单。

Usage

<template>\n  <div>\n    <a-button type="primary" @contextmenu="handleContext">Right Click on me</a-button>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { useContextMenu } from '/@/hooks/web/useContextMenu';\n  import { CollapseContainer } from '/@/components/Container';\n  import { useMessage } from '/@/hooks/web/useMessage';\n  export default defineComponent({\n    components: { CollapseContainer },\n    setup() {\n      const [createContextMenu] = useContextMenu();\n      const { createMessage } = useMessage();\n      function handleContext(e: MouseEvent) {\n        createContextMenu({\n          event: e,\n          items: [\n            {\n              label: 'New',\n              icon: 'ant-design:plus-outlined',\n              handler: () => {\n                createMessage.success('click new');\n              },\n            },\n            {\n              label: 'Open',\n              icon: 'ant-design:folder-open-filled',\n              handler: () => {\n                createMessage.success('click open');\n              },\n            },\n          ],\n        });\n      }\n      return { handleContext };\n    },\n  });\n</script>\n

createContextMenu

Options

属性类型默认值可选值说明
eventEvent--需要创建的 dom 的 Event 对象
itemsContextMenuItem[]--右键菜单列表,ContextMenuItem见下方说明

ContextMenuItem

属性类型说明
labelstring文本
iconstring图标,参考图标组件
disabledboolean是否禁用
handler()=>void点击触发函数
',9);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/components_functional_context-menu.md.3bd48599.lean.js b/assets/components_functional_context-menu.md.3bd48599.lean.js new file mode 100644 index 00000000..89b6dbc3 --- /dev/null +++ b/assets/components_functional_context-menu.md.3bd48599.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"ContextMenu","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"createContextMenu","slug":"createcontextmenu"}],"relativePath":"components/functional/context-menu.md","lastUpdated":1697592578589}',p={},o=a('',9);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/components_functional_loading.md.8cd1f253.js b/assets/components_functional_loading.md.8cd1f253.js new file mode 100644 index 00000000..eee3c049 --- /dev/null +++ b/assets/components_functional_loading.md.8cd1f253.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Loading","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"useLoading","slug":"useloading"},{"level":3,"title":"UseLoadingOptions","slug":"useloadingoptions"},{"level":3,"title":"LoadingProps","slug":"loadingprops"},{"level":3,"title":"返回值","slug":"返回值"}],"relativePath":"components/functional/loading.md","lastUpdated":1697592578589}',p={},o=s('

Loading

Usage

<template>\n  <div class="p-5" ref="wrapEl" v-loading="loadingRef" loading-tip="加载中...">\n    <a-alert message="函数方式" />\n\n    <a-button class="my-4 mr-4" type="primary" @click="openFnFullLoading">全屏 Loading</a-button>\n    <a-button class="my-4" type="primary" @click="openFnWrapLoading">容器内 Loading</a-button>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent, reactive, toRefs, ref } from 'vue';\n  import { Loading, useLoading } from '/@/components/Loading';\n  export default defineComponent({\n    components: { Loading },\n    setup() {\n      const [openFullLoading, closeFullLoading] = useLoading({\n        tip: '加载中...',\n      });\n\n      const [openWrapLoading, closeWrapLoading] = useLoading({\n        target: wrapEl,\n        props: {\n          tip: '加载中...',\n          absolute: true,\n        },\n      });\n\n      function openFnFullLoading() {\n        openFullLoading();\n\n        setTimeout(() => {\n          closeFullLoading();\n        }, 2000);\n      }\n\n      function openFnWrapLoading() {\n        openWrapLoading();\n\n        setTimeout(() => {\n          closeWrapLoading();\n        }, 2000);\n      }\n\n      return {\n        openFnFullLoading,\n        openFnWrapLoading,\n        ...toRefs(compState),\n      };\n    },\n  });\n</script>\n

useLoading

使用

import { useLoading } from '/@/components/Loading';\n\nconst [open, close, setTip] = useLoading(opt: Partial<LoadingProps> | Partial<UseLoadingOptions>);\n

UseLoadingOptions

属性类型默认值可选值说明
targetHTMLElement or Ref<HTMLElement>--挂载的 dom 节点
propsLoadingProps--loading 组件参数

LoadingProps

属性类型默认值可选值说明
tipstring--加载文本
sizedefault, small , largedefault-大小
absolutebooleanfalse-绝对定位,为 false 时可以全屏
loadingboolean--当前加载状态
backgroundstring--背景色,
theme'dark' or 'light'light-背景色主题 ,当背景色不为空时使用背景色

返回值

open

打开 loading

close

关闭 loading

setTip

设置加在提示文案(v2.6.2以上版本)

',17);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_functional_loading.md.8cd1f253.lean.js b/assets/components_functional_loading.md.8cd1f253.lean.js new file mode 100644 index 00000000..e3ec1af6 --- /dev/null +++ b/assets/components_functional_loading.md.8cd1f253.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Loading","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"useLoading","slug":"useloading"},{"level":3,"title":"UseLoadingOptions","slug":"useloadingoptions"},{"level":3,"title":"LoadingProps","slug":"loadingprops"},{"level":3,"title":"返回值","slug":"返回值"}],"relativePath":"components/functional/loading.md","lastUpdated":1697592578589}',p={},o=s('',17);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_functional_preview.md.6a63f787.js b/assets/components_functional_preview.md.6a63f787.js new file mode 100644 index 00000000..2cce634d --- /dev/null +++ b/assets/components_functional_preview.md.6a63f787.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Preview","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"createImgPreview","slug":"createimgpreview"},{"level":3,"title":"参数/Options","slug":"参数-options"},{"level":3,"title":"返回值/PreviewActions","slug":"返回值-previewactions"}],"relativePath":"components/functional/preview.md","lastUpdated":1697592578589}',p={},o=s('

Preview

将图片预览组件组件函数化。通过函数方便创建组件

Usage

<template>\n  <div class="p-4">\n    <Alert message="有预览图" type="info" />\n    <div class="flex justify-center mt-4">\n      <img :src="img" v-for="img in imgList" :key="img" class="mr-2" @click="handleClick(img)" />\n    </div>\n    <Alert message="无预览图" type="info" />\n    <a-button @click="handlePreview" type="primary" class="mt-4">预览图片</a-button>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { Alert } from 'ant-design-vue';\n  import { createImgPreview } from '/@/components/Preview/index';\n  const imgList: string[] = [\n    'https://picsum.photos/id/66/346/216',\n    'https://picsum.photos/id/67/346/216',\n    'https://picsum.photos/id/68/346/216',\n  ];\n  export default defineComponent({\n    components: { Alert },\n    setup() {\n      function handleClick(img: string) {\n        createImgPreview({ imageList: [img] });\n      }\n\n      function handlePreview() {\n        createImgPreview({ imageList: imgList });\n      }\n      return { imgList, handleClick, handlePreview };\n    },\n  });\n</script>\n

createImgPreview

参数/Options

属性类型默认值可选值说明
imgListstring[]--图片列表
indexnumber0-初始预览时的图片索引
scaleStepnumber--缩放步进值(每次缩放的幅度)。默认为自动(当前缩放值的10%)
defaultWidthnumber--默认宽度(单位px)。当提供此值时,所有图片初始时都会被缩放至此宽度
maskClosablebooleanfalsetrue/false点击遮罩时是否自动关闭预览
rememberStatebooleanfalsetrue/false是否记住每张图片各自的缩放状态
onImgLoad({ index: number, url: string, dom: HTMLImageElement }) => void--图片加载成功时的回调函数
onImgError({ index: number, url: string, dom: HTMLImageElement }) => void--图片加载失败时的回调函数

返回值/PreviewActions

可用于控制当前预览状态

interface PreviewActions {\n  // 重置状态\n  resume: () => void;\n  // 关闭预览\n  close: () => void;\n  // 显示前一张\n  prev: () => void;\n  // 显示后一张\n  next: () => void;\n  // 设置缩放比例(针对当前图片)\n  setScale: (scale: number) => void;\n  // 设置旋转角度(针对当前图片)\n  setRotate: (rotate: number) => void;\n}\n
',10);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_functional_preview.md.6a63f787.lean.js b/assets/components_functional_preview.md.6a63f787.lean.js new file mode 100644 index 00000000..3118b2ef --- /dev/null +++ b/assets/components_functional_preview.md.6a63f787.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Preview","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"createImgPreview","slug":"createimgpreview"},{"level":3,"title":"参数/Options","slug":"参数-options"},{"level":3,"title":"返回值/PreviewActions","slug":"返回值-previewactions"}],"relativePath":"components/functional/preview.md","lastUpdated":1697592578589}',p={},o=s('',10);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_glob_button.md.30d4499c.js b/assets/components_glob_button.md.30d4499c.js new file mode 100644 index 00000000..cb614e69 --- /dev/null +++ b/assets/components_glob_button.md.30d4499c.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.8cddb23b.js";const s='{"title":"button 按钮","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/glob/button.md","lastUpdated":1697592578589}',o={},p=n('

button 按钮

二次封装按钮组件,且使用相同的组件名替换全局的 a-button 组件

TIP

  • 按钮不需要 import,已经全局注册,直接使用 a-button 标签即可
  • 如果是 Tsx 文件,需要手动 import

Usage

<template>\n  <a-button color="success">成功按钮</a-button>\n  <a-button color="error">错误按钮</a-button>\n  <a-button color="warning">警告按钮</a-button>\n</template>\n

Props

提示

保持 ant design button 组件 原有功能的情况下扩展以下属性

属性类型默认值说明
color'error','warning', 'success'-按钮的颜色场景状态颜色,
preIconstring-按钮文本前图标,参考 Icon 组件
postIconstring-按钮文本后图标,参考 Icon 组件
iconSizenumber14按钮图标大小
',8);o.render=function(n,s,o,e,c,l){return t(),a("div",null,[p])};export default o;export{s as __pageData}; diff --git a/assets/components_glob_button.md.30d4499c.lean.js b/assets/components_glob_button.md.30d4499c.lean.js new file mode 100644 index 00000000..76612889 --- /dev/null +++ b/assets/components_glob_button.md.30d4499c.lean.js @@ -0,0 +1 @@ +import{o as t,c as a,a as n}from"./app.8cddb23b.js";const s='{"title":"button 按钮","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/glob/button.md","lastUpdated":1697592578589}',o={},p=n('',8);o.render=function(n,s,o,e,c,l){return t(),a("div",null,[p])};export default o;export{s as __pageData}; diff --git a/assets/components_icon.md.59ee5f53.js b/assets/components_icon.md.59ee5f53.js new file mode 100644 index 00000000..01251456 --- /dev/null +++ b/assets/components_icon.md.59ee5f53.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"icon 图标组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"Icon","slug":"icon"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":2,"title":"SvgIcon","slug":"svgicon"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Props","slug":"props-1"},{"level":2,"title":"IconPicker","slug":"iconpicker"},{"level":3,"title":"Usage","slug":"usage-2"},{"level":3,"title":"Props","slug":"props-2"}],"relativePath":"components/icon.md","lastUpdated":1697592578589}',p={},o=a('

icon 图标组件

Icon

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

icon 组件位于 src/components/Icon

TIP

icon 的值可以在 IconifyNetlify 上查询

Usage

<template>\n  <Icon icon="gg:loadbar-doc"></Icon>\n</template>\n\n<script>\n  import { defineComponent } from 'vue';\n  import { Icon } from '/@/components/Icon';\n  export default defineComponent({\n    components: { Icon },\n  });\n</script>\n

Props

属性类型默认值说明
iconstring-图标名
colorstring-图标颜色
sizenumber16图标大小
prefixstring-图标前缀

提示

如果 icon 值以 |svg 结尾,则会渲染成 SvgIcon 组件

SvgIcon

用于使用项目 svg 雪碧图

Usage

<template>\n  <div>\n    <SvgIcon name="test"> </SvgIcon>\n  </div>\n</template>\n<script>\n  import { SvgIcon } from '/@/components/Icon';\n  import { defineComponent } from 'vue';\n  export default defineComponent({\n    components: { SvgIcon },\n  });\n</script>\n

Props

属性类型默认值说明
namestring-svg 图标名
sizenumber16图标大小

IconPicker

本组件详细说明请参阅图标选择器

Usage

<template>\n  <div>\n    <IconPicker />\n  </div>\n</template>\n<script>\n  import { IconPicker } from '/@/components/Icon';\n  import { defineComponent } from 'vue';\n  export default defineComponent({\n    components: { IconPicker },\n  });\n</script>\n

Props

属性类型默认值说明
widthstring100%宽度
pageSizenumber140每页显示的图标数
copybooleanfalse是否可以复制
modestringiconify备选图标池,为 svg 时,会读取所有 svg sprite 图标。详见下方说明

mode 说明

  • modeiconify时,会使用预生成的图标集数据作为备选图标池
  • modesvg时,会使用 /src/assets/icons 下的所有svg图标(可包含一级子目录)作为备选图标池,详见vite-plugin-svg-icons
',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_icon.md.59ee5f53.lean.js b/assets/components_icon.md.59ee5f53.lean.js new file mode 100644 index 00000000..b9f42910 --- /dev/null +++ b/assets/components_icon.md.59ee5f53.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"icon 图标组件","description":"","frontmatter":{},"headers":[{"level":2,"title":"Icon","slug":"icon"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":2,"title":"SvgIcon","slug":"svgicon"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Props","slug":"props-1"},{"level":2,"title":"IconPicker","slug":"iconpicker"},{"level":3,"title":"Usage","slug":"usage-2"},{"level":3,"title":"Props","slug":"props-2"}],"relativePath":"components/icon.md","lastUpdated":1697592578589}',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_introduction.md.d908af44.js b/assets/components_introduction.md.d908af44.js new file mode 100644 index 00000000..5f3e1710 --- /dev/null +++ b/assets/components_introduction.md.d908af44.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"前言","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"}],"relativePath":"components/introduction.md","lastUpdated":1697592578589}',p={},o=s('

前言

注意事项

组件的 defaultXXX 属性不要使用,ant-design-vue 2.2 版本之后将会逐步移除。二次封装的组件也不兼容 defaultXXX 属性。

Usage

该项目的组件大部分没有进行全局注册。采用了按需引入注册方式,如下

<template>\n  <ConfigProvider>\n    <router-view />\n  </ConfigProvider>\n</template>\n\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { ConfigProvider } from 'ant-design-vue';\n  export default defineComponent({\n    name: 'App',\n    components: { ConfigProvider },\n  });\n</script>\n
',5);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_introduction.md.d908af44.lean.js b/assets/components_introduction.md.d908af44.lean.js new file mode 100644 index 00000000..0de261a8 --- /dev/null +++ b/assets/components_introduction.md.d908af44.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"前言","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"}],"relativePath":"components/introduction.md","lastUpdated":1697592578589}',p={},o=s('',5);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_json-preview.md.ca86822d.js b/assets/components_json-preview.md.ca86822d.js new file mode 100644 index 00000000..bcc0f96a --- /dev/null +++ b/assets/components_json-preview.md.ca86822d.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"JsonPreview","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/json-preview.md","lastUpdated":1697592578589}',p={},o=s('

JsonPreview

json 数据预览组件

Usage

<template>\n  <JsonPreview :data="data" />\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { JsonPreview } from '/@/components/CodeEditor';\n\n  export default defineComponent({\n    components: { JsonPreview },\n    setup() {\n      return {\n        data: {},\n      };\n    },\n  });\n</script>\n

Props

属性类型默认值可选值说明
dataobject--需要预览的 Json 数据
',6);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_json-preview.md.ca86822d.lean.js b/assets/components_json-preview.md.ca86822d.lean.js new file mode 100644 index 00000000..82fe3e4a --- /dev/null +++ b/assets/components_json-preview.md.ca86822d.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"JsonPreview","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/json-preview.md","lastUpdated":1697592578589}',p={},o=s('',6);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_lazy-container.md.7fe04b3b.js b/assets/components_lazy-container.md.7fe04b3b.js new file mode 100644 index 00000000..2a4fa862 --- /dev/null +++ b/assets/components_lazy-container.md.7fe04b3b.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"LazyContainer","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/lazy-container.md","lastUpdated":1697592578589}',p={},o=s('

LazyContainer

延时加载/懒加载组件, 只在组件可见或者延迟一段时间才进行加载

Usage

<template>\n  <div class="p-4 lazy-base-demo">\n    <div class="lazy-base-demo-wrap">\n      <h1>向下滚动</h1>\n      <LazyContainer @init="() => {}">\n        <TargetContent />\n        <template #skeleton>\n          <Skeleton :rows="10" />\n        </template>\n      </LazyContainer>\n    </div>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { Skeleton } from 'ant-design-vue';\n  import TargetContent from './TargetContent.vue';\n  import { LazyContainer } from '/@/components/Container/index';\n  export default defineComponent({\n    components: { LazyContainer, TargetContent, Skeleton },\n  });\n</script>\n<style lang="less" scoped>\n  .lazy-base-demo {\n    &-wrap {\n      display: flex;\n      width: 50%;\n      height: 2000px;\n      margin: 20px auto;\n      text-align: center;\n      background: #fff;\n      justify-content: center;\n      flex-direction: column;\n      align-items: center;\n    }\n\n    h1 {\n      height: 1300px;\n      margin: 20px 0;\n    }\n  }\n</style>\n

Props

属性类型默认值可选值说明
timeoutnumber--等待时间,如果指定了时间,不论可见与否,在指定时间之后自动加载
viewportHTMLElement--组件所在的视口,如果组件是在页面容器内滚动,视口就是该容器
thresholdstring0px-预加载阈值, css 单位
direction'vertical', 'horizontal' , vertical-视口的滚动方向, vertical 代表垂直方向,horizontal 代表水平方向
tagstring'div-包裹组件的外层容器的标签名
transitionNamestring'lazy-container-transition 动画 name
maxWaitingTimenumber'80-最大等待时间

Events

事件回调参数说明
init()=>void初始化之后

Slots

名称说明
default默认区域
skeleton懒加载骨架屏
',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_lazy-container.md.7fe04b3b.lean.js b/assets/components_lazy-container.md.7fe04b3b.lean.js new file mode 100644 index 00000000..6d52edb5 --- /dev/null +++ b/assets/components_lazy-container.md.7fe04b3b.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"LazyContainer","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/lazy-container.md","lastUpdated":1697592578589}',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/components_loading.md.9345e82a.js b/assets/components_loading.md.9345e82a.js new file mode 100644 index 00000000..8597d5c3 --- /dev/null +++ b/assets/components_loading.md.9345e82a.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Loading","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"}],"relativePath":"components/loading.md","lastUpdated":1697592578589}',p={},o=s('

Loading

Usage

<template>\n  <div class="p-5" ref="wrapEl" v-loading="loadingRef" loading-tip="加载中...">\n    <a-button class="my-4 mr-4" type="primary" @click="openCompFullLoading">全屏 Loading</a-button>\n    <a-button class="my-4" type="primary" @click="openCompAbsolute">容器内 Loading</a-button>\n    <Loading :loading="loading" :absolute="absolute" :tip="tip" />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent, reactive, toRefs, ref } from 'vue';\n  import { Loading } from '/@/components/Loading';\n  export default defineComponent({\n    components: { Loading },\n    setup() {\n      const compState = reactive({\n        absolute: false,\n        loading: false,\n        tip: '加载中...',\n      });\n\n      function openLoading(absolute: boolean) {\n        compState.absolute = absolute;\n        compState.loading = true;\n        setTimeout(() => {\n          compState.loading = false;\n        }, 2000);\n      }\n\n      function openCompFullLoading() {\n        openLoading(false);\n      }\n\n      function openCompAbsolute() {\n        openLoading(true);\n      }\n\n      return {\n        openCompFullLoading,\n        openCompAbsolute,\n        ...toRefs(compState),\n      };\n    },\n  });\n</script>\n

Props

属性类型默认值可选值说明
tipstring--加载文本
sizedefault, small , largedefault-大小
absolutebooleanfalse-绝对定位,为 false 时可以全屏
loadingboolean--当前加载状态
backgroundstring--背景色
theme'dark' or 'light'light-背景色主题,当背景色不为空时使用背景色
',5);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_loading.md.9345e82a.lean.js b/assets/components_loading.md.9345e82a.lean.js new file mode 100644 index 00000000..1e23e116 --- /dev/null +++ b/assets/components_loading.md.9345e82a.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Loading","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"}],"relativePath":"components/loading.md","lastUpdated":1697592578589}',p={},o=s('',5);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_markdown.md.12502c77.js b/assets/components_markdown.md.12502c77.js new file mode 100644 index 00000000..5948062e --- /dev/null +++ b/assets/components_markdown.md.12502c77.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Markdown","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":3,"title":"Methods","slug":"methods"}],"relativePath":"components/markdown.md","lastUpdated":1697592578589}',p={},o=s('

Markdown

基于 Vditor 的 MarkDown 编辑器

Usage

<template>\n  <div class="p-4">\n    <a-button @click="toggleTheme" class="mb-2" type="primary">黑暗主题</a-button>\n    <MarkDown v-model:value="value" ref="markDownRef" />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent, ref, unref } from 'vue';\n  import { MarkDown, MarkDownActionType } from '/@/components/Markdown';\n  export default defineComponent({\n    components: { MarkDown },\n    setup() {\n      const markDownRef = ref<Nullable<MarkDownActionType>>(null);\n      const valueRef = ref(`\n# title\n\n# content\n`);\n\n      function toggleTheme() {\n        const markDown = unref(markDownRef);\n        if (!markDown) return;\n        const vditor = markDown.getVditor();\n        vditor.setTheme('dark');\n      }\n      return {\n        value: valueRef,\n        toggleTheme,\n        markDownRef,\n      };\n    },\n  });\n</script>\n

Props

TIP

除以下两个外,props 还可以传入 vidtor 的所有属性。可用 v-bind 统一绑定

属性类型默认值可选值说明
v-modelstring--双向绑定文本值
heightnumber--高度

Methods

名称回调参数说明
getVditorFunction获取 vditor 实例
',9);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_markdown.md.12502c77.lean.js b/assets/components_markdown.md.12502c77.lean.js new file mode 100644 index 00000000..ab0a3c45 --- /dev/null +++ b/assets/components_markdown.md.12502c77.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Markdown","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":3,"title":"Methods","slug":"methods"}],"relativePath":"components/markdown.md","lastUpdated":1697592578589}',p={},o=s('',9);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_modal.md.ab172b28.js b/assets/components_modal.md.ab172b28.js new file mode 100644 index 00000000..a14f9fea --- /dev/null +++ b/assets/components_modal.md.ab172b28.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Modal 弹窗","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"useModal","slug":"usemodal"},{"level":2,"title":"useModalInner","slug":"usemodalinner"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/modal.md","lastUpdated":1697592578589}',p={},o=s('

Modal 弹窗

对 antv 的 modal 组件进行封装,扩展拖拽,全屏,自适应高度等功能

代码路径 src/components/Modal

Usage

由于弹窗内代码一般作为单文件组件存在,也推荐这样做,所以示例都为单文件组件形式

TIP

注意 v-bind="$attrs"记得写,用于将弹窗组件的 attribute 传入 BasicModal 组件

// Modal.vue\n<template>\n  <BasicModal v-bind="$attrs" title="Modal Title" :helpMessage="['提示1', '提示2']">\n    Modal Info.\n  </BasicModal>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicModal } from '/@/components/Modal';\n  export default defineComponent({\n    components: { BasicModal },\n    setup() {\n      return {};\n    },\n  });\n</script>\n

页面引用弹窗

// Page.vue\n<template>\n  <div class="px-10">\n    <Modal @register="register" />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { useModal } from '/@/components/Modal';\n  import Modal from './Modal.vue';\n  export default defineComponent({\n    components: { Modal },\n    setup() {\n      const [register, { openModal }] = useModal();\n      return {\n        register,\n        openModal,\n      };\n    },\n  });\n</script>\n

useModal

用于外部组件调用

useModal 用于操作组件

const [register, { openModal, setModalProps }] = useModal();\n

register

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

原理其实很简单,就是 vue 的组件子传父通信,内部通过 emit("register",instance) 实现。

同时独立出去的组件需要将 attrs 绑定到 BasicModal 上面。

<template>\n  <BasicModal v-bind="$attrs"></BasicModal>\n</template>\n

openModal

用于打开/关闭弹窗

// true/false: 打开关闭弹窗\n// data: 传递到子组件的数据\nopenModal(true, data);\n

closeModal

用于关闭弹窗

closeModal();\n

setModalProps

用于更改 modal 的 props 参数因为 modal 内容独立成组件,如果在外部页面需要更改 props 可能比较麻烦,所以提供 setModalProps 方便更改内部 modal 的 props

Props 内容可以见下方

setModalProps(props);\n

useModalInner

用于独立的 Modal 内部调用

Usage

<template>\n  <BasicModal\n    v-bind="$attrs"\n    @register="register"\n    title="Modal Title"\n    :helpMessage="['提示1', '提示2']"\n  >\n    <a-button type="primary" @click="closeModal" class="mr-2">从内部关闭弹窗</a-button>\n\n    <a-button type="primary" @click="setModalProps">从内部修改title</a-button>\n  </BasicModal>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicModal, useModalInner } from '/@/components/Modal';\n  export default defineComponent({\n    components: { BasicModal },\n    setup() {\n      const [register, { closeModal, setModalProps }] = useModalInner();\n      return {\n        register,\n        closeModal,\n        setModalProps: () => {\n          setModalProps({ title: 'Modal New Title' });\n        },\n      };\n    },\n  });\n</script>\n

useModalInner用于操作独立组件

const [register, { closeModal, setModalProps }] = useModalInner(callback);\n

callback

type: (data:any)=>void

回调函数用于接收 openModal 第二个参数传递的值

useModal((data: any) => {\n  console.log(data);\n});\n

closeModal

用于关闭弹窗

closeModal();\n

changeOkLoading

用于修改确认按钮的 loading 状态

changeOkLoading(true);\n

changeLoading

用于修改 modal 的 loading 状态

// true or false\nchangeLoading(true);\n

setModalProps

用于更改 modal 的 props 参数因为 modal 内容独立成组件,如果在外部页面需要更改 props 可能比较麻烦,所以提供 setModalProps 方便更改内部 modal 的 props

Props 内容可以见下方

Props

TIP

除以下参数外,组件库文档内的 props 也都支持,具体可以参考 antv modal

属性类型默认值可选值说明
titlestring--modal 标题
heightnumber--固定 modal 的高度
minHeightnumber--设置 modal 的最小高度
draggablebooleantruetrue/false是否开启拖拽
useWrapperbooleantruetrue/false是否开启自适应高度,开启后会跟随屏幕变化自适应内容,并出现滚动条
wrapperFooterOffsetnumber0-开启是适应高度后,如果超过屏幕高度,底部和顶部会保持一样的间距,该参数可以用来缩小底部的间距
canFullscreenbooleantruetrue/false是否可以进行全屏
defaultFullscreenbooleanfalsetrue/false默认全屏
loadingbooleanfalsetrue/falseloading 状态
loadingTipstring--loading 文本
showCancelBtnbooleantruetrue/false显示关闭按钮
showOkBtnbooleantruetrue/false显示确认按钮
helpMessagestring , string[]--标题右侧提示文本
centeredbooleanfalsetrue/false是否居中弹窗
cancelTextstring'关闭'-关闭按钮文本
okTextstring'保存'-确认按钮文本
closeFunc() => Promise<boolean>关闭函数-关闭前执行,返回 true 则关闭,否则不关闭

Events

事件回调参数说明
okfunction(e)点击确定回调
cancelfunction(e)点击取消回调
visible-change(visible:boolean)=>{}打开或者关闭触发

Slots

名称说明
default默认区域
footer底部区域(会替换掉默认的按钮)
insertFooter关闭按钮的左边(不使用footer插槽时有效)
centerFooter关闭按钮和确认按钮的中间(不使用footer插槽时有效)
appendFooter确认按钮的右边(不使用footer插槽时有效)
',57);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_modal.md.ab172b28.lean.js b/assets/components_modal.md.ab172b28.lean.js new file mode 100644 index 00000000..946e822c --- /dev/null +++ b/assets/components_modal.md.ab172b28.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Modal 弹窗","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"useModal","slug":"usemodal"},{"level":2,"title":"useModalInner","slug":"usemodalinner"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/modal.md","lastUpdated":1697592578589}',p={},o=s('',57);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_page.md.f76be211.js b/assets/components_page.md.f76be211.js new file mode 100644 index 00000000..f1b7ef05 --- /dev/null +++ b/assets/components_page.md.f76be211.js @@ -0,0 +1 @@ +import{o as a,c as n,a as t}from"./app.8cddb23b.js";const s='{"title":"Page","description":"","frontmatter":{},"headers":[{"level":2,"title":"PageWrapper","slug":"pagewrapper"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":3,"title":"Slots","slug":"slots"},{"level":2,"title":"PageFooter","slug":"pagefooter"},{"level":3,"title":"使用","slug":"使用"},{"level":3,"title":"Slots","slug":"slots-1"}],"relativePath":"components/page.md","lastUpdated":1697592578589}',p={},e=t('

Page

页面相关组件

PageWrapper

用于包裹页面组件

Usage

<template>\n  <div>\n    <PageWrapper>\n      <template #left>left</template>\n      <template #right>right</template>\n    </PageWrapper>\n  </div>\n</template>\n<script>\n  import { PageWrapper } from '/@/components/Page';\n  import { defineComponent } from 'vue';\n  export default defineComponent({\n    components: { PageWrapper },\n    setup() {\n      return {};\n    },\n  });\n</script>\n

Props

属性类型默认值说明
titlestring-pageHeader title
dense是否缩小主体区域false为 true 将会取消 padding/margin
contentstring-pageHeader Content 内容
contentStyleobject-主体区域样式
contentClassstring-主体区域 class
contentBackgroundboolean-主体区域背景
contentFullHeightbooleanfalse主体区域是否占满整个屏幕高度
fixedHeightbooleanfalse固定主体区域高度

Slots

pageHeader 的 slot 都支持

名称说明
leftFooterPageFooter 左侧区域
rightFooterPageFooter 右侧区域
headerContentpageHeader 主体内容
default主体区域

用于页面底部工具栏

使用

<template>\n  <div>\n    <PageFooter>\n      <template #left>left</template>\n      <template #right>right</template>\n    </PageFooter>\n  </div>\n</template>\n<script>\n  import { PageFooter } from '/@/components/Page';\n  import { defineComponent } from 'vue';\n  export default defineComponent({\n    components: { PageFooter },\n    setup() {\n      return {};\n    },\n  });\n</script>\n

Slots

名称说明
left左侧区域
right右侧区域
',17);p.render=function(t,s,p,o,c,l){return a(),n("div",null,[e])};export default p;export{s as __pageData}; diff --git a/assets/components_page.md.f76be211.lean.js b/assets/components_page.md.f76be211.lean.js new file mode 100644 index 00000000..a0642ca1 --- /dev/null +++ b/assets/components_page.md.f76be211.lean.js @@ -0,0 +1 @@ +import{o as a,c as n,a as t}from"./app.8cddb23b.js";const s='{"title":"Page","description":"","frontmatter":{},"headers":[{"level":2,"title":"PageWrapper","slug":"pagewrapper"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":3,"title":"Slots","slug":"slots"},{"level":2,"title":"PageFooter","slug":"pagefooter"},{"level":3,"title":"使用","slug":"使用"},{"level":3,"title":"Slots","slug":"slots-1"}],"relativePath":"components/page.md","lastUpdated":1697592578589}',p={},e=t('',17);p.render=function(t,s,p,o,c,l){return a(),n("div",null,[e])};export default p;export{s as __pageData}; diff --git a/assets/components_pop-confirm-button.md.e437f662.js b/assets/components_pop-confirm-button.md.e437f662.js new file mode 100644 index 00000000..46209540 --- /dev/null +++ b/assets/components_pop-confirm-button.md.e437f662.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"PopConfirmButton 按钮","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/pop-confirm-button.md","lastUpdated":1697592578589}',p={},o=s('

PopConfirmButton 按钮

带有 PopConfirm 下拉菜单功能的按钮

Usage

<template>\n  <PopConfirmButton>按钮文本</PopConfirmButton>\n</template>\n\n<script>\n  import { defineComponent } from 'vue';\n  import { PopConfirmButton } from '/@/components/Button';\n  export default defineComponent({\n    components: { PopConfirmButton },\n  });\n</script>\n

Props

提示

保持 anv design popconfirm 组件 原有功能的情况下扩展以下属性

属性类型默认值说明
enablebooleantrue是否启用下拉菜单,为 false 则显示默认按钮
',7);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_pop-confirm-button.md.e437f662.lean.js b/assets/components_pop-confirm-button.md.e437f662.lean.js new file mode 100644 index 00000000..c91f1e47 --- /dev/null +++ b/assets/components_pop-confirm-button.md.e437f662.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"PopConfirmButton 按钮","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/pop-confirm-button.md","lastUpdated":1697592578589}',p={},o=s('',7);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_qrcode.md.5d95ff15.js b/assets/components_qrcode.md.5d95ff15.js new file mode 100644 index 00000000..6937b305 --- /dev/null +++ b/assets/components_qrcode.md.5d95ff15.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"QrCode","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Methods","slug":"methods"},{"level":2,"title":"事件","slug":"事件"}],"relativePath":"components/qrcode.md","lastUpdated":1697592578589}',p={},o=a('

QrCode

用于生成二维码的组件

Usage

<template>\n  <QrCode :value="qrCodeUrl" />\n</template>\n<script lang="ts">\n  import { defineComponent, ref, unref } from 'vue';\n  import { QrCode, QrCodeActionType } from '/@/components/Qrcode/index';\n  import LogoImg from '/@/assets/images/logo.png';\n  const qrCodeUrl = 'https://www.vvbin.cn';\n  export default defineComponent({\n    components: { QrCode },\n    setup() {\n      const qrRef = ref<Nullable<QrCodeActionType>>(null);\n      function download() {\n        const qrEl = unref(qrRef);\n        if (!qrEl) return;\n        qrEl.download('文件名');\n      }\n      return {\n        qrCodeUrl,\n        LogoImg,\n        download,\n        qrRef,\n      };\n    },\n  });\n</script>\n<style scoped>\n  .qrcode-demo-item {\n    width: 30%;\n    margin-right: 1%;\n  }\n</style>\n

Props

属性类型默认值可选值说明
valuestring--二维码地址
optionsQRCodeRenderersOptions--二维码配置 ,见 QRCodeRenderersOptions
widthnumber2-宽度
logostring|LogoType--中间 logo 配置,见 LogoType
tag渲染标签canvascanvas | imgimg 不支持内嵌 logo

QRCodeRenderersOptions

/**\n * 定义margin的宽度。.\n * Default: 4\n */\nmargin?: number;\n/**\n * 比例因子。值1表示每个模块1像素(黑点)。\n * Default: 4\n */\nscale?: number;\n/**\n * 为输出图像强制指定宽度。\n * 如果宽度太小而不能包含qr符号,则此选项将被忽略。\n * 优先于规模。\n */\nwidth?: number;\ncolor?: {\n  /**\n   * 暗模块的颜色。值必须为十六进制格式(RGBA).\n   * 注意:深色应始终比color.light暗。.\n   * Default: #000000ff\n   */\n  dark?: string;\n  /**\n   * 照明模块的颜色。值必须为十六进制格式(RGBA).\n   * Default: #ffffffff\n   */\n  light?: string;\n};\n\n

LogoType

{\n  // logo图片\n  src: string;\n  // logo大小\n  logoSize: number;\n  // 背景颜色\n  bgColor: string;\n  // logo圆角\n  logoRadius: number;\n}\n

Methods

名称回调参数说明
downloadFunction(fileName:string)下载

事件

名称回调参数说明
done(data: QrcodeDoneEventParams)=>void绘制完成
error(error)=>void生成二维码时发生错误

QrcodeDoneEventParams

{\n  url: string;  // 二维码DataURL数据\n  ctx?: CanvasRenderingContext2D;  // 该对象为画布的2D渲染上下文,仅在tag为canvas时有效,可用于自定义绘制\n}\n

done 事件回调中可以对二维码进行自定义的绘制,示例代码如下:

<QrCode\n  :value="qrCodeUrl"\n  :width="200"\n  @done="onQrcodeDone"\n/>\n
function onQrcodeDone({ ctx }) {\n  if (ctx instanceof CanvasRenderingContext2D) {\n    // 额外绘制\n    ctx.fillStyle = 'black';\n    ctx.font = '16px "微软雅黑"';\n    ctx.textBaseline = 'bottom';\n    ctx.textAlign = 'center';\n    ctx.fillText('你帅你先扫', 100, 195, 200);\n  }\n}\n

有关 CanvasRenderingContext2D 的更多资料以及绘制方法,请参考MDN

',20);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_qrcode.md.5d95ff15.lean.js b/assets/components_qrcode.md.5d95ff15.lean.js new file mode 100644 index 00000000..a482cf0f --- /dev/null +++ b/assets/components_qrcode.md.5d95ff15.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"QrCode","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Methods","slug":"methods"},{"level":2,"title":"事件","slug":"事件"}],"relativePath":"components/qrcode.md","lastUpdated":1697592578589}',p={},o=a('',20);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_scroll-container.md.cb595324.js b/assets/components_scroll-container.md.cb595324.js new file mode 100644 index 00000000..3f70de98 --- /dev/null +++ b/assets/components_scroll-container.md.cb595324.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"ScrollContainer","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Methods","slug":"methods"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/scroll-container.md","lastUpdated":1697592578589}',p={},o=s('

ScrollContainer

参考 element-ui 的 el-scrollbar 组件实现

滚动容器组件

Usage

<template>\n  <div class="p-4">\n    <div class="my-4">\n      <a-button @click="scrollTo(100)">滚动到100px位置</a-button>\n      <a-button @click="scrollTo(800)">滚动到800px位置</a-button>\n      <a-button @click="scrollTo(0)">滚动到顶部</a-button>\n      <a-button @click="scrollBottom()">滚动到底部</a-button>\n    </div>\n    <div class="scroll-wrap">\n      <ScrollContainer ref="scrollRef">\n        <ul>\n          <template v-for="index in 100" :key="index">\n            <li>{{ index }}</li>\n          </template>\n        </ul>\n      </ScrollContainer>\n    </div>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent, ref, unref } from 'vue';\n  import { CollapseContainer } from '/@/components/Container/index';\n  import { ScrollContainer, ScrollActionType } from '/@/components/Container/index';\n  export default defineComponent({\n    components: { CollapseContainer, ScrollContainer },\n    setup() {\n      const scrollRef = ref<Nullable<ScrollActionType>>(null);\n      const getScroll = () => {\n        const scroll = unref(scrollRef);\n        if (!scroll) {\n          throw new Error('scroll is Null');\n        }\n        return scroll;\n      };\n\n      function scrollTo(top: number) {\n        getScroll()?.scrollTo(top);\n      }\n\n      function scrollBottom() {\n        getScroll()?.scrollBottom();\n      }\n\n      return {\n        scrollTo,\n        scrollRef,\n        scrollBottom,\n      };\n    },\n  });\n</script>\n<style lang="less" scoped>\n  .scroll-wrap {\n    width: 50%;\n    height: 300px;\n    background: #fff;\n  }\n</style>\n

Methods

名称回调参数说明
getScrollWrap()=>HtmlElement获取滚动容器 el
scrollBottomFunction滚动到底部
scrollToFunction(to:number,duration = 500)滚动到指定位置

Slots

名称说明
default默认区域
',9);p.render=function(s,t,p,c,e,l){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_scroll-container.md.cb595324.lean.js b/assets/components_scroll-container.md.cb595324.lean.js new file mode 100644 index 00000000..5ffdd566 --- /dev/null +++ b/assets/components_scroll-container.md.cb595324.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"ScrollContainer","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Methods","slug":"methods"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/scroll-container.md","lastUpdated":1697592578589}',p={},o=s('',9);p.render=function(s,t,p,c,e,l){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_strength-meter.md.709cd846.js b/assets/components_strength-meter.md.709cd846.js new file mode 100644 index 00000000..2523f804 --- /dev/null +++ b/assets/components_strength-meter.md.709cd846.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"StrengthMeter","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"}],"relativePath":"components/strength-meter.md","lastUpdated":1697592578589}',p={},e=s('

StrengthMeter

用于校验密码强度

Usage

<template>\n  <div class="p-4 flex justify-center">\n    <div class="demo-wrap p-10">\n      <StrengthMeter placeholder="默认" />\n      <StrengthMeter placeholder="禁用" disabled />\n      <br />\n      <StrengthMeter placeholder="隐藏input" :show-input="false" value="!@#qwe12345" />\n    </div>\n  </div>\n</template>\n\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import StrengthMeter from '/@/components/StrengthMeter/index';\n  export default defineComponent({\n    components: {\n      StrengthMeter,\n    },\n  });\n</script>\n<style lang="less" scoped>\n  .demo-wrap {\n    width: 50%;\n    background: #fff;\n    border-radius: 10px;\n  }\n</style>\n

Props

属性类型默认值可选值说明
valuestring--校验的值
showInputbooleantrue-是否显示 input
disabledbooleanfalse-是否禁用

Events

事件回调参数说明
score-changenumber强度值改变触发
changestringinput 值改变触发
',8);p.render=function(s,t,p,o,c,l){return n(),a("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/components_strength-meter.md.709cd846.lean.js b/assets/components_strength-meter.md.709cd846.lean.js new file mode 100644 index 00000000..a2a3ef9b --- /dev/null +++ b/assets/components_strength-meter.md.709cd846.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"StrengthMeter","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"}],"relativePath":"components/strength-meter.md","lastUpdated":1697592578589}',p={},e=s('',8);p.render=function(s,t,p,o,c,l){return n(),a("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/components_table.md.3c62f0f9.js b/assets/components_table.md.3c62f0f9.js new file mode 100644 index 00000000..cf66e879 --- /dev/null +++ b/assets/components_table.md.3c62f0f9.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Table 表格","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":3,"title":"示例","slug":"示例"},{"level":3,"title":"template 示例","slug":"template-示例"},{"level":3,"title":"BasicColumn 和 tableAction 通过权限和业务控制显示隐藏的示例","slug":"basiccolumn-和-tableaction-通过权限和业务控制显示隐藏的示例"},{"level":2,"title":"useTable","slug":"usetable"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Methods","slug":"methods"},{"level":2,"title":"Props","slug":"props"},{"level":3,"title":"TableSetting","slug":"tablesetting"},{"level":2,"title":"BasicColumn","slug":"basiccolumn"},{"level":3,"title":"EditComponentType","slug":"editcomponenttype"},{"level":3,"title":"CellFormat","slug":"cellformat"},{"level":2,"title":"事件","slug":"事件"},{"level":2,"title":"Slots","slug":"slots"},{"level":2,"title":"Form-Slots","slug":"form-slots"},{"level":2,"title":"ColumnSetting组件","slug":"columnsetting组件"},{"level":2,"title":"内置组件(只能用于表格内部)","slug":"内置组件(只能用于表格内部)"},{"level":3,"title":"TableAction","slug":"tableaction"},{"level":3,"title":"TableImg","slug":"tableimg"},{"level":2,"title":"全局配置","slug":"全局配置"}],"relativePath":"components/table.md","lastUpdated":1697592578589}',p={},o=s('

Table 表格

antv 的 table 组件进行封装

如果文档内没有,可以尝试在在线示例内寻找

Usage

示例

<template>\n  <div class="p-4">\n    <BasicTable\n      title="基础示例"\n      titleHelpMessage="温馨提醒"\n      :columns="columns"\n      :dataSource="data"\n      :canResize="canResize"\n      :loading="loading"\n      :striped="striped"\n      :bordered="border"\n      :pagination="{ pageSize: 20 }"\n    >\n      <template #toolbar>\n        <a-button type="primary"> 操作按钮 </a-button>\n      </template>\n    </BasicTable>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent, ref } from 'vue';\n  import { BasicTable } from '/@/components/Table';\n  import { getBasicColumns, getBasicData } from './tableData';\n\n  export default defineComponent({\n    components: { BasicTable },\n    setup() {\n      return {\n        columns: getBasicColumns(),\n        data: getBasicData(),\n      };\n    },\n  });\n</script>\n

template 示例

所有可调用函数见下方 Methods 说明

<template>\n  <div class="p-4">\n    <BasicTable\n      :canResize="false"\n      title="RefTable示例"\n      titleHelpMessage="使用Ref调用表格内方法"\n      ref="tableRef"\n      :api="api"\n      :columns="columns"\n      rowKey="id"\n      :rowSelection="{ type: 'checkbox' }"\n    />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent, ref, unref } from 'vue';\n  import { BasicTable, TableActionType } from '/@/components/Table';\n  import { getBasicColumns, getBasicShortColumns } from './tableData';\n  import { demoListApi } from '/@/api/demo/table';\n  export default defineComponent({\n    components: { BasicTable },\n    setup() {\n      const tableRef = ref<Nullable<TableActionType>>(null);\n\n      function getTableAction() {\n        const tableAction = unref(tableRef);\n        if (!tableAction) {\n          throw new Error('tableAction is null');\n        }\n        return tableAction;\n      }\n      function changeLoading() {\n        getTableAction().setLoading(true);\n        setTimeout(() => {\n          getTableAction().setLoading(false);\n        }, 1000);\n      }\n      return {\n        tableRef,\n        api: demoListApi,\n        columns: getBasicColumns(),\n        changeLoading,\n      };\n    },\n  });\n</script>\n

BasicColumn 和 tableAction 通过权限和业务控制显示隐藏的示例

<template>\n  <div class="p-4">\n    <BasicTable @register="registerTable">\n      <template #action="{ record }">\n        <TableAction\n          :actions="[\n            {\n              label: '编辑',\n              onClick: handleEdit.bind(null, record),\n              auth: 'other', // 根据权限控制是否显示: 无权限,不显示\n            },\n            {\n              label: '删除',\n              icon: 'ic:outline-delete-outline',\n              onClick: handleDelete.bind(null, record),\n              auth: 'super', // 根据权限控制是否显示: 有权限,会显示\n            },\n          ]"\n          :dropDownActions="[\n            {\n              label: '启用',\n              popConfirm: {\n                title: '是否启用?',\n                confirm: handleOpen.bind(null, record),\n              },\n              ifShow: (_action) => {\n                return record.status !== 'enable'; // 根据业务控制是否显示: 非enable状态的不显示启用按钮\n              },\n            },\n            {\n              label: '禁用',\n              popConfirm: {\n                title: '是否禁用?',\n                confirm: handleOpen.bind(null, record),\n              },\n              ifShow: () => {\n                return record.status === 'enable'; // 根据业务控制是否显示: enable状态的显示禁用按钮\n              },\n            },\n            {\n              label: '同时控制',\n              popConfirm: {\n                title: '是否动态显示?',\n                confirm: handleOpen.bind(null, record),\n              },\n              auth: 'super', // 同时根据权限和业务控制是否显示\n              ifShow: () => {\n                return true; // 根据业务控制是否显示\n              },\n            },\n          ]"\n        />\n      </template>\n    </BasicTable>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicTable, useTable, BasicColumn, TableAction } from '/@/components/Table';\n\n  import { demoListApi } from '/@/api/demo/table';\n  const columns: BasicColumn[] = [\n    {\n      title: '姓名',\n      dataIndex: 'name',\n      auth: 'test', // 根据权限控制是否显示: 无权限,不显示\n    },\n    {\n      title: '地址',\n      dataIndex: 'address',\n      auth: 'super', // 同时根据权限控制是否显示\n      ifShow: (_column) => {\n        return true; // 根据业务控制是否显示\n      },\n    },\n  ];\n  export default defineComponent({\n    components: { BasicTable, TableAction },\n    setup() {\n      const [registerTable] = useTable({\n        title: 'TableAction组件及固定列示例',\n        api: demoListApi,\n        columns: columns,\n        bordered: true,\n        actionColumn: {\n          width: 250,\n          title: 'Action',\n          dataIndex: 'action',\n          slots: { customRender: 'action' },\n        },\n      });\n      function handleEdit(record: Recordable) {\n        console.log('点击了编辑', record);\n      }\n      function handleDelete(record: Recordable) {\n        console.log('点击了删除', record);\n      }\n      function handleOpen(record: Recordable) {\n        console.log('点击了启用', record);\n      }\n      return {\n        registerTable,\n        handleEdit,\n        handleDelete,\n        handleOpen,\n      };\n    },\n  });\n</script>\n

useTable

使用组件自带的 useTable 可以方便使用表单

下面是一个使用简单表格的示例,

<template>\n  <BasicTable @register="registerTable" />\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicTable, useTable } from '/@/components/Table';\n  import { getBasicColumns, getBasicShortColumns } from './tableData';\n  import { demoListApi } from '/@/api/demo/table';\n  export default defineComponent({\n    components: { BasicTable },\n    setup() {\n      const [\n        registerTable,\n        {\n          setLoading,\n        },\n      ] = useTable({\n        api: demoListApi,\n        columns: getBasicColumns(),\n      });\n\n      function changeLoading() {\n        setLoading(true);\n        setTimeout(() => {\n          setLoading(false);\n        }, 1000);\n      }\n      }\n      return {\n        registerTable,\n        changeLoading,\n      };\n    },\n  });\n</script>\n

Usage

用于调用 Table 内部方法及 table 参数配置

// 表格的props也可以直接注册到useTable内部\nconst [register, methods] = useTable(props);\n

register

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

<template>\n  <BasicTable @register="register" />\n</template>\n<script>\n  export default defineComponent({\n    components: { BasicForm },\n    setup() {\n      const [register] = useTable();\n      return { register };\n    },\n  });\n</script>\n

Methods

setProps

类型:(props: Partial<BasicTableProps>) => void

说明: 用于设置表格参数

reload

类型:(opt?: FetchParams) => Promise<void>

说明: 刷新表格

redoHeight

类型:() => void

说明: 重新计算表格高度

setLoading

类型:(loading: boolean) => void

说明: 设置表格 loading 状态

getDataSource

获取表格数据

类型:<T = Recordable>() => T[]

说明: 获取表格数据

getRawDataSource

获取后端接口原始数据

类型:<T = Recordable>() => T

说明: 获取后端接口原始数据

getColumns

类型:(opt?: GetColumnsParams) => BasicColumn[]

说明: 获取表格数据

setColumns

类型:(columns: BasicColumn[] | string[]) => void

说明: 设置表头数据

setTableData

类型:<T = Recordable>(values: T[]) => void

说明: 设置表格数据

setPagination

类型:(info: Partial<PaginationProps>) => void

说明: 设置分页信息

deleteSelectRowByKey

类型:(key: string) => void

说明: 根据 key 删除取消选中行

getSelectRowKeys

类型:() => string[]

说明: 获取选中行的 keys

getSelectRows

类型:<T = Recordable>() => T[]

说明: 获取选中行的 rows

clearSelectedRowKeys

类型:() => void

说明: 清空选中行

setSelectedRowKeys

类型:(rowKeys: string[] | number[]) => void

说明: 设置选中行

getPaginationRef

类型:() => PaginationProps | boolean

说明: 获取当前分页信息

getShowPagination

类型:() => boolean

说明: 获取当前是否显示分页

setShowPagination

类型:(show: boolean) => Promise<void>

说明: 设置当前是否显示分页

getRowSelection

类型:() => TableRowSelection<Recordable>

说明: 获取勾选框信息

updateTableData

类型:(index: number, key: string, value: any)=>void

说明: 更新表格数据

updateTableDataRecord

类型: (rowKey: string | number, record: Recordable) => Recordable | void

说明: 根据唯一的 rowKey 更新指定行的数据.可用于不刷新整个表格而局部更新数据

deleteTableDataRecord

类型: (rowKey: string | number | string[] | number[]) => void

说明: 根据唯一的rowKey 动态删除指定行的数据.可用于不刷新整个表格而局部更新数据

insertTableDataRecord

类型: (record: Recordable, index?: number) => Recordable | void

说明: 可根据传入的 index 值决定插入数据行的位置,不传则是顺序插入,可用于不刷新整个表格而局部更新数据

getForm

类型:() => FormActionType

说明: 如果开启了搜索区域。可以通过该函数获取表单对象函数进行操作

expandAll

类型:() => void

说明: 展开树形表格

collapseAll

类型:() => void

说明: 折叠树形表格

Props

温馨提醒

  • 除以下参数外,官方文档内的 props 也都支持,具体可以参考 antv table
  • 注意:defaultExpandAllRowsdefaultExpandedRowKeys 属性在basicTable中不受支持,并且在antv table v2.2.0之后也被移除。
属性类型默认值可选值说明版本
clickToRowSelectbooleantrue-点击行是否选中 checkbox 或者 radio。需要开启
sortFn(sortInfo: SorterResult<any>) => any--自定义排序方法。见下方全局配置说明
filterFn(sortInfo: Partial<Recordable<string[]>>) => any--自定义过滤方法。见下方全局配置说明
showTableSettingbooleanfalse-显示表格设置工具
tableSettingTableSetting--表格设置工具配置,见下方 TableSetting
stripedbooleantrue-斑马纹
insetbooleanfalse-取消表格的默认 padding
autoCreateKeybooleantrue-是否自动生成 key
showSummarybooleanfalse-是否显示合计行
summaryDataany[]--自定义合计数据。如果有则显示该数据
emptyDataIsShowTablebooleantrue-在启用搜索表单的前提下,是否在表格没有数据的时候显示表格
summaryFunc(...arg) => any[]--计算合计行的方法
canRowDragbooleanfalse-是否可拖拽行排序
canColDragbooleanfalse-是否可拖拽列
isTreeTablebooleanfalse-是否树表
api(...arg: any) => Promise<any>--请求接口,可以直接将src/api内的函数直接传入
beforeFetch(T)=>T--请求之前对参数进行处理
afterFetch(T)=>T--请求之后对返回值进行处理
handleSearchInfoFn(T)=>T--开启表单后,在请求之前处理搜索条件参数
fetchSettingFetchSetting--接口请求配置,可以配置请求的字段和响应的字段名,见下方全局配置说明
immediatebooleantrue-组件加载后是否立即请求接口,在 api 有传的情况下,如果为 false,需要自行使用 reload 加载表格数据
searchInfoany--额外的请求参数
useSearchFormbooleanfalse-使用搜索表单
formConfigany--表单配置,参考表单组件的 Props
columnsany--表单列信息 BasicColumn[]
showIndexColumnbooleanture-是否显示序号列
indexColumnPropsany--序号列配置 BasicColumn
actionColumnany--表格右侧操作列配置 BasicColumn
ellipsisbooleantrue-文本超过宽度是否显示...
canResizebooleantrue-是否可以自适应高度(如果置于PageWrapper组件内,请勿启用PageWrapper的fixedHeight属性,二者不可同时使用)
clearSelectOnPageChangebooleanfalse-切换页码是否重置勾选状态
resizeHeightOffsetnumber0-表格自适应高度计算结果会减去这个值
rowSelectionany--选择列配置
titlestring--表格标题
titleHelpMessagestring | string[]--表格标题右侧温馨提醒
maxHeightnumber--表格最大高度,超出会显示滚动条
dataSourceany[]--表格数据,非 api 加载情况
borderedbooleanfalse-是否显示表格边框
paginationany--分页信息配置,为 false 不显示分页
loadingbooleanfalse-表格 loading 状态
scrollany--参考官方文档 scroll
beforeEditSubmit({record: Recordable,index: number,key: string | number,value: any}) => Promise<any>--单元格编辑状态提交回调,返回false将阻止单元格提交数据到table。该回调在行编辑模式下无效。2.7.2

TableSetting

{\n  // 是否显示刷新按钮\n  redo?: boolean;\n  // 是否显示尺寸调整按钮\n  size?: boolean;\n  // 是否显示字段调整按钮\n  setting?: boolean;\n  // 是否显示全屏按钮\n  fullScreen?: boolean;\n}\n

BasicColumn

除 参考官方 Column 配置外,扩展以下参数

属性类型默认值可选值说明
defaultHiddenbooleanfalse-默认隐藏,可在列配置显示
helpMessagestring|string[]--列头右侧帮助文本
editboolean--是否开启单元格编辑
editRowboolean--是否开启行编辑
editablebooleanfalse-是否处于编辑状态
editComponentComponentTypeInput-编辑组件
editComponentPropsany--对应编辑组件的 props
editRule((text: string, record: Recordable) => Promise<string>)--对应编辑组件的表单校验
editValueMap(value: any) => string--对应单元格值枚举
onEditRow()=>void--触发行编辑
formatCellFormat--单元格格式化
authRoleEnumRoleEnum[]stringstring[]--根据权限编码来控制当前列是否显示
ifShowboolean | ((action: ActionItem) => boolean)--根据业务状态来控制当前列是否显示

EditComponentType

export type ComponentType =\n  | 'Input'\n  | 'InputNumber'\n  | 'Select'\n  | 'ApiSelect'\n  | 'Checkbox'\n  | 'Switch'\n  | 'DatePicker'  // v2.5.0 以上\n  | 'TimePicker'; // v2.5.0 以上\n

CellFormat

export type CellFormat =\n  | string\n  | ((text: string, record: Recordable, index: number) => string | number)\n  | Map<string | number, any>;\n

事件

温馨提醒

除以下事件外,官方文档内的 event 也都支持,具体可以参考 antv table

事件回调参数说明
fetch-successFunction({items,total})接口请求成功后触发
fetch-errorFunction(error)错误信息
selection-changeFunction({keys,rows})勾选事件触发
row-clickFunction(record, index, event)行点击触发
row-dbClickFunction(record, index, event)行双击触发
row-contextmenuFunction(record, index, event)行右键触发
row-mouseenterFunction(record, index, event)行移入触发
row-mouseleaveFunction(record, index, event)行移出触发
edit-endFunction({record, index, key, value})单元格编辑完成触发
edit-cancelFunction({record, index, key, value})单元格取消编辑触发
edit-row-endFunction()行编辑结束触发
edit-changeFunction({column,value,record})单元格编辑组件的 value 发生变化时触发

edit-change 说明

从版本 2.4.2 起,对于 edit-change 事件,record 中的 editValueRefs 装载了当前行的所有编辑组件(如果有的话)的值的 ref 对象,可用于处理同一行中的编辑组件的联动。请看下面的例子

      function onEditChange({ column, record }) {\n        // 当同一行的单价或者数量发生变化时,更新合计金额(三个数据均为当前行编辑组件的值)\n        if (column.dataIndex === 'qty' || column.dataIndex === 'price') {\n          const { editValueRefs: { total, qty, price } } = record;\n          total.value = unref(qty) * unref(price);\n        }\n      }\n

Slots

温馨提醒

除以下参数外,官方文档内的 slot 也都支持,具体可以参考 antv table

名称说明版本
tableTitle表格顶部左侧区域
toolbar表格顶部右侧区域
expandedRowRender展开行区域
headerTop表格顶部区域(标题上方)2.6.1

Form-Slots

当开启 form 表单后。以form-xxxx为前缀的 slot 会被视为 form 的 slot

xxxx 为 form 组件的 slot。具体参考form 组件文档

e.g

form-submitBefore\n

ColumnSetting组件

字段调整组件

提供了可视化操作表格每一列的是否展示、位置、固定;包括序号列、勾选列。会响应tableMethodssetColumnssetProps方法的更改内容。

值得注意的是

序号列勾选列是在table的props中定义的,对应的字段分别是showIndexColumnrowSelection。因此在动态改变表格列配置的时候,建议使用setProps方法,并显式地设置这两个字段的值来保证达到预期效果

// ...\nconst [registerTable, { setProps }] = useTable({...})\n\nsetProps({\n  columns: [], // 表格的列配置 BasicColumn[]\n  showIndexColumn: false, // 是否展示序号列\n  rowSelection: false // 勾选列配置\n})\n

内置组件(只能用于表格内部)

TableAction

用于表格右侧操作列渲染

Props

属性类型默认值可选值说明版本
actionsActionItem[]--右侧操作列按钮列表
dropDownActionsActionItem[]--右侧操作列更多下拉按钮列表
stopButtonPropagationbooleanfalsetrue/false是否阻止操作按钮的click事件冒泡2.5.0

ActionItem

export interface ActionItem {\n  // 按钮文本\n  label: string;\n  // 是否禁用\n  disabled?: boolean;\n  // 按钮颜色\n  color?: 'success' | 'error' | 'warning';\n  // 按钮类型\n  type?: string;\n  // button组件props\n  props?: any;\n  // 按钮图标\n  icon?: string;\n  // 气泡确认框\n  popConfirm?: PopConfirm;\n  // 是否显示分隔线,v2.0.0+\n  divider?: boolean;\n  // 根据权限编码来控制当前列是否显示,v2.4.0+\n  auth?: RoleEnum | RoleEnum[] | string | string[];\n  // 根据业务状态来控制当前列是否显示,v2.4.0+\n  ifShow?: boolean | ((action: ActionItem) => boolean);\n  // 点击回调\n  onClick?: Fn;\n  // Tooltip配置,2.5.3以上版本支持,可以配置为string,或者完整的tooltip属性\n  tooltip?: string | TooltipProps\n}\n

有关TooltipProps的说明,请参考tooltip

PopConfirm

export interface PopConfirm {\n  title: string;\n  okText?: string;\n  cancelText?: string;\n  confirm: Fn;\n  cancel?: Fn;\n  icon?: string;\n}\n

TableImg

用于渲染单元格图片,支持图片预览

Props

属性类型默认值可选值说明版本
imgListstring[]--图片地址列表
sizenumber--图片大小
simpleShowbooleanfalsetrue/false简单显示模式(只显示第一张图片)2.5.0
showBadgebooleantruetrue/false简单模式下是否显示计数Badge2.5.0
marginnumber4-常规模式下的图片间距2.5.0
srcPrefixstring--在每一个图片src前插入的内容2.5.0

全局配置

componentsSettings 可以配置全局参数。用于统一整个项目的风格。可以通过 props 传值覆盖

',148);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.3c62f0f9.lean.js b/assets/components_table.md.3c62f0f9.lean.js new file mode 100644 index 00000000..9d83331e --- /dev/null +++ b/assets/components_table.md.3c62f0f9.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Table 表格","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":3,"title":"示例","slug":"示例"},{"level":3,"title":"template 示例","slug":"template-示例"},{"level":3,"title":"BasicColumn 和 tableAction 通过权限和业务控制显示隐藏的示例","slug":"basiccolumn-和-tableaction-通过权限和业务控制显示隐藏的示例"},{"level":2,"title":"useTable","slug":"usetable"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"Methods","slug":"methods"},{"level":2,"title":"Props","slug":"props"},{"level":3,"title":"TableSetting","slug":"tablesetting"},{"level":2,"title":"BasicColumn","slug":"basiccolumn"},{"level":3,"title":"EditComponentType","slug":"editcomponenttype"},{"level":3,"title":"CellFormat","slug":"cellformat"},{"level":2,"title":"事件","slug":"事件"},{"level":2,"title":"Slots","slug":"slots"},{"level":2,"title":"Form-Slots","slug":"form-slots"},{"level":2,"title":"ColumnSetting组件","slug":"columnsetting组件"},{"level":2,"title":"内置组件(只能用于表格内部)","slug":"内置组件(只能用于表格内部)"},{"level":3,"title":"TableAction","slug":"tableaction"},{"level":3,"title":"TableImg","slug":"tableimg"},{"level":2,"title":"全局配置","slug":"全局配置"}],"relativePath":"components/table.md","lastUpdated":1697592578589}',p={},o=s('',148);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_time.md.059df4f7.js b/assets/components_time.md.059df4f7.js new file mode 100644 index 00000000..1f74d4b4 --- /dev/null +++ b/assets/components_time.md.059df4f7.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Time","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/time.md","lastUpdated":1697592578589}',p={},o=s('

Time

相对时间组件

Usage

<template>\n  <Time :value="time" />\n</template>\n<script lang="ts">\n  import { defineComponent, reactive, toRefs } from 'vue';\n  import { Time } from '/@/components/Time';\n\n  export default defineComponent({\n    components: { Time },\n    setup() {\n      const now = new Date().getTime();\n      const state = reactive({\n        time: now - 60 * 3 * 1000,\n      });\n      return {\n        ...toRefs(state),\n        now,\n      };\n    },\n  });\n</script>\n

Props

属性类型默认值可选值说明
valuestring,Date,number--时间值
stepnumber60-刷新时间
modestringrelative-模式,date:日期,datetime:时间戳,relative:相对时间
',6);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_time.md.059df4f7.lean.js b/assets/components_time.md.059df4f7.lean.js new file mode 100644 index 00000000..0df44eb8 --- /dev/null +++ b/assets/components_time.md.059df4f7.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Time","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"}],"relativePath":"components/time.md","lastUpdated":1697592578589}',p={},o=s('',6);p.render=function(s,t,p,e,c,u){return n(),a("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_tinymce.md.bb6dd194.js b/assets/components_tinymce.md.bb6dd194.js new file mode 100644 index 00000000..f34d18e7 --- /dev/null +++ b/assets/components_tinymce.md.bb6dd194.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Tinymce","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"}],"relativePath":"components/tinymce.md","lastUpdated":1697592578589}',p={},e=s('

Tinymce

富文本组件位于 src/components/TinyMce

富文本组件使用的是 CDN 方式引入

可在 /@/components/TinyMce/src/Editor.vue 更改下面 CDN 地址

const CDN_URL = 'https://cdn.bootcdn.net/ajax/libs/tinymce/5.5.1';\n

Usage

<template>\n  <Tinymce v-model="value" @change="handleChange" width="100%" />\n</template>\n<script lang="ts">\n  import { defineComponent, ref } from 'vue';\n  import { Tinymce } from '/@/components/Tinymce/index';\n\n  export default defineComponent({\n    components: { Tinymce },\n    setup() {\n      const value = ref('hello world!');\n      function handleChange(value: string) {\n        console.log(value);\n      }\n      return { handleChange, value };\n    },\n  });\n</script>\n

Props

属性类型默认值说明
optionsany{}tinymce 的配置项
value(v-model)string-双向绑定值
heightnumber , string400高度
widthnumber , stringauto宽度
toolbarstring[]-工具栏
pluginsstring[]-插件
showImageUploadbooleantrue是否显示上传按钮

Events

事件回调参数返回值说明
change(str:string)=>{}富文本内容改变触发事件
',9);p.render=function(s,t,p,o,c,l){return n(),a("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/components_tinymce.md.bb6dd194.lean.js b/assets/components_tinymce.md.bb6dd194.lean.js new file mode 100644 index 00000000..4288da4f --- /dev/null +++ b/assets/components_tinymce.md.bb6dd194.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"Tinymce","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"}],"relativePath":"components/tinymce.md","lastUpdated":1697592578589}',p={},e=s('',9);p.render=function(s,t,p,o,c,l){return n(),a("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/components_transition.md.3cc0f9ff.js b/assets/components_transition.md.3cc0f9ff.js new file mode 100644 index 00000000..58e162cc --- /dev/null +++ b/assets/components_transition.md.3cc0f9ff.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"Transition","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"}],"relativePath":"components/transition.md","lastUpdated":1697592578589}',p={},o=a('

Transition

用于页面/组件切换动画

Usage

<template>\n  <div class="p-4">\n    <div class="flex">\n      <Select\n        :options="options"\n        v-model:value="value"\n        placeholder="选择动画"\n        :style="{ width: '150px' }"\n      />\n      <a-button type="primary" class="ml-4" @click="start"> start </a-button>\n    </div>\n    <component :is="`${value}Transition`">\n      <div class="box" v-show="show"></div>\n    </component>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent, ref } from 'vue';\n  import { Select } from 'ant-design-vue';\n  import {\n    FadeTransition,\n    ScaleTransition,\n    SlideYTransition,\n    ScrollYTransition,\n    SlideYReverseTransition,\n    ScrollYReverseTransition,\n    SlideXTransition,\n    ScrollXTransition,\n    SlideXReverseTransition,\n    ScrollXReverseTransition,\n    ScaleRotateTransition,\n    ExpandXTransition,\n    ExpandTransition,\n  } from '/@/components/Transition/index';\n\n  const transitionList = [\n    'Fade',\n    'Scale',\n    'SlideY',\n    'ScrollY',\n    'SlideYReverse',\n    'ScrollYReverse',\n    'SlideX',\n    'ScrollX',\n    'SlideXReverse',\n    'ScrollXReverse',\n    'ScaleRotate',\n    'ExpandX',\n    'Expand',\n  ];\n  const options = transitionList.map((item) => ({\n    label: item,\n    value: item,\n    key: item,\n  }));\n\n  export default defineComponent({\n    components: {\n      Select,\n      FadeTransition,\n      ScaleTransition,\n      SlideYTransition,\n      ScrollYTransition,\n      SlideYReverseTransition,\n      ScrollYReverseTransition,\n      SlideXTransition,\n      ScrollXTransition,\n      SlideXReverseTransition,\n      ScrollXReverseTransition,\n      ScaleRotateTransition,\n      ExpandXTransition,\n      ExpandTransition,\n    },\n    setup() {\n      const value = ref('Fade');\n      const show = ref(true);\n      function start() {\n        show.value = false;\n        setTimeout(() => {\n          show.value = true;\n        }, 300);\n      }\n      return { options, value, start, show };\n    },\n  });\n</script>\n<style lang="less" scoped>\n  .box {\n    width: 150px;\n    height: 150px;\n    margin-top: 20px;\n    background: pink;\n  }\n</style>\n
',4);p.render=function(a,t,p,c,e,u){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_transition.md.3cc0f9ff.lean.js b/assets/components_transition.md.3cc0f9ff.lean.js new file mode 100644 index 00000000..49e16b32 --- /dev/null +++ b/assets/components_transition.md.3cc0f9ff.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"Transition","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"}],"relativePath":"components/transition.md","lastUpdated":1697592578589}',p={},o=a('',4);p.render=function(a,t,p,c,e,u){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/components_tree.md.8773eb7b.js b/assets/components_tree.md.8773eb7b.js new file mode 100644 index 00000000..b9ff5c70 --- /dev/null +++ b/assets/components_tree.md.8773eb7b.js @@ -0,0 +1 @@ +import{o as n,c as t,a as s}from"./app.8cddb23b.js";const a='{"title":"Tree","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Slots","slug":"slots"},{"level":2,"title":"Methods","slug":"methods"}],"relativePath":"components/tree.md","lastUpdated":1697592578589}',p={},e=s('

Tree

antv 的 tree 组件进行封装

Usage

<template>\n  <BasicTree :treeData="treeData" />\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicTree } from '/@/components/Tree/index';\n  import { treeData } from './data';\n  import { CollapseContainer } from '/@/components/Container/index';\n  import { TreeItem } from '/@/components/Tree/index';\n\n  export const treeData: TreeItem[] = [\n    {\n      title: 'parent 1',\n      key: '0-0',\n      icon: 'home|svg',\n      children: [\n        { title: 'leaf', key: '0-0-0' },\n        {\n          title: 'leaf',\n          key: '0-0-1',\n          children: [\n            { title: 'leaf', key: '0-0-0-0' },\n            { title: 'leaf', key: '0-0-0-1' },\n          ],\n        },\n      ],\n    },\n    {\n      title: 'parent 2',\n      key: '1-1',\n      icon: 'home|svg',\n      children: [\n        { title: 'leaf', key: '1-1-0' },\n        { title: 'leaf', key: '1-1-1' },\n      ],\n    },\n    {\n      title: 'parent 3',\n      key: '2-2',\n      icon: 'home|svg',\n      children: [\n        { title: 'leaf', key: '2-2-0' },\n        { title: 'leaf', key: '2-2-1' },\n      ],\n    },\n  ];\n  export default defineComponent({\n    components: { BasicTree, CollapseContainer },\n    setup() {\n      return { treeData };\n    },\n  });\n</script>\n

Props

温馨提醒

除以下参数外,官方文档内的 props 也都支持,具体可以参考 antv tree

属性类型默认值可选值说明版本
treeDataTreeItem[]--树组件数据
rightMenuListContextMenuItem[]--右键菜单列表
checkedKeysstring[]--勾选的节点
selectedKeysstring[]--选中的节点
expandedKeysstring[]--展开的节点
actionListActionItem[]--鼠标移动上去右边操作按钮列表
titlestring--定制标题字符串
toolbarboolean--是否显示工具栏
searchboolean--显示搜索框
clickRowToExpandboolean--是否在点击行时自动展开
beforeRightClick(node, event)=>ContextMenuItem[]--右键点击回调,可返回右键菜单列表数据来生成右键菜单
rightMenuListContextMenuItem[]--右键菜单列表数据
defaultExpandLevelstring | number--初次渲染后默认展开的层级2.4.1
defaultExpandAllbooleanfalsetrue/false初次渲染后默认全部2.4.1
searchValue(v-model)string--当前搜索词2.7.1

注意

defaultExpandLeveldefaultExpandAll 仅在初次渲染时生效。如果basicTree是在创建完毕之后才设置的treeData(如异步数据),需要在更新后自己调用basicTree提供的expandAllfilterByLevel来执行展开

ActionItem

{\n  // 渲染的图标\n  render: (record: any) => any;\n  // 是否显示\n  show?: boolean | ((record: Recordable) => boolean);\n}\n

ContextMenuItem

{\n  // 文本\n  label: string;\n  // 图标\n  icon?: string;\n  // 是否禁用\n  disabled?: boolean;\n  // 事件\n  handler?: (...arg) => any;\n  // 是否显示分隔线\n  divider?: boolean;\n  // 子级菜单数据\n  children?: ContextMenuItem[];\n}\n

Slots

温馨提醒

官方文档内的 slot 都支持,具体可以参考 antv tree

Methods

名称回调参数说明
checkAll(checkAll: boolean) => void选择所有
expandAll(expandAll: boolean) => void展开所有
setExpandedKeys(keys: Keys) => void设置展开节点
getExpandedKeys() => Keys获取展开节点
setSelectedKeys(keys: Keys) => void设置选中节点
getSelectedKeys() => Keys获取选中节点
setCheckedKeys(keys: CheckKeys) => void设置勾选节点
getCheckedKeys() => CheckKeys获取勾选节点
filterByLevel(level: number) => void显示指定等级
insertNodeByKey(opt: InsertNodeParams) => void插入子节点到指定节点内
deleteNodeByKey(key: string) => void根据 key 删除节点
updateNodeByKey(key: string, node: Omit<TreeItem, 'key'>) => void根据 key 更新节点
setSearchValue(value: string) => void设置当前搜索词(v2.7.1)
getSearchValue() => string获取当前搜索词(v2.7.1)
',16);p.render=function(s,a,p,o,c,d){return n(),t("div",null,[e])};export default p;export{a as __pageData}; diff --git a/assets/components_tree.md.8773eb7b.lean.js b/assets/components_tree.md.8773eb7b.lean.js new file mode 100644 index 00000000..325a806e --- /dev/null +++ b/assets/components_tree.md.8773eb7b.lean.js @@ -0,0 +1 @@ +import{o as n,c as t,a as s}from"./app.8cddb23b.js";const a='{"title":"Tree","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Slots","slug":"slots"},{"level":2,"title":"Methods","slug":"methods"}],"relativePath":"components/tree.md","lastUpdated":1697592578589}',p={},e=s('',16);p.render=function(s,a,p,o,c,d){return n(),t("div",null,[e])};export default p;export{a as __pageData}; diff --git a/assets/components_upload.md.6a85a24f.js b/assets/components_upload.md.6a85a24f.js new file mode 100644 index 00000000..e1e1fe0b --- /dev/null +++ b/assets/components_upload.md.6a85a24f.js @@ -0,0 +1 @@ +import{o as n,c as t,a}from"./app.8cddb23b.js";const s='{"title":"Upload","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Config","slug":"config"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"}],"relativePath":"components/upload.md","lastUpdated":1697592578589}',p={},o=a('

Upload

文件上传组件

Usage

<template>\n  <BasicUpload :maxSize="20" :maxNumber="10" @change="handleChange" :api="uploadApi" />\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { BasicUpload } from '/@/components/Upload';\n  import { uploadApi } from '/@/api/sys/upload';\n\n  export default defineComponent({\n    components: { BasicUpload },\n    setup() {\n      return {\n        uploadApi,\n        handleChange: (list: string[]) => {\n          createMessage.info(`已上传文件${JSON.stringify(list)}`);\n        },\n      };\n    },\n  });\n</script>\n

Config

.env.development.env.production 配置开发和生产的文件上传地址

# .env.development\n\nVITE_PROXY=[["/upload","http://localhost:3001/upload"]]\n\n::: tip\nv3.0.0开始,作者重构了vite.config.ts,新版本不再支持VITE_PROXY环境变量。\n:::\n\n# 如果没有跨域问题,则直接使用真实上传地址\nVITE_GLOB_UPLOAD_URL=/upload\n\n# .env.production\nVITE_GLOB_UPLOAD_URL=/upload\n\n

Props

属性类型默认值可选值说明
valuestring[]--已上传的文件列表,支持v-model
showPreviewNumberbooleantrue-是否显示预览数量
emptyHidePreviewbooleanfalse-没有上传文件时是否隐藏预览
helpTextstring--帮助文本
maxSizenumber2-单个文件最大体积,单位 M
maxNumbernumberInfinity-最大上传数量,Infinity 则不限制
acceptstring[]--限制上传格式,可使用文件后缀名(点号可选)或MIME字符串。例如 ['.doc,','docx','application/msword','image/*']
multipleboolean--开启多文件上传
uploadParamsany--上传携带的参数
apiFn--上传接口,为上面配置的接口

Events

事件回调参数返回值说明版本
change(fileList)=>void文件列表内容改变触发事件
delete(record)=>void在上传列表中删除文件的事件
preview-delete(url:string)=>void在预览列表中删除文件的事件2.5.3
',11);p.render=function(a,s,p,e,c,d){return n(),t("div",null,[o])};export default p;export{s as __pageData}; diff --git a/assets/components_upload.md.6a85a24f.lean.js b/assets/components_upload.md.6a85a24f.lean.js new file mode 100644 index 00000000..0e9db446 --- /dev/null +++ b/assets/components_upload.md.6a85a24f.lean.js @@ -0,0 +1 @@ +import{o as n,c as t,a}from"./app.8cddb23b.js";const s='{"title":"Upload","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Config","slug":"config"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Events","slug":"events"}],"relativePath":"components/upload.md","lastUpdated":1697592578589}',p={},o=a('',11);p.render=function(a,s,p,e,c,d){return n(),t("div",null,[o])};export default p;export{s as __pageData}; diff --git a/assets/components_verify.md.688b0bf0.js b/assets/components_verify.md.688b0bf0.js new file mode 100644 index 00000000..5e19328c --- /dev/null +++ b/assets/components_verify.md.688b0bf0.js @@ -0,0 +1 @@ +import{o as t,c as n,a}from"./app.8cddb23b.js";const s='{"title":"BasicDragVerify","description":"","frontmatter":{},"headers":[{"level":2,"title":"BasicDragVerify","slug":"basicdragverify-1"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":3,"title":"Methods","slug":"methods"},{"level":2,"title":"RotateDragVerify","slug":"rotatedragverify"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"props","slug":"props-1"},{"level":3,"title":"Methods","slug":"methods-1"}],"relativePath":"components/verify.md","lastUpdated":1697592578589}',p={},e=a('

BasicDragVerify

拖动校验组件

BasicDragVerify

Usage

<template>\n  <div class="p-10">\n    <BasicDragVerify @success="handleSuccess" />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent, ref } from 'vue';\n  import { BasicDragVerify, DragVerifyActionType, PassingData } from '/@/components/Verify/index';\n  export default defineComponent({\n    components: { BasicDragVerify },\n    setup() {\n      function handleSuccess(data: PassingData) {\n        const { time } = data;\n        createMessage.success(`校验成功,耗时${time}`);\n      }\n      return {\n        handleSuccess,\n        handleBtnClick,\n      };\n    },\n  });\n</script>\n

Props

属性类型默认值说明
valueboolean-是否通过
textstring请按住滑块拖动未拖动时候显示文字
successTextstring验证通过验证成功后显示文本
heightstring|string40高度
widthstring|string260宽度
circlebooleanfalse是否圆角
wrapStyleany-外层容器样式
contentStyleany-主体内容样式
barStyleany-bar 样式
actionStyleany-拖拽按钮样式

Methods

名称回调参数说明
resume()=>{}还原初始值

RotateDragVerify

图片还原正方向校验组件

Usage

<template>\n  <div class="p-10">\n    <RotateDragVerify :src="img" ref="el" @success="handleSuccess" />\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { RotateDragVerify } from '/@/components/Verify/index';\n\n  import img from '/@/assets/images/header.jpg';\n  export default defineComponent({\n    components: { RotateDragVerify },\n    setup() {\n      const handleSuccess = () => {\n        console.log('success!');\n      };\n      return {\n        handleSuccess,\n        img,\n      };\n    },\n  });\n</script>\n

props

属性类型默认值说明
srcstring-图片地址
imgWidthnumber-图片宽度
imgWrapStyleany-图片外层容器样式
minDegreenumber-最小旋转角度
maxDegreenumber-最大旋转角度
diffDegreenumber-误差角度
valueboolean-是否通过
textstring请按住滑块拖动未拖动时候显示文字
successTextstring验证通过验证成功后显示文本
heightstring|string40高度
widthstring|string260宽度
circlebooleanfalse是否圆角
wrapStyleany-外层容器样式
contentStyleany-主体内容样式
barStyleany-bar 样式
actionStyleany-拖拽按钮样式

Methods

名称回调参数说明
resumeFunction还原初始值
',17);p.render=function(a,s,p,o,c,d){return t(),n("div",null,[e])};export default p;export{s as __pageData}; diff --git a/assets/components_verify.md.688b0bf0.lean.js b/assets/components_verify.md.688b0bf0.lean.js new file mode 100644 index 00000000..63dbc83d --- /dev/null +++ b/assets/components_verify.md.688b0bf0.lean.js @@ -0,0 +1 @@ +import{o as t,c as n,a}from"./app.8cddb23b.js";const s='{"title":"BasicDragVerify","description":"","frontmatter":{},"headers":[{"level":2,"title":"BasicDragVerify","slug":"basicdragverify-1"},{"level":3,"title":"Usage","slug":"usage"},{"level":3,"title":"Props","slug":"props"},{"level":3,"title":"Methods","slug":"methods"},{"level":2,"title":"RotateDragVerify","slug":"rotatedragverify"},{"level":3,"title":"Usage","slug":"usage-1"},{"level":3,"title":"props","slug":"props-1"},{"level":3,"title":"Methods","slug":"methods-1"}],"relativePath":"components/verify.md","lastUpdated":1697592578589}',p={},e=a('',17);p.render=function(a,s,p,o,c,d){return t(),n("div",null,[e])};export default p;export{s as __pageData}; diff --git a/assets/components_virtual-scroll.md.65256e7b.js b/assets/components_virtual-scroll.md.65256e7b.js new file mode 100644 index 00000000..118c7f8d --- /dev/null +++ b/assets/components_virtual-scroll.md.65256e7b.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"VirtualScroll","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/virtual-scroll.md","lastUpdated":1697592578589}',p={},o=s('

VirtualScroll

虚拟滚动组件(用于大量数据纯展示时使用)

Usage

<template>\n  <div class="p-4 virtual-scroll-demo">\n    <Divider>基础滚动示例</Divider>\n    <div class="virtual-scroll-demo-wrap">\n      <VirtualScroll :itemHeight="41" :items="data" :height="300" :width="300">\n        <template v-slot="{ item }">\n          <div class="virtual-scroll-demo__item">{{ item.title }}</div>\n        </template>\n      </VirtualScroll>\n    </div>\n\n    <Divider>即使不可见,也预先加载50条数据,防止空白</Divider>\n    <div class="virtual-scroll-demo-wrap">\n      <VirtualScroll :itemHeight="41" :items="data" :height="300" :width="300" :bench="50">\n        <template v-slot="{ item }">\n          <div class="virtual-scroll-demo__item">{{ item.title }}</div>\n        </template>\n      </VirtualScroll>\n    </div>\n  </div>\n</template>\n<script lang="ts">\n  import { defineComponent } from 'vue';\n  import { VirtualScroll } from '/@/components/VirtualScroll/index';\n\n  import { Divider } from 'ant-design-vue';\n  const data: any[] = (() => {\n    const arr: any[] = [];\n    for (let index = 1; index < 20000; index++) {\n      arr.push({\n        title: '列表项' + index,\n      });\n    }\n    return arr;\n  })();\n  export default defineComponent({\n    components: { VirtualScroll, Divider },\n    setup() {\n      return { data: data };\n    },\n  });\n</script>\n<style lang="less" scoped>\n  .virtual-scroll-demo {\n    &-wrap {\n      display: flex;\n      margin: 0 30%;\n      background: #fff;\n      justify-content: center;\n    }\n\n    /deep/ &__item {\n      height: 40px;\n      padding: 0 20px;\n      line-height: 40px;\n      border-bottom: 1px solid #ddd;\n    }\n  }\n</style>\n

Props

属性类型默认值可选值说明
heightstring|number--高度
widthstring|number--宽度
maxHeightstring|number--最大高度
maxWidthstring|number--最大宽度
minHeightstring|number--最小高度
minWidthstring|number--最小宽度
itemHeightstring|number--每个选项高度,必传
itemsany[]--选项列表

Slots

名称说明
default默认
',8);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_virtual-scroll.md.65256e7b.lean.js b/assets/components_virtual-scroll.md.65256e7b.lean.js new file mode 100644 index 00000000..82993aee --- /dev/null +++ b/assets/components_virtual-scroll.md.65256e7b.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"VirtualScroll","description":"","frontmatter":{},"headers":[{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Props","slug":"props"},{"level":2,"title":"Slots","slug":"slots"}],"relativePath":"components/virtual-scroll.md","lastUpdated":1697592578589}',p={},o=s('',8);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_cors.md.37a05889.js b/assets/dep_cors.md.37a05889.js new file mode 100644 index 00000000..c5a9a6f4 --- /dev/null +++ b/assets/dep_cors.md.37a05889.js @@ -0,0 +1 @@ +import{o as l,c as t,a as i}from"./app.8cddb23b.js";const o='{"title":"跨域处理","description":"","frontmatter":{},"headers":[{"level":2,"title":"产生原因","slug":"产生原因"},{"level":2,"title":"解决方式","slug":"解决方式"}],"relativePath":"dep/cors.md","lastUpdated":1697592578589}',r={},e=i('

跨域处理

产生原因

跨域产生的原因是由于前端地址与后台接口不是同源,从而导致 ajax 不能发送

非同源产生的问题

  1. Cookie、LocalStorage 和 IndexDB 无法获取
  2. DOM 无法获得
  3. AJAX 请求不能发送

同源条件

协议端口主机 三者相同即为同源

反之,其中只要 某一个 不一样则为不同源

解决方式

本地开发跨域

本地开发一般使用下面 3 种方式进行处理

  1. vite 的 proxy 进行代理
  2. 后台开启 cors
  3. 使用 nginx 转发请求

项目内部自带第一种方式,具体可以参考服务端交互-本地开发环境接口地址修改

生产环境跨域

生产环境一般使用下面 2 种方式进行处理

  1. 后台开启 cors
  2. 使用 nginx 转发请求

后台开启 cors 不需要前端做任何改动

nginx 配置文件可以查看nginx 配置

',15);r.render=function(i,o,r,s,a,n){return l(),t("div",null,[e])};export default r;export{o as __pageData}; diff --git a/assets/dep_cors.md.37a05889.lean.js b/assets/dep_cors.md.37a05889.lean.js new file mode 100644 index 00000000..ce0310c7 --- /dev/null +++ b/assets/dep_cors.md.37a05889.lean.js @@ -0,0 +1 @@ +import{o as l,c as t,a as i}from"./app.8cddb23b.js";const o='{"title":"跨域处理","description":"","frontmatter":{},"headers":[{"level":2,"title":"产生原因","slug":"产生原因"},{"level":2,"title":"解决方式","slug":"解决方式"}],"relativePath":"dep/cors.md","lastUpdated":1697592578589}',r={},e=i('',15);r.render=function(i,o,r,s,a,n){return l(),t("div",null,[e])};export default r;export{o as __pageData}; diff --git a/assets/dep_dark.md.37147652.js b/assets/dep_dark.md.37147652.js new file mode 100644 index 00000000..99ed73a9 --- /dev/null +++ b/assets/dep_dark.md.37147652.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"黑暗主题","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"原理","slug":"原理"},{"level":2,"title":"配置","slug":"配置"},{"level":2,"title":"切换","slug":"切换"}],"relativePath":"dep/dark.md","lastUpdated":1697592578593}',p={},e=a('

黑暗主题

介绍

项目已经内置了黑暗主题切换,只需配置自己需要的颜色变量,即可在项目中使用

原理

通过 vite-plugin-theme 插件,将所有的颜色变量抽取到独立的 css 文件,并且全部在 html 上面加上 css 选择器。通过改变 html 标签的 data-theme 属性来进行黑暗主题切换

配置

黑暗主题颜色配置通过 vite-plugin-theme 实现,具体代码在 build/vite/plugin/theme

antdDarkThemePlugin({\n  darkModifyVars: {\n    ...generateModifyVars(true),\n    'text-color': '#c9d1d9',\n    'text-color-base': '#c9d1d9',\n    'component-background': '#151515',\n    'text-color-secondary': '#8b949e',\n    'border-color-base': '#303030',\n    'item-active-bg': '#111b26',\n    'app-content-background': 'rgb(255 255 255 / 4%)',\n  },\n});\n

切换

只需要使用 vite-plugin-theme 提供的函数来进行切换即可

import { darkCssIsReady, loadDarkThemeCss } from 'vite-plugin-theme/es/client';\n\nexport async function updateDarkTheme(mode: string | null = 'light') {\n  const htmlRoot = document.getElementById('htmlRoot');\n  if (mode === 'dark') {\n    if (import.meta.env.PROD && !darkCssIsReady) {\n      await loadDarkThemeCss();\n    }\n    htmlRoot?.setAttribute('data-theme', 'dark');\n  } else {\n    htmlRoot?.setAttribute('data-theme', 'light');\n  }\n}\n
',11);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/dep_dark.md.37147652.lean.js b/assets/dep_dark.md.37147652.lean.js new file mode 100644 index 00000000..02fb6000 --- /dev/null +++ b/assets/dep_dark.md.37147652.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"黑暗主题","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"原理","slug":"原理"},{"level":2,"title":"配置","slug":"配置"},{"level":2,"title":"切换","slug":"切换"}],"relativePath":"dep/dark.md","lastUpdated":1697592578593}',p={},e=a('',11);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/dep_i18n.md.a96f197a.js b/assets/dep_i18n.md.a96f197a.js new file mode 100644 index 00000000..46a18fb9 --- /dev/null +++ b/assets/dep_i18n.md.a96f197a.js @@ -0,0 +1 @@ +import{o as n,c as s,b as a,d as t}from"./app.8cddb23b.js";const e='{"title":"国际化","description":"","frontmatter":{},"headers":[{"level":2,"title":"I18n-ally 插件","slug":"i18n-ally-插件"},{"level":2,"title":"配置默认语言","slug":"配置默认语言"},{"level":2,"title":"配置","slug":"配置"},{"level":3,"title":"语言文件","slug":"语言文件"},{"level":3,"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":"setupI18n 函数","slug":"setupi18n-函数"},{"level":3,"title":"changeLocale 函数","slug":"changelocale-函数"}],"relativePath":"dep/i18n.md","lastUpdated":1697592578593}',o={},c=a("h1",{id:"国际化"},[a("a",{class:"header-anchor",href:"#国际化","aria-hidden":"true"},"#"),t(" 国际化")],-1),l=a("p",null,[t("如果你使用的 vscode 开发工具,则推荐安装 "),a("a",{href:"https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally",target:"_blank",rel:"noopener noreferrer"},"I18n-ally"),t(" 这个插件")],-1),p=a("h2",{id:"i18n-ally-插件"},[a("a",{class:"header-anchor",href:"#i18n-ally-插件","aria-hidden":"true"},"#"),t(" I18n-ally 插件")],-1),u=a("p",null,"安装了该插件后,你的代码内可以实时看到对应的语言内容",-1),r=a("p",null,[a("img",{src:"/images/i18n.png",alt:""})],-1),i=a("h2",{id:"配置默认语言"},[a("a",{class:"header-anchor",href:"#配置默认语言","aria-hidden":"true"},"#"),t(" 配置默认语言")],-1),k=a("p",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/settings/localeSetting.ts",target:"_blank",rel:"noopener noreferrer"},"src/settings/localeSetting.ts"),t(" 内可以配置默认语言")],-1),d=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token constant"},"LOCALE"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t(),a("span",{class:"token punctuation"},"["),t("key"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token builtin"},"string"),a("span",{class:"token punctuation"},"]"),a("span",{class:"token operator"},":"),t(" LocaleType "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token constant"},"ZH_CN"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'zh_CN'"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token constant"},"EN_US"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'en'"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"const"),t(" localeSetting"),a("span",{class:"token operator"},":"),t(" LocaleSetting "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"// 是否显示语言选择器"),t("\n showPicker"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"true"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// 当前语言"),t("\n locale"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token constant"},"LOCALE"),a("span",{class:"token punctuation"},"."),a("span",{class:"token constant"},"ZH_CN"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// 默认语言"),t("\n fallback"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token constant"},"LOCALE"),a("span",{class:"token punctuation"},"."),a("span",{class:"token constant"},"ZH_CN"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// 允许的语言"),t("\n availableLocales"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"["),a("span",{class:"token constant"},"LOCALE"),a("span",{class:"token punctuation"},"."),a("span",{class:"token constant"},"ZH_CN"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token constant"},"LOCALE"),a("span",{class:"token punctuation"},"."),a("span",{class:"token constant"},"EN_US"),a("span",{class:"token punctuation"},"]"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token comment"},"// 配置语言列表"),t("\n"),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"const"),t(" localeList"),a("span",{class:"token operator"},":"),t(" DropMenu"),a("span",{class:"token punctuation"},"["),a("span",{class:"token punctuation"},"]"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token punctuation"},"["),t("\n "),a("span",{class:"token punctuation"},"{"),t("\n text"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'简体中文'"),a("span",{class:"token punctuation"},","),t("\n event"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'zh_CN'"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"{"),t("\n text"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'English'"),a("span",{class:"token punctuation"},","),t("\n event"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'en'"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"]"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),g=a("h2",{id:"配置"},[a("a",{class:"header-anchor",href:"#配置","aria-hidden":"true"},"#"),t(" 配置")],-1),m=a("p",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/setupI18n.ts",target:"_blank",rel:"noopener noreferrer"},"src/locales/setupI18n.ts"),t(" 内引入的 i18n 这个无需修改")],-1),h=a("h3",{id:"语言文件"},[a("a",{class:"header-anchor",href:"#语言文件","aria-hidden":"true"},"#"),t(" 语言文件")],-1),f=a("p",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/lang",target:"_blank",rel:"noopener noreferrer"},"src/locales/lang/"),t(" 可以配置具体的语言")],-1),y=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# locales/lang/"),t("\n\n"),a("span",{class:"token comment"},"# 中文语言"),t("\nzh_CN:\n component: 组件相关\n layout: 布局相关\n routes: 路由菜单相关\n sys: 系统页面相关\n\nen: 同上\n\n")])])],-1),b=a("h3",{id:"语言导入逻辑说明"},[a("a",{class:"header-anchor",href:"#语言导入逻辑说明","aria-hidden":"true"},"#"),t(" 语言导入逻辑说明")],-1),v=a("ol",null,[a("li",null,"初始化")],-1),w=a("p",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/setupI18n.ts",target:"_blank",rel:"noopener noreferrer"},"src/locales/setupI18n"),t(" 内的根语言文件可以看到")],-1),L=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"const"),t(" defaultLocal "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token keyword"},"import"),a("span",{class:"token punctuation"},"("),a("span",{class:"token template-string"},[a("span",{class:"token template-punctuation string"},"`"),a("span",{class:"token string"},"./lang/"),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token string"},".ts"),a("span",{class:"token template-punctuation string"},"`")]),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),I=a("p",null,[t("这会导入 "),a("code",null,"src/locales/lang/{lang}.ts"),t(" 文件语言包,此文件会导入对应语言下的所有文件。")],-1),_=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" genMessage "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'../helper'"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token keyword"},"import"),t(" antdLocale "),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'ant-design-vue/es/locale/zh_CN'"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token keyword"},"import"),t(" momentLocale "),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'moment/dist/locale/zh-cn'"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"const"),t(" modules "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token keyword"},"import"),a("span",{class:"token punctuation"},"."),t("meta"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"globEager"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'./zh_CN/**/*.ts'"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token punctuation"},"{"),t("\n message"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token operator"},"..."),a("span",{class:"token function"},"genMessage"),a("span",{class:"token punctuation"},"("),t("modules"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token string"},"'zh_CN'"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},","),t("\n antdLocale"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n momentLocale"),a("span",{class:"token punctuation"},","),t("\n momentLocaleName"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'zh-cn'"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),N=a("p",null,"并将其按相应的目录结构转化为多层级的",-1),C=a("p",null,"例:",-1),j=a("p",null,[a("code",null,"lang/zh_CN/components/modal.ts"),t(" 的文件内容为")],-1),x=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token punctuation"},"{"),t("\n title"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'标题'"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1),z=a("p",null,[t("则在使用的使用直接使用 "),a("code",null,"t('components.modal.title')"),t(" 进行获取。")],-1),E=a("p",null,"这样做的好处在于更容易管理大型项目的多语言。如果不需要分模块划分,可以直接自己手动导入即可。",-1),S=a("h2",{id:"使用"},[a("a",{class:"header-anchor",href:"#使用","aria-hidden":"true"},"#"),t(" 使用")],-1),M=a("p",null,[t("引入项目自带的 "),a("code",null,"useI18n"),t(),a("strong",null,"注意不要引入 vue-i18n 的 useI18n")],-1),O=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" useI18n "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/hooks/web/useI18n'"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token punctuation"},"{"),t(" t "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"useI18n"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"const"),t(" title "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"t"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'components.modal.title'"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),A=a("h2",{id:"切换语言"},[a("a",{class:"header-anchor",href:"#切换语言","aria-hidden":"true"},"#"),t(" 切换语言")],-1),P=a("p",null,[t("切换语言需要使用 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/useLocale.ts",target:"_blank",rel:"noopener noreferrer"},"src/locales/useLocale.ts")],-1),H=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" useLocale "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/locales/useLocale'"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token punctuation"},"{"),t(" changeLocale "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"useLocale"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token function"},"changeLocale"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'en'"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),Z=a("h2",{id:"新增"},[a("a",{class:"header-anchor",href:"#新增","aria-hidden":"true"},"#"),t(" 新增")],-1),T=a("h3",{id:"语言文件-1"},[a("a",{class:"header-anchor",href:"#语言文件-1","aria-hidden":"true"},"#"),t(" 语言文件")],-1),U=a("p",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/lang",target:"_blank",rel:"noopener noreferrer"},"src/locales/lang/"),t(" 增加对应语言的文件即可")],-1),W=a("h3",{id:"新增语言"},[a("a",{class:"header-anchor",href:"#新增语言","aria-hidden":"true"},"#"),t(" 新增语言")],-1),$=a("p",null,[t("目前项目自带的语言只有 "),a("code",null,"zh_CN"),t(" 和 "),a("code",null,"en"),t(" 两种")],-1),D=a("p",null,"如果需要新增,按以下操作即可",-1),F=a("ol",null,[a("li",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/lang",target:"_blank",rel:"noopener noreferrer"},"src/locales/lang/"),t(" 下新增相应的语言目录及语言文件并引入 引入 ant-design-vue 和 moment 对应的语言包")]),a("li",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/types/config",target:"_blank",rel:"noopener noreferrer"},"types/config.d.ts"),t(" 内加上预览类型定义")]),a("li",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/settings/localeSetting.ts",target:"_blank",rel:"noopener noreferrer"},"src/settings/localeSetting.ts"),t(" 修改语言配置")])],-1),q=a("h2",{id:"远程读取语言数据"},[a("a",{class:"header-anchor",href:"#远程读取语言数据","aria-hidden":"true"},"#"),t(" 远程读取语言数据")],-1),B=a("p",null,[t("目前项目会在 "),a("code",null,"src/main.ts"),t(" 内等待 "),a("code",null,"setupI18n"),t(" 这个函数执行完之后才会渲染界面,所以只需在 setupI18n 内发送 ajax 请求,将对应的数据设置到 i18n 实例上即可")],-1),G=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"// src/main.ts"),t("\n"),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token function"},"setupI18n"),a("span",{class:"token punctuation"},"("),t("app"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\napp"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"mount"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'#app'"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token boolean"},"true"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),J=a("h3",{id:"setupi18n-函数"},[a("a",{class:"header-anchor",href:"#setupi18n-函数","aria-hidden":"true"},"#"),t(" setupI18n 函数")],-1),K=a("p",null,[t("代码: "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/setupI18n.ts",target:"_blank",rel:"noopener noreferrer"},"src/locales/setupI18n/")],-1),Q=a("p",null,"如下所示,这里会先设置一个默认语言,默认语言可以设置在本地,也可以在这里等待接口返回默认语言",-1),R=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"// setup i18n instance with glob"),t("\n"),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"async"),t(),a("span",{class:"token keyword"},"function"),t(),a("span",{class:"token function"},"setupI18n"),a("span",{class:"token punctuation"},"("),t("app"),a("span",{class:"token operator"},":"),t(" App"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(" options "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token function"},"createI18nOptions"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n i18n "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"createI18n"),a("span",{class:"token punctuation"},"("),t("options"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"as"),t(" I18n"),a("span",{class:"token punctuation"},";"),t("\n app"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"use"),a("span",{class:"token punctuation"},"("),t("i18n"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n\n"),a("span",{class:"token keyword"},"async"),t(),a("span",{class:"token keyword"},"function"),t(),a("span",{class:"token function"},"createI18nOptions"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token builtin"},"Promise"),a("span",{class:"token operator"},"<"),t("I18nOptions"),a("span",{class:"token operator"},">"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(" locale "),a("span",{class:"token operator"},"="),t(" localeStore"),a("span",{class:"token punctuation"},"."),t("getLocale"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token comment"},"// 这里改成接口获取"),t("\n "),a("span",{class:"token keyword"},"const"),t(" defaultLocal "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token keyword"},"import"),a("span",{class:"token punctuation"},"("),a("span",{class:"token template-string"},[a("span",{class:"token template-punctuation string"},"`"),a("span",{class:"token string"},"./lang/"),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token string"},".ts"),a("span",{class:"token template-punctuation string"},"`")]),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"const"),t(" message "),a("span",{class:"token operator"},"="),t(" defaultLocal"),a("span",{class:"token punctuation"},"."),a("span",{class:"token keyword"},"default"),a("span",{class:"token operator"},"?."),t("message "),a("span",{class:"token operator"},"??"),t(),a("span",{class:"token punctuation"},"{"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"return"),t(),a("span",{class:"token punctuation"},"{"),t("\n legacy"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"false"),a("span",{class:"token punctuation"},","),t("\n locale"),a("span",{class:"token punctuation"},","),t("\n fallbackLocale"),a("span",{class:"token operator"},":"),t(" fallback"),a("span",{class:"token punctuation"},","),t("\n messages"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token punctuation"},"["),t("locale"),a("span",{class:"token punctuation"},"]"),a("span",{class:"token operator"},":"),t(" message"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n availableLocales"),a("span",{class:"token operator"},":"),t(" availableLocales"),a("span",{class:"token punctuation"},","),t("\n sync"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"true"),a("span",{class:"token punctuation"},","),t("\n silentTranslationWarn"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"true"),a("span",{class:"token punctuation"},","),t("\n missingWarn"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"false"),a("span",{class:"token punctuation"},","),t("\n silentFallbackWarn"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"true"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1),V=a("h3",{id:"changelocale-函数"},[a("a",{class:"header-anchor",href:"#changelocale-函数","aria-hidden":"true"},"#"),t(" changeLocale 函数")],-1),X=a("p",null,[t("代码: "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/useLocale",target:"_blank",rel:"noopener noreferrer"},"src/locales/useLocale/")],-1),Y=a("p",null,[t("当手动切换语言的时候会触发 "),a("code",null,"useLocale"),t(" 函数,useLocale 也是异步函数,只需等待接口返回响应的数据后,再进行设置即可")],-1),nn=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"async"),t(),a("span",{class:"token keyword"},"function"),t(),a("span",{class:"token function"},"changeLocale"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token operator"},":"),t(" LocaleType"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(" globalI18n "),a("span",{class:"token operator"},"="),t(" i18n"),a("span",{class:"token punctuation"},"."),t("global"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"const"),t(" currentLocale "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"unref"),a("span",{class:"token punctuation"},"("),t("globalI18n"),a("span",{class:"token punctuation"},"."),t("locale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),t("currentLocale "),a("span",{class:"token operator"},"==="),t(" locale"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"return"),t(" locale"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),t("loadLocalePool"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"includes"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token function"},"setI18nLanguage"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"return"),t(" locale"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n "),a("span",{class:"token comment"},"// 这里改成接口获取"),t("\n "),a("span",{class:"token keyword"},"const"),t(" langModule "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token keyword"},"import"),a("span",{class:"token punctuation"},"("),a("span",{class:"token template-string"},[a("span",{class:"token template-punctuation string"},"`"),a("span",{class:"token string"},"./lang/"),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token string"},".ts"),a("span",{class:"token template-punctuation string"},"`")]),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"as"),t(),a("span",{class:"token builtin"},"any"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},"."),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token keyword"},"as"),t(" LangModule"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token operator"},"!"),t("langModule"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"return"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token punctuation"},"{"),t(" message"),a("span",{class:"token punctuation"},","),t(" momentLocale"),a("span",{class:"token punctuation"},","),t(" momentLocaleName "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token operator"},"="),t(" langModule"),a("span",{class:"token punctuation"},";"),t("\n\n globalI18n"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"setLocaleMessage"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token punctuation"},","),t(" message"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n moment"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"updateLocale"),a("span",{class:"token punctuation"},"("),t("momentLocaleName"),a("span",{class:"token punctuation"},","),t(" momentLocale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n loadLocalePool"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"push"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token function"},"setI18nLanguage"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"return"),t(" locale"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1);o.render=function(a,t,e,o,sn,an){return n(),s("div",null,[c,l,p,u,r,i,k,d,g,m,h,f,y,b,v,w,L,I,_,N,C,j,x,z,E,S,M,O,A,P,H,Z,T,U,W,$,D,F,q,B,G,J,K,Q,R,V,X,Y,nn])};export default o;export{e as __pageData}; diff --git a/assets/dep_i18n.md.a96f197a.lean.js b/assets/dep_i18n.md.a96f197a.lean.js new file mode 100644 index 00000000..46a18fb9 --- /dev/null +++ b/assets/dep_i18n.md.a96f197a.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,b as a,d as t}from"./app.8cddb23b.js";const e='{"title":"国际化","description":"","frontmatter":{},"headers":[{"level":2,"title":"I18n-ally 插件","slug":"i18n-ally-插件"},{"level":2,"title":"配置默认语言","slug":"配置默认语言"},{"level":2,"title":"配置","slug":"配置"},{"level":3,"title":"语言文件","slug":"语言文件"},{"level":3,"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":"setupI18n 函数","slug":"setupi18n-函数"},{"level":3,"title":"changeLocale 函数","slug":"changelocale-函数"}],"relativePath":"dep/i18n.md","lastUpdated":1697592578593}',o={},c=a("h1",{id:"国际化"},[a("a",{class:"header-anchor",href:"#国际化","aria-hidden":"true"},"#"),t(" 国际化")],-1),l=a("p",null,[t("如果你使用的 vscode 开发工具,则推荐安装 "),a("a",{href:"https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally",target:"_blank",rel:"noopener noreferrer"},"I18n-ally"),t(" 这个插件")],-1),p=a("h2",{id:"i18n-ally-插件"},[a("a",{class:"header-anchor",href:"#i18n-ally-插件","aria-hidden":"true"},"#"),t(" I18n-ally 插件")],-1),u=a("p",null,"安装了该插件后,你的代码内可以实时看到对应的语言内容",-1),r=a("p",null,[a("img",{src:"/images/i18n.png",alt:""})],-1),i=a("h2",{id:"配置默认语言"},[a("a",{class:"header-anchor",href:"#配置默认语言","aria-hidden":"true"},"#"),t(" 配置默认语言")],-1),k=a("p",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/settings/localeSetting.ts",target:"_blank",rel:"noopener noreferrer"},"src/settings/localeSetting.ts"),t(" 内可以配置默认语言")],-1),d=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token constant"},"LOCALE"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t(),a("span",{class:"token punctuation"},"["),t("key"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token builtin"},"string"),a("span",{class:"token punctuation"},"]"),a("span",{class:"token operator"},":"),t(" LocaleType "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token constant"},"ZH_CN"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'zh_CN'"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token constant"},"EN_US"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'en'"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"const"),t(" localeSetting"),a("span",{class:"token operator"},":"),t(" LocaleSetting "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"// 是否显示语言选择器"),t("\n showPicker"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"true"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// 当前语言"),t("\n locale"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token constant"},"LOCALE"),a("span",{class:"token punctuation"},"."),a("span",{class:"token constant"},"ZH_CN"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// 默认语言"),t("\n fallback"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token constant"},"LOCALE"),a("span",{class:"token punctuation"},"."),a("span",{class:"token constant"},"ZH_CN"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// 允许的语言"),t("\n availableLocales"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"["),a("span",{class:"token constant"},"LOCALE"),a("span",{class:"token punctuation"},"."),a("span",{class:"token constant"},"ZH_CN"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token constant"},"LOCALE"),a("span",{class:"token punctuation"},"."),a("span",{class:"token constant"},"EN_US"),a("span",{class:"token punctuation"},"]"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token comment"},"// 配置语言列表"),t("\n"),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"const"),t(" localeList"),a("span",{class:"token operator"},":"),t(" DropMenu"),a("span",{class:"token punctuation"},"["),a("span",{class:"token punctuation"},"]"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token punctuation"},"["),t("\n "),a("span",{class:"token punctuation"},"{"),t("\n text"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'简体中文'"),a("span",{class:"token punctuation"},","),t("\n event"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'zh_CN'"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"{"),t("\n text"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'English'"),a("span",{class:"token punctuation"},","),t("\n event"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'en'"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"]"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),g=a("h2",{id:"配置"},[a("a",{class:"header-anchor",href:"#配置","aria-hidden":"true"},"#"),t(" 配置")],-1),m=a("p",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/setupI18n.ts",target:"_blank",rel:"noopener noreferrer"},"src/locales/setupI18n.ts"),t(" 内引入的 i18n 这个无需修改")],-1),h=a("h3",{id:"语言文件"},[a("a",{class:"header-anchor",href:"#语言文件","aria-hidden":"true"},"#"),t(" 语言文件")],-1),f=a("p",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/lang",target:"_blank",rel:"noopener noreferrer"},"src/locales/lang/"),t(" 可以配置具体的语言")],-1),y=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# locales/lang/"),t("\n\n"),a("span",{class:"token comment"},"# 中文语言"),t("\nzh_CN:\n component: 组件相关\n layout: 布局相关\n routes: 路由菜单相关\n sys: 系统页面相关\n\nen: 同上\n\n")])])],-1),b=a("h3",{id:"语言导入逻辑说明"},[a("a",{class:"header-anchor",href:"#语言导入逻辑说明","aria-hidden":"true"},"#"),t(" 语言导入逻辑说明")],-1),v=a("ol",null,[a("li",null,"初始化")],-1),w=a("p",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/setupI18n.ts",target:"_blank",rel:"noopener noreferrer"},"src/locales/setupI18n"),t(" 内的根语言文件可以看到")],-1),L=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"const"),t(" defaultLocal "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token keyword"},"import"),a("span",{class:"token punctuation"},"("),a("span",{class:"token template-string"},[a("span",{class:"token template-punctuation string"},"`"),a("span",{class:"token string"},"./lang/"),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token string"},".ts"),a("span",{class:"token template-punctuation string"},"`")]),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),I=a("p",null,[t("这会导入 "),a("code",null,"src/locales/lang/{lang}.ts"),t(" 文件语言包,此文件会导入对应语言下的所有文件。")],-1),_=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" genMessage "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'../helper'"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token keyword"},"import"),t(" antdLocale "),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'ant-design-vue/es/locale/zh_CN'"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token keyword"},"import"),t(" momentLocale "),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'moment/dist/locale/zh-cn'"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"const"),t(" modules "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token keyword"},"import"),a("span",{class:"token punctuation"},"."),t("meta"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"globEager"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'./zh_CN/**/*.ts'"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token punctuation"},"{"),t("\n message"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token operator"},"..."),a("span",{class:"token function"},"genMessage"),a("span",{class:"token punctuation"},"("),t("modules"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token string"},"'zh_CN'"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},","),t("\n antdLocale"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n momentLocale"),a("span",{class:"token punctuation"},","),t("\n momentLocaleName"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'zh-cn'"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),N=a("p",null,"并将其按相应的目录结构转化为多层级的",-1),C=a("p",null,"例:",-1),j=a("p",null,[a("code",null,"lang/zh_CN/components/modal.ts"),t(" 的文件内容为")],-1),x=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token punctuation"},"{"),t("\n title"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'标题'"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1),z=a("p",null,[t("则在使用的使用直接使用 "),a("code",null,"t('components.modal.title')"),t(" 进行获取。")],-1),E=a("p",null,"这样做的好处在于更容易管理大型项目的多语言。如果不需要分模块划分,可以直接自己手动导入即可。",-1),S=a("h2",{id:"使用"},[a("a",{class:"header-anchor",href:"#使用","aria-hidden":"true"},"#"),t(" 使用")],-1),M=a("p",null,[t("引入项目自带的 "),a("code",null,"useI18n"),t(),a("strong",null,"注意不要引入 vue-i18n 的 useI18n")],-1),O=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" useI18n "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/hooks/web/useI18n'"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token punctuation"},"{"),t(" t "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"useI18n"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"const"),t(" title "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"t"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'components.modal.title'"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),A=a("h2",{id:"切换语言"},[a("a",{class:"header-anchor",href:"#切换语言","aria-hidden":"true"},"#"),t(" 切换语言")],-1),P=a("p",null,[t("切换语言需要使用 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/useLocale.ts",target:"_blank",rel:"noopener noreferrer"},"src/locales/useLocale.ts")],-1),H=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" useLocale "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/locales/useLocale'"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token punctuation"},"{"),t(" changeLocale "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"useLocale"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token function"},"changeLocale"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'en'"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),Z=a("h2",{id:"新增"},[a("a",{class:"header-anchor",href:"#新增","aria-hidden":"true"},"#"),t(" 新增")],-1),T=a("h3",{id:"语言文件-1"},[a("a",{class:"header-anchor",href:"#语言文件-1","aria-hidden":"true"},"#"),t(" 语言文件")],-1),U=a("p",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/lang",target:"_blank",rel:"noopener noreferrer"},"src/locales/lang/"),t(" 增加对应语言的文件即可")],-1),W=a("h3",{id:"新增语言"},[a("a",{class:"header-anchor",href:"#新增语言","aria-hidden":"true"},"#"),t(" 新增语言")],-1),$=a("p",null,[t("目前项目自带的语言只有 "),a("code",null,"zh_CN"),t(" 和 "),a("code",null,"en"),t(" 两种")],-1),D=a("p",null,"如果需要新增,按以下操作即可",-1),F=a("ol",null,[a("li",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/lang",target:"_blank",rel:"noopener noreferrer"},"src/locales/lang/"),t(" 下新增相应的语言目录及语言文件并引入 引入 ant-design-vue 和 moment 对应的语言包")]),a("li",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/types/config",target:"_blank",rel:"noopener noreferrer"},"types/config.d.ts"),t(" 内加上预览类型定义")]),a("li",null,[t("在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/settings/localeSetting.ts",target:"_blank",rel:"noopener noreferrer"},"src/settings/localeSetting.ts"),t(" 修改语言配置")])],-1),q=a("h2",{id:"远程读取语言数据"},[a("a",{class:"header-anchor",href:"#远程读取语言数据","aria-hidden":"true"},"#"),t(" 远程读取语言数据")],-1),B=a("p",null,[t("目前项目会在 "),a("code",null,"src/main.ts"),t(" 内等待 "),a("code",null,"setupI18n"),t(" 这个函数执行完之后才会渲染界面,所以只需在 setupI18n 内发送 ajax 请求,将对应的数据设置到 i18n 实例上即可")],-1),G=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"// src/main.ts"),t("\n"),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token function"},"setupI18n"),a("span",{class:"token punctuation"},"("),t("app"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\napp"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"mount"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'#app'"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token boolean"},"true"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),J=a("h3",{id:"setupi18n-函数"},[a("a",{class:"header-anchor",href:"#setupi18n-函数","aria-hidden":"true"},"#"),t(" setupI18n 函数")],-1),K=a("p",null,[t("代码: "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/setupI18n.ts",target:"_blank",rel:"noopener noreferrer"},"src/locales/setupI18n/")],-1),Q=a("p",null,"如下所示,这里会先设置一个默认语言,默认语言可以设置在本地,也可以在这里等待接口返回默认语言",-1),R=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"// setup i18n instance with glob"),t("\n"),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"async"),t(),a("span",{class:"token keyword"},"function"),t(),a("span",{class:"token function"},"setupI18n"),a("span",{class:"token punctuation"},"("),t("app"),a("span",{class:"token operator"},":"),t(" App"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(" options "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token function"},"createI18nOptions"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n i18n "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"createI18n"),a("span",{class:"token punctuation"},"("),t("options"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"as"),t(" I18n"),a("span",{class:"token punctuation"},";"),t("\n app"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"use"),a("span",{class:"token punctuation"},"("),t("i18n"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n\n"),a("span",{class:"token keyword"},"async"),t(),a("span",{class:"token keyword"},"function"),t(),a("span",{class:"token function"},"createI18nOptions"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token builtin"},"Promise"),a("span",{class:"token operator"},"<"),t("I18nOptions"),a("span",{class:"token operator"},">"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(" locale "),a("span",{class:"token operator"},"="),t(" localeStore"),a("span",{class:"token punctuation"},"."),t("getLocale"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token comment"},"// 这里改成接口获取"),t("\n "),a("span",{class:"token keyword"},"const"),t(" defaultLocal "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token keyword"},"import"),a("span",{class:"token punctuation"},"("),a("span",{class:"token template-string"},[a("span",{class:"token template-punctuation string"},"`"),a("span",{class:"token string"},"./lang/"),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token string"},".ts"),a("span",{class:"token template-punctuation string"},"`")]),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"const"),t(" message "),a("span",{class:"token operator"},"="),t(" defaultLocal"),a("span",{class:"token punctuation"},"."),a("span",{class:"token keyword"},"default"),a("span",{class:"token operator"},"?."),t("message "),a("span",{class:"token operator"},"??"),t(),a("span",{class:"token punctuation"},"{"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"return"),t(),a("span",{class:"token punctuation"},"{"),t("\n legacy"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"false"),a("span",{class:"token punctuation"},","),t("\n locale"),a("span",{class:"token punctuation"},","),t("\n fallbackLocale"),a("span",{class:"token operator"},":"),t(" fallback"),a("span",{class:"token punctuation"},","),t("\n messages"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token punctuation"},"["),t("locale"),a("span",{class:"token punctuation"},"]"),a("span",{class:"token operator"},":"),t(" message"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n availableLocales"),a("span",{class:"token operator"},":"),t(" availableLocales"),a("span",{class:"token punctuation"},","),t("\n sync"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"true"),a("span",{class:"token punctuation"},","),t("\n silentTranslationWarn"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"true"),a("span",{class:"token punctuation"},","),t("\n missingWarn"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"false"),a("span",{class:"token punctuation"},","),t("\n silentFallbackWarn"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token boolean"},"true"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1),V=a("h3",{id:"changelocale-函数"},[a("a",{class:"header-anchor",href:"#changelocale-函数","aria-hidden":"true"},"#"),t(" changeLocale 函数")],-1),X=a("p",null,[t("代码: "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/locales/useLocale",target:"_blank",rel:"noopener noreferrer"},"src/locales/useLocale/")],-1),Y=a("p",null,[t("当手动切换语言的时候会触发 "),a("code",null,"useLocale"),t(" 函数,useLocale 也是异步函数,只需等待接口返回响应的数据后,再进行设置即可")],-1),nn=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"async"),t(),a("span",{class:"token keyword"},"function"),t(),a("span",{class:"token function"},"changeLocale"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token operator"},":"),t(" LocaleType"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(" globalI18n "),a("span",{class:"token operator"},"="),t(" i18n"),a("span",{class:"token punctuation"},"."),t("global"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"const"),t(" currentLocale "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"unref"),a("span",{class:"token punctuation"},"("),t("globalI18n"),a("span",{class:"token punctuation"},"."),t("locale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),t("currentLocale "),a("span",{class:"token operator"},"==="),t(" locale"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"return"),t(" locale"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),t("loadLocalePool"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"includes"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token function"},"setI18nLanguage"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"return"),t(" locale"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n "),a("span",{class:"token comment"},"// 这里改成接口获取"),t("\n "),a("span",{class:"token keyword"},"const"),t(" langModule "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token keyword"},"import"),a("span",{class:"token punctuation"},"("),a("span",{class:"token template-string"},[a("span",{class:"token template-punctuation string"},"`"),a("span",{class:"token string"},"./lang/"),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("locale"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token string"},".ts"),a("span",{class:"token template-punctuation string"},"`")]),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"as"),t(),a("span",{class:"token builtin"},"any"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},"."),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token keyword"},"as"),t(" LangModule"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token operator"},"!"),t("langModule"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"return"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token punctuation"},"{"),t(" message"),a("span",{class:"token punctuation"},","),t(" momentLocale"),a("span",{class:"token punctuation"},","),t(" momentLocaleName "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token operator"},"="),t(" langModule"),a("span",{class:"token punctuation"},";"),t("\n\n globalI18n"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"setLocaleMessage"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token punctuation"},","),t(" message"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n moment"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"updateLocale"),a("span",{class:"token punctuation"},"("),t("momentLocaleName"),a("span",{class:"token punctuation"},","),t(" momentLocale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n loadLocalePool"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"push"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token function"},"setI18nLanguage"),a("span",{class:"token punctuation"},"("),t("locale"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"return"),t(" locale"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1);o.render=function(a,t,e,o,sn,an){return n(),s("div",null,[c,l,p,u,r,i,k,d,g,m,h,f,y,b,v,w,L,I,_,N,C,j,x,z,E,S,M,O,A,P,H,Z,T,U,W,$,D,F,q,B,G,J,K,Q,R,V,X,Y,nn])};export default o;export{e as __pageData}; diff --git a/assets/dep_icon.md.360e0f2b.js b/assets/dep_icon.md.360e0f2b.js new file mode 100644 index 00000000..9eb1480b --- /dev/null +++ b/assets/dep_icon.md.360e0f2b.js @@ -0,0 +1 @@ +import{o as n,c as s,b as a,d as t}from"./app.8cddb23b.js";const o='{"title":"图标","description":"","frontmatter":{},"headers":[{"level":2,"title":"组件库图标","slug":"组件库图标"},{"level":2,"title":"Svg Sprite 图标","slug":"svg-sprite-图标"},{"level":3,"title":"使用","slug":"使用"},{"level":2,"title":"Iconify 图标","slug":"iconify-图标"},{"level":2,"title":"图标选择器","slug":"图标选择器"},{"level":3,"title":"图标集预生成","slug":"图标集预生成"},{"level":3,"title":"生成","slug":"生成"},{"level":3,"title":"优缺点","slug":"优缺点"}],"relativePath":"dep/icon.md","lastUpdated":1697592578593}',p={},c=a("h1",{id:"图标"},[a("a",{class:"header-anchor",href:"#图标","aria-hidden":"true"},"#"),t(" 图标")],-1),e=a("p",null,"项目中有以下多种图标使用方式。",-1),l=a("h2",{id:"组件库图标"},[a("a",{class:"header-anchor",href:"#组件库图标","aria-hidden":"true"},"#"),t(" 组件库图标")],-1),u=a("p",null,[t("使用 "),a("code",null,"ant-design-vue"),t(" 提供的图标")],-1),k=a("div",{class:"language-vue"},[a("pre",null,[a("code",null,[a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("template")]),a("span",{class:"token punctuation"},">")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("StarOutlined")]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("StarFilled")]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("StarTwoTone")]),t(),a("span",{class:"token attr-name"},"twoToneColor"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("#eb2f96"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("script")]),a("span",{class:"token punctuation"},">")]),a("span",{class:"token script"},[a("span",{class:"token language-javascript"},[t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" defineComponent "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" StarOutlined"),a("span",{class:"token punctuation"},","),t(" StarFilled"),a("span",{class:"token punctuation"},","),t(" StarTwoTone "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'@ant-design/icons-vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token function"},"defineComponent"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"{"),t("\n components"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t(" StarOutlined"),a("span",{class:"token punctuation"},","),t(" StarFilled"),a("span",{class:"token punctuation"},","),t(" StarTwoTone "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n")])])],-1),i=a("h2",{id:"svg-sprite-图标"},[a("a",{class:"header-anchor",href:"#svg-sprite-图标","aria-hidden":"true"},"#"),t(" Svg Sprite 图标")],-1),r=a("h3",{id:"使用"},[a("a",{class:"header-anchor",href:"#使用","aria-hidden":"true"},"#"),t(" 使用")],-1),g=a("p",null,[t("将需要的 svg 图标放到"),a("code",null,"src/assets/icons"),t("内")],-1),d=a("p",null,"例: test.svg",-1),f=a("ol",null,[a("li",null,[t("使用"),a("code",null,"SvgIcon"),t("组件进行展示")])],-1),m=a("div",{class:"language-vue"},[a("pre",null,[a("code",null,[a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("template")]),a("span",{class:"token punctuation"},">")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("SvgIcon")]),t(),a("span",{class:"token attr-name"},"name"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("test"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("script")]),a("span",{class:"token punctuation"},">")]),a("span",{class:"token script"},[a("span",{class:"token language-javascript"},[t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" defineComponent "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" SvgIcon "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/components/Icon'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token function"},"defineComponent"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"{"),t("\n components"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t(" SvgIcon "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n")])])],-1),y=a("ol",{start:"2"},[a("li",null,[t("使用"),a("code",null,"Icon"),t("组件进行展示")])],-1),v=a("p",null,[t("以 "),a("code",null,"|svg"),t(" 结尾会自动使用"),a("code",null,"SvgIcon"),t("组件")],-1),w=a("div",{class:"language-vue"},[a("pre",null,[a("code",null,[a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("template")]),a("span",{class:"token punctuation"},">")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("Icon")]),t(),a("span",{class:"token attr-name"},"name"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("test|svg"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("script")]),a("span",{class:"token punctuation"},">")]),a("span",{class:"token script"},[a("span",{class:"token language-javascript"},[t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" defineComponent "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" Icon "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/components/Icon'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token function"},"defineComponent"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"{"),t("\n components"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t(" Icon "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n")])])],-1),h=a("h2",{id:"iconify-图标"},[a("a",{class:"header-anchor",href:"#iconify-图标","aria-hidden":"true"},"#"),t(" Iconify 图标")],-1),S=a("p",null,[t("使用方式请参考 "),a("a",{href:"./../components/icon.html"},"Icon 组件")],-1),I=a("p",null,[t("项目中使用到的是 "),a("a",{href:"https://github.com/antfu/purge-icons/blob/main/packages/vite-plugin-purge-icons/README.md",target:"_blank",rel:"noopener noreferrer"},"vite-plugin-purge-icons"),t(" 这个插件来进行图标实现。")],-1),b=a("ol",null,[a("li",null,"安装依赖")],-1),T=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("\n"),a("span",{class:"token function"},"yarn"),t(),a("span",{class:"token function"},"add"),t(" @iconify/iconify\n\n"),a("span",{class:"token function"},"yarn"),t(),a("span",{class:"token function"},"add"),t(" @iconify/json @purge-icons/generated -D\n\n")])])],-1),x=a("ol",{start:"2"},[a("li",null,[t("在 "),a("code",null,"vite.config.ts"),t("内引入插件")])],-1),C=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"import"),t(" PurgeIcons "),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vite-plugin-purge-icons'"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token punctuation"},"{"),t("\n plugins"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"["),a("span",{class:"token function"},"PurgeIcons"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},"]"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),q=a("ol",{start:"3"},[a("li",null,"编写 Icon 组件")],-1),_=a("p",null,[t("完整代码 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/blob/main/src/components/Icon/src/Icon.vue",target:"_blank",rel:"noopener noreferrer"},"src/components/Icon/src/Icon.vue")],-1),z=a("div",{class:"language-vue"},[a("pre",null,[a("code",null,[a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("template")]),a("span",{class:"token punctuation"},">")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("SvgIcon")]),t(),a("span",{class:"token attr-name"},":size"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("size"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token attr-name"},":name"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("getSvgIcon"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token attr-name"},"v-if"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("isSvgIcon"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token attr-name"},":class"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("[$attrs.class]"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token attr-name"},":spin"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("spin"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("span")]),t("\n "),a("span",{class:"token attr-name"},"v-else"),t("\n "),a("span",{class:"token attr-name"},"ref"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("elRef"),a("span",{class:"token punctuation"},'"')]),t("\n "),a("span",{class:"token attr-name"},":class"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("[$attrs.class, "),a("span",{class:"token punctuation"},"'"),t("app-iconify anticon"),a("span",{class:"token punctuation"},"'"),t(", spin && "),a("span",{class:"token punctuation"},"'"),t("app-iconify-spin"),a("span",{class:"token punctuation"},"'"),t("]"),a("span",{class:"token punctuation"},'"')]),t("\n "),a("span",{class:"token attr-name"},":style"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("getWrapStyle"),a("span",{class:"token punctuation"},'"')]),t("\n "),a("span",{class:"token punctuation"},">")]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("script")]),t(),a("span",{class:"token attr-name"},"lang"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("ts"),a("span",{class:"token punctuation"},'"')]),a("span",{class:"token punctuation"},">")]),a("span",{class:"token script"},[a("span",{class:"token language-javascript"},[t("\n "),a("span",{class:"token keyword"},"import"),t(" type "),a("span",{class:"token punctuation"},"{"),t(" PropType "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t("\n defineComponent"),a("span",{class:"token punctuation"},","),t("\n ref"),a("span",{class:"token punctuation"},","),t("\n watch"),a("span",{class:"token punctuation"},","),t("\n onMounted"),a("span",{class:"token punctuation"},","),t("\n nextTick"),a("span",{class:"token punctuation"},","),t("\n unref"),a("span",{class:"token punctuation"},","),t("\n computed"),a("span",{class:"token punctuation"},","),t("\n CSSProperties"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue'"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"import"),t(" SvgIcon "),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'./SvgIcon.vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(" Iconify "),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'@purge-icons/generated'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" isString "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/utils/is'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" propTypes "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/utils/propTypes'"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token constant"},"SVG_END_WITH_FLAG"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token string"},"'|svg'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token function"},"defineComponent"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"{"),t("\n name"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'GIcon'"),a("span",{class:"token punctuation"},","),t("\n components"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t(" SvgIcon "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n props"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"// icon name"),t("\n icon"),a("span",{class:"token operator"},":"),t(" propTypes"),a("span",{class:"token punctuation"},"."),t("string"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// icon color"),t("\n color"),a("span",{class:"token operator"},":"),t(" propTypes"),a("span",{class:"token punctuation"},"."),t("string"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// icon size"),t("\n size"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t("\n type"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"["),t("String"),a("span",{class:"token punctuation"},","),t(" Number"),a("span",{class:"token punctuation"},"]"),t(),a("span",{class:"token keyword"},"as"),t(" PropType"),a("span",{class:"token operator"},"<"),t("string "),a("span",{class:"token operator"},"|"),t(" number"),a("span",{class:"token operator"},">"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token keyword"},"default"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token number"},"16"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n spin"),a("span",{class:"token operator"},":"),t(" propTypes"),a("span",{class:"token punctuation"},"."),t("bool"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"def"),a("span",{class:"token punctuation"},"("),a("span",{class:"token boolean"},"false"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},","),t("\n prefix"),a("span",{class:"token operator"},":"),t(" propTypes"),a("span",{class:"token punctuation"},"."),t("string"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"def"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"''"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token function"},"setup"),a("span",{class:"token punctuation"},"("),a("span",{class:"token parameter"},"props"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(" elRef "),a("span",{class:"token operator"},"="),t(" ref"),a("span",{class:"token operator"},"<"),t("ElRef"),a("span",{class:"token operator"},">"),a("span",{class:"token punctuation"},"("),a("span",{class:"token keyword"},"null"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(" isSvgIcon "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"computed"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token operator"},"=>"),t(" props"),a("span",{class:"token punctuation"},"."),t("icon"),a("span",{class:"token operator"},"?."),a("span",{class:"token function"},"endsWith"),a("span",{class:"token punctuation"},"("),a("span",{class:"token constant"},"SVG_END_WITH_FLAG"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"const"),t(" getSvgIcon "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"computed"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token operator"},"=>"),t(" props"),a("span",{class:"token punctuation"},"."),t("icon"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"replace"),a("span",{class:"token punctuation"},"("),a("span",{class:"token constant"},"SVG_END_WITH_FLAG"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token string"},"''"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"const"),t(" getIconRef "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"computed"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token operator"},"=>"),t(),a("span",{class:"token template-string"},[a("span",{class:"token template-punctuation string"},"`"),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("props"),a("span",{class:"token punctuation"},"."),t("prefix "),a("span",{class:"token operator"},"?"),t(" props"),a("span",{class:"token punctuation"},"."),t("prefix "),a("span",{class:"token operator"},"+"),t(),a("span",{class:"token string"},"':'"),t(),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"''"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("props"),a("span",{class:"token punctuation"},"."),t("icon"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token template-punctuation string"},"`")]),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token function-variable function"},"update"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token keyword"},"async"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token operator"},"=>"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token function"},"unref"),a("span",{class:"token punctuation"},"("),t("isSvgIcon"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"return"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(" el "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"unref"),a("span",{class:"token punctuation"},"("),t("elRef"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token operator"},"!"),t("el"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"return"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token function"},"nextTick"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"const"),t(" icon "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"unref"),a("span",{class:"token punctuation"},"("),t("getIconRef"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token operator"},"!"),t("icon"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"return"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(" svg "),a("span",{class:"token operator"},"="),t(" Iconify"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"renderSVG"),a("span",{class:"token punctuation"},"("),t("icon"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token punctuation"},"{"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),t("svg"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n el"),a("span",{class:"token punctuation"},"."),t("textContent "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token string"},"''"),a("span",{class:"token punctuation"},";"),t("\n el"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"appendChild"),a("span",{class:"token punctuation"},"("),t("svg"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"else"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(" span "),a("span",{class:"token operator"},"="),t(" document"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"createElement"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'span'"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n span"),a("span",{class:"token punctuation"},"."),t("className "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token string"},"'iconify'"),a("span",{class:"token punctuation"},";"),t("\n span"),a("span",{class:"token punctuation"},"."),t("dataset"),a("span",{class:"token punctuation"},"."),t("icon "),a("span",{class:"token operator"},"="),t(" icon"),a("span",{class:"token punctuation"},";"),t("\n el"),a("span",{class:"token punctuation"},"."),t("textContent "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token string"},"''"),a("span",{class:"token punctuation"},";"),t("\n el"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"appendChild"),a("span",{class:"token punctuation"},"("),t("span"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(" getWrapStyle "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"computed"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token parameter"},"CSSProperties"),t(),a("span",{class:"token operator"},"=>"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token punctuation"},"{"),t(" size"),a("span",{class:"token punctuation"},","),t(" color "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token operator"},"="),t(" props"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"let"),t(" fs "),a("span",{class:"token operator"},"="),t(" size"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token function"},"isString"),a("span",{class:"token punctuation"},"("),t("size"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n fs "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"parseInt"),a("span",{class:"token punctuation"},"("),t("size"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token number"},"10"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n\n "),a("span",{class:"token keyword"},"return"),t(),a("span",{class:"token punctuation"},"{"),t("\n fontSize"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token template-string"},[a("span",{class:"token template-punctuation string"},"`"),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("fs"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token string"},"px"),a("span",{class:"token template-punctuation string"},"`")]),a("span",{class:"token punctuation"},","),t("\n color"),a("span",{class:"token operator"},":"),t(" color"),a("span",{class:"token punctuation"},","),t("\n display"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'inline-flex'"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token function"},"watch"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token operator"},"=>"),t(" props"),a("span",{class:"token punctuation"},"."),t("icon"),a("span",{class:"token punctuation"},","),t(" update"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token punctuation"},"{"),t(" flush"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'post'"),t(),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token function"},"onMounted"),a("span",{class:"token punctuation"},"("),t("update"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"return"),t(),a("span",{class:"token punctuation"},"{"),t(" elRef"),a("span",{class:"token punctuation"},","),t(" getWrapStyle"),a("span",{class:"token punctuation"},","),t(" isSvgIcon"),a("span",{class:"token punctuation"},","),t(" getSvgIcon "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("style")]),t(),a("span",{class:"token attr-name"},"lang"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("less"),a("span",{class:"token punctuation"},'"')]),a("span",{class:"token punctuation"},">")]),a("span",{class:"token style"},[a("span",{class:"token language-css"},[t("\n "),a("span",{class:"token selector"},".app-iconify"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token property"},"display"),a("span",{class:"token punctuation"},":"),t(" inline-block"),a("span",{class:"token punctuation"},";"),t("\n // "),a("span",{class:"token property"},"vertical-align"),a("span",{class:"token punctuation"},":"),t(" middle"),a("span",{class:"token selector"},";\n\n &-spin"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token selector"},"svg"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token property"},"animation"),a("span",{class:"token punctuation"},":"),t(" loadingCircle 1s infinite linear"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n\n "),a("span",{class:"token selector"},"span.iconify"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token property"},"display"),a("span",{class:"token punctuation"},":"),t(" block"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token property"},"min-width"),a("span",{class:"token punctuation"},":"),t(" 1em"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token property"},"min-height"),a("span",{class:"token punctuation"},":"),t(" 1em"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token property"},"background-color"),a("span",{class:"token punctuation"},":"),t(),a("span",{class:"token atrule"},[a("span",{class:"token rule"},"@iconify-bg-color"),a("span",{class:"token punctuation"},";")]),t("\n "),a("span",{class:"token property"},"border-radius"),a("span",{class:"token punctuation"},":"),t(" 100%"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n")])]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n")])])],-1),j=a("h2",{id:"图标选择器"},[a("a",{class:"header-anchor",href:"#图标选择器","aria-hidden":"true"},"#"),t(" 图标选择器")],-1),G=a("h3",{id:"图标集预生成"},[a("a",{class:"header-anchor",href:"#图标集预生成","aria-hidden":"true"},"#"),t(" 图标集预生成")],-1),R=a("p",null,"由于图标选择器这个比较特殊的存在,项目会打包一些比较多的图标,图标选择器的图标需要事先指定并生成相应的文件。",-1),E=a("h3",{id:"生成"},[a("a",{class:"header-anchor",href:"#生成","aria-hidden":"true"},"#"),t(" 生成")],-1),P=a("ul",null,[a("li",null,"执行图标生成命令")],-1),W=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token function"},"yarn"),t(" gen:icon\n")])])],-1),D=a("ul",null,[a("li",null,"这里会让你选择本地还是在线生成,两种方式各有利弊。如下图所示")],-1),F=a("p",null,"local 表示本地,online 表示在线,回车确认",-1),N=a("p",null,[a("img",{src:"/images/genIcon.png",alt:""})],-1),$=a("ul",null,[a("li",null,"选择你要生成的图标集,回车确认")],-1),A=a("p",null,[a("img",{src:"/images/selectIconSet.png",alt:""})],-1),V=a("ul",null,[a("li",null,"选择图标输出的目录(项目默认 src/components/Icon/data),可以直接回车选择默认")],-1),H=a("p",null,[a("img",{src:"/images/outDir.png",alt:""})],-1),L=a("p",null,"到这里图标集已经生成完成了,此时你的图标选择器已经是你所选的的图标集的图标了。",-1),M=a("div",{class:"danger custom-block"},[a("p",{class:"custom-block-title"},"注意不要频繁更新"),a("p",null,"如果前面选择的是本地生成的话,频繁更换图标集,可能会导致图标丢失或者显示不出来")],-1),O=a("h3",{id:"优缺点"},[a("a",{class:"header-anchor",href:"#优缺点","aria-hidden":"true"},"#"),t(" 优缺点")],-1),U=a("ul",null,[a("li",null,[a("strong",null,"在线图标(项目默认,推荐)")])],-1),B=a("p",null,"该方式会在图标选择器使用到图标的时候进行在线请求,然后缓存对应的图标到浏览器。可以有效减少代码打包体积。",-1),J=a("p",null,"如果你的项目可以访问外网,建议可以使用这种方式",-1),K=a("p",null,[a("strong",null,"缺点:"),t(" 在局域网或者无法访问到外网的环境中图标显示不出来")],-1),Q=a("ul",null,[a("li",null,[a("strong",null,"本地图标")])],-1),X=a("p",null,"该方式会在打包的时候将图标选择器的图标全部打包到 js 内。在使用的时候不会额外的请求在线图标",-1),Y=a("p",null,[a("strong",null,"缺点:"),t(" 打包体积会偏大,具体的体积增加得看前面选择图标集的时候选择的图标数量的多少决定")],-1);p.render=function(a,t,o,p,Z,nn){return n(),s("div",null,[c,e,l,u,k,i,r,g,d,f,m,y,v,w,h,S,I,b,T,x,C,q,_,z,j,G,R,E,P,W,D,F,N,$,A,V,H,L,M,O,U,B,J,K,Q,X,Y])};export default p;export{o as __pageData}; diff --git a/assets/dep_icon.md.360e0f2b.lean.js b/assets/dep_icon.md.360e0f2b.lean.js new file mode 100644 index 00000000..9eb1480b --- /dev/null +++ b/assets/dep_icon.md.360e0f2b.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,b as a,d as t}from"./app.8cddb23b.js";const o='{"title":"图标","description":"","frontmatter":{},"headers":[{"level":2,"title":"组件库图标","slug":"组件库图标"},{"level":2,"title":"Svg Sprite 图标","slug":"svg-sprite-图标"},{"level":3,"title":"使用","slug":"使用"},{"level":2,"title":"Iconify 图标","slug":"iconify-图标"},{"level":2,"title":"图标选择器","slug":"图标选择器"},{"level":3,"title":"图标集预生成","slug":"图标集预生成"},{"level":3,"title":"生成","slug":"生成"},{"level":3,"title":"优缺点","slug":"优缺点"}],"relativePath":"dep/icon.md","lastUpdated":1697592578593}',p={},c=a("h1",{id:"图标"},[a("a",{class:"header-anchor",href:"#图标","aria-hidden":"true"},"#"),t(" 图标")],-1),e=a("p",null,"项目中有以下多种图标使用方式。",-1),l=a("h2",{id:"组件库图标"},[a("a",{class:"header-anchor",href:"#组件库图标","aria-hidden":"true"},"#"),t(" 组件库图标")],-1),u=a("p",null,[t("使用 "),a("code",null,"ant-design-vue"),t(" 提供的图标")],-1),k=a("div",{class:"language-vue"},[a("pre",null,[a("code",null,[a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("template")]),a("span",{class:"token punctuation"},">")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("StarOutlined")]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("StarFilled")]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("StarTwoTone")]),t(),a("span",{class:"token attr-name"},"twoToneColor"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("#eb2f96"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("script")]),a("span",{class:"token punctuation"},">")]),a("span",{class:"token script"},[a("span",{class:"token language-javascript"},[t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" defineComponent "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" StarOutlined"),a("span",{class:"token punctuation"},","),t(" StarFilled"),a("span",{class:"token punctuation"},","),t(" StarTwoTone "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'@ant-design/icons-vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token function"},"defineComponent"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"{"),t("\n components"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t(" StarOutlined"),a("span",{class:"token punctuation"},","),t(" StarFilled"),a("span",{class:"token punctuation"},","),t(" StarTwoTone "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n")])])],-1),i=a("h2",{id:"svg-sprite-图标"},[a("a",{class:"header-anchor",href:"#svg-sprite-图标","aria-hidden":"true"},"#"),t(" Svg Sprite 图标")],-1),r=a("h3",{id:"使用"},[a("a",{class:"header-anchor",href:"#使用","aria-hidden":"true"},"#"),t(" 使用")],-1),g=a("p",null,[t("将需要的 svg 图标放到"),a("code",null,"src/assets/icons"),t("内")],-1),d=a("p",null,"例: test.svg",-1),f=a("ol",null,[a("li",null,[t("使用"),a("code",null,"SvgIcon"),t("组件进行展示")])],-1),m=a("div",{class:"language-vue"},[a("pre",null,[a("code",null,[a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("template")]),a("span",{class:"token punctuation"},">")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("SvgIcon")]),t(),a("span",{class:"token attr-name"},"name"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("test"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("script")]),a("span",{class:"token punctuation"},">")]),a("span",{class:"token script"},[a("span",{class:"token language-javascript"},[t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" defineComponent "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" SvgIcon "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/components/Icon'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token function"},"defineComponent"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"{"),t("\n components"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t(" SvgIcon "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n")])])],-1),y=a("ol",{start:"2"},[a("li",null,[t("使用"),a("code",null,"Icon"),t("组件进行展示")])],-1),v=a("p",null,[t("以 "),a("code",null,"|svg"),t(" 结尾会自动使用"),a("code",null,"SvgIcon"),t("组件")],-1),w=a("div",{class:"language-vue"},[a("pre",null,[a("code",null,[a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("template")]),a("span",{class:"token punctuation"},">")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("Icon")]),t(),a("span",{class:"token attr-name"},"name"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("test|svg"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("script")]),a("span",{class:"token punctuation"},">")]),a("span",{class:"token script"},[a("span",{class:"token language-javascript"},[t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" defineComponent "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" Icon "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/components/Icon'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token function"},"defineComponent"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"{"),t("\n components"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t(" Icon "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n")])])],-1),h=a("h2",{id:"iconify-图标"},[a("a",{class:"header-anchor",href:"#iconify-图标","aria-hidden":"true"},"#"),t(" Iconify 图标")],-1),S=a("p",null,[t("使用方式请参考 "),a("a",{href:"./../components/icon.html"},"Icon 组件")],-1),I=a("p",null,[t("项目中使用到的是 "),a("a",{href:"https://github.com/antfu/purge-icons/blob/main/packages/vite-plugin-purge-icons/README.md",target:"_blank",rel:"noopener noreferrer"},"vite-plugin-purge-icons"),t(" 这个插件来进行图标实现。")],-1),b=a("ol",null,[a("li",null,"安装依赖")],-1),T=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("\n"),a("span",{class:"token function"},"yarn"),t(),a("span",{class:"token function"},"add"),t(" @iconify/iconify\n\n"),a("span",{class:"token function"},"yarn"),t(),a("span",{class:"token function"},"add"),t(" @iconify/json @purge-icons/generated -D\n\n")])])],-1),x=a("ol",{start:"2"},[a("li",null,[t("在 "),a("code",null,"vite.config.ts"),t("内引入插件")])],-1),C=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"import"),t(" PurgeIcons "),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vite-plugin-purge-icons'"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token punctuation"},"{"),t("\n plugins"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"["),a("span",{class:"token function"},"PurgeIcons"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},"]"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),q=a("ol",{start:"3"},[a("li",null,"编写 Icon 组件")],-1),_=a("p",null,[t("完整代码 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/blob/main/src/components/Icon/src/Icon.vue",target:"_blank",rel:"noopener noreferrer"},"src/components/Icon/src/Icon.vue")],-1),z=a("div",{class:"language-vue"},[a("pre",null,[a("code",null,[a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("template")]),a("span",{class:"token punctuation"},">")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("SvgIcon")]),t(),a("span",{class:"token attr-name"},":size"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("size"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token attr-name"},":name"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("getSvgIcon"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token attr-name"},"v-if"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("isSvgIcon"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token attr-name"},":class"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("[$attrs.class]"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token attr-name"},":spin"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("spin"),a("span",{class:"token punctuation"},'"')]),t(),a("span",{class:"token punctuation"},"/>")]),t("\n "),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("span")]),t("\n "),a("span",{class:"token attr-name"},"v-else"),t("\n "),a("span",{class:"token attr-name"},"ref"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("elRef"),a("span",{class:"token punctuation"},'"')]),t("\n "),a("span",{class:"token attr-name"},":class"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("[$attrs.class, "),a("span",{class:"token punctuation"},"'"),t("app-iconify anticon"),a("span",{class:"token punctuation"},"'"),t(", spin && "),a("span",{class:"token punctuation"},"'"),t("app-iconify-spin"),a("span",{class:"token punctuation"},"'"),t("]"),a("span",{class:"token punctuation"},'"')]),t("\n "),a("span",{class:"token attr-name"},":style"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("getWrapStyle"),a("span",{class:"token punctuation"},'"')]),t("\n "),a("span",{class:"token punctuation"},">")]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("script")]),t(),a("span",{class:"token attr-name"},"lang"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("ts"),a("span",{class:"token punctuation"},'"')]),a("span",{class:"token punctuation"},">")]),a("span",{class:"token script"},[a("span",{class:"token language-javascript"},[t("\n "),a("span",{class:"token keyword"},"import"),t(" type "),a("span",{class:"token punctuation"},"{"),t(" PropType "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t("\n defineComponent"),a("span",{class:"token punctuation"},","),t("\n ref"),a("span",{class:"token punctuation"},","),t("\n watch"),a("span",{class:"token punctuation"},","),t("\n onMounted"),a("span",{class:"token punctuation"},","),t("\n nextTick"),a("span",{class:"token punctuation"},","),t("\n unref"),a("span",{class:"token punctuation"},","),t("\n computed"),a("span",{class:"token punctuation"},","),t("\n CSSProperties"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue'"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"import"),t(" SvgIcon "),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'./SvgIcon.vue'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(" Iconify "),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'@purge-icons/generated'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" isString "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/utils/is'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" propTypes "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'/@/utils/propTypes'"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token constant"},"SVG_END_WITH_FLAG"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token string"},"'|svg'"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"export"),t(),a("span",{class:"token keyword"},"default"),t(),a("span",{class:"token function"},"defineComponent"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"{"),t("\n name"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'GIcon'"),a("span",{class:"token punctuation"},","),t("\n components"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t(" SvgIcon "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n props"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"// icon name"),t("\n icon"),a("span",{class:"token operator"},":"),t(" propTypes"),a("span",{class:"token punctuation"},"."),t("string"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// icon color"),t("\n color"),a("span",{class:"token operator"},":"),t(" propTypes"),a("span",{class:"token punctuation"},"."),t("string"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// icon size"),t("\n size"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"{"),t("\n type"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token punctuation"},"["),t("String"),a("span",{class:"token punctuation"},","),t(" Number"),a("span",{class:"token punctuation"},"]"),t(),a("span",{class:"token keyword"},"as"),t(" PropType"),a("span",{class:"token operator"},"<"),t("string "),a("span",{class:"token operator"},"|"),t(" number"),a("span",{class:"token operator"},">"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token keyword"},"default"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token number"},"16"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n spin"),a("span",{class:"token operator"},":"),t(" propTypes"),a("span",{class:"token punctuation"},"."),t("bool"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"def"),a("span",{class:"token punctuation"},"("),a("span",{class:"token boolean"},"false"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},","),t("\n prefix"),a("span",{class:"token operator"},":"),t(" propTypes"),a("span",{class:"token punctuation"},"."),t("string"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"def"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"''"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token function"},"setup"),a("span",{class:"token punctuation"},"("),a("span",{class:"token parameter"},"props"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(" elRef "),a("span",{class:"token operator"},"="),t(" ref"),a("span",{class:"token operator"},"<"),t("ElRef"),a("span",{class:"token operator"},">"),a("span",{class:"token punctuation"},"("),a("span",{class:"token keyword"},"null"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(" isSvgIcon "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"computed"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token operator"},"=>"),t(" props"),a("span",{class:"token punctuation"},"."),t("icon"),a("span",{class:"token operator"},"?."),a("span",{class:"token function"},"endsWith"),a("span",{class:"token punctuation"},"("),a("span",{class:"token constant"},"SVG_END_WITH_FLAG"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"const"),t(" getSvgIcon "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"computed"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token operator"},"=>"),t(" props"),a("span",{class:"token punctuation"},"."),t("icon"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"replace"),a("span",{class:"token punctuation"},"("),a("span",{class:"token constant"},"SVG_END_WITH_FLAG"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token string"},"''"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"const"),t(" getIconRef "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"computed"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token operator"},"=>"),t(),a("span",{class:"token template-string"},[a("span",{class:"token template-punctuation string"},"`"),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("props"),a("span",{class:"token punctuation"},"."),t("prefix "),a("span",{class:"token operator"},"?"),t(" props"),a("span",{class:"token punctuation"},"."),t("prefix "),a("span",{class:"token operator"},"+"),t(),a("span",{class:"token string"},"':'"),t(),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"''"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("props"),a("span",{class:"token punctuation"},"."),t("icon"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token template-punctuation string"},"`")]),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token function-variable function"},"update"),t(),a("span",{class:"token operator"},"="),t(),a("span",{class:"token keyword"},"async"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token operator"},"=>"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token function"},"unref"),a("span",{class:"token punctuation"},"("),t("isSvgIcon"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"return"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(" el "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"unref"),a("span",{class:"token punctuation"},"("),t("elRef"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token operator"},"!"),t("el"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"return"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"await"),t(),a("span",{class:"token function"},"nextTick"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"const"),t(" icon "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"unref"),a("span",{class:"token punctuation"},"("),t("getIconRef"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token operator"},"!"),t("icon"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token keyword"},"return"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(" svg "),a("span",{class:"token operator"},"="),t(" Iconify"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"renderSVG"),a("span",{class:"token punctuation"},"("),t("icon"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token punctuation"},"{"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),t("svg"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n el"),a("span",{class:"token punctuation"},"."),t("textContent "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token string"},"''"),a("span",{class:"token punctuation"},";"),t("\n el"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"appendChild"),a("span",{class:"token punctuation"},"("),t("svg"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"else"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(" span "),a("span",{class:"token operator"},"="),t(" document"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"createElement"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'span'"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n span"),a("span",{class:"token punctuation"},"."),t("className "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token string"},"'iconify'"),a("span",{class:"token punctuation"},";"),t("\n span"),a("span",{class:"token punctuation"},"."),t("dataset"),a("span",{class:"token punctuation"},"."),t("icon "),a("span",{class:"token operator"},"="),t(" icon"),a("span",{class:"token punctuation"},";"),t("\n el"),a("span",{class:"token punctuation"},"."),t("textContent "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token string"},"''"),a("span",{class:"token punctuation"},";"),t("\n el"),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"appendChild"),a("span",{class:"token punctuation"},"("),t("span"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"const"),t(" getWrapStyle "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"computed"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token parameter"},"CSSProperties"),t(),a("span",{class:"token operator"},"=>"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token keyword"},"const"),t(),a("span",{class:"token punctuation"},"{"),t(" size"),a("span",{class:"token punctuation"},","),t(" color "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token operator"},"="),t(" props"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"let"),t(" fs "),a("span",{class:"token operator"},"="),t(" size"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token function"},"isString"),a("span",{class:"token punctuation"},"("),t("size"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n fs "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token function"},"parseInt"),a("span",{class:"token punctuation"},"("),t("size"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token number"},"10"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n\n "),a("span",{class:"token keyword"},"return"),t(),a("span",{class:"token punctuation"},"{"),t("\n fontSize"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token template-string"},[a("span",{class:"token template-punctuation string"},"`"),a("span",{class:"token interpolation"},[a("span",{class:"token interpolation-punctuation punctuation"},"${"),t("fs"),a("span",{class:"token interpolation-punctuation punctuation"},"}")]),a("span",{class:"token string"},"px"),a("span",{class:"token template-punctuation string"},"`")]),a("span",{class:"token punctuation"},","),t("\n color"),a("span",{class:"token operator"},":"),t(" color"),a("span",{class:"token punctuation"},","),t("\n display"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'inline-flex'"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token function"},"watch"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token operator"},"=>"),t(" props"),a("span",{class:"token punctuation"},"."),t("icon"),a("span",{class:"token punctuation"},","),t(" update"),a("span",{class:"token punctuation"},","),t(),a("span",{class:"token punctuation"},"{"),t(" flush"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token string"},"'post'"),t(),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token function"},"onMounted"),a("span",{class:"token punctuation"},"("),t("update"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token keyword"},"return"),t(),a("span",{class:"token punctuation"},"{"),t(" elRef"),a("span",{class:"token punctuation"},","),t(" getWrapStyle"),a("span",{class:"token punctuation"},","),t(" isSvgIcon"),a("span",{class:"token punctuation"},","),t(" getSvgIcon "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n"),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"<"),t("style")]),t(),a("span",{class:"token attr-name"},"lang"),a("span",{class:"token attr-value"},[a("span",{class:"token punctuation attr-equals"},"="),a("span",{class:"token punctuation"},'"'),t("less"),a("span",{class:"token punctuation"},'"')]),a("span",{class:"token punctuation"},">")]),a("span",{class:"token style"},[a("span",{class:"token language-css"},[t("\n "),a("span",{class:"token selector"},".app-iconify"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token property"},"display"),a("span",{class:"token punctuation"},":"),t(" inline-block"),a("span",{class:"token punctuation"},";"),t("\n // "),a("span",{class:"token property"},"vertical-align"),a("span",{class:"token punctuation"},":"),t(" middle"),a("span",{class:"token selector"},";\n\n &-spin"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token selector"},"svg"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token property"},"animation"),a("span",{class:"token punctuation"},":"),t(" loadingCircle 1s infinite linear"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n\n "),a("span",{class:"token selector"},"span.iconify"),t(),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token property"},"display"),a("span",{class:"token punctuation"},":"),t(" block"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token property"},"min-width"),a("span",{class:"token punctuation"},":"),t(" 1em"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token property"},"min-height"),a("span",{class:"token punctuation"},":"),t(" 1em"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token property"},"background-color"),a("span",{class:"token punctuation"},":"),t(),a("span",{class:"token atrule"},[a("span",{class:"token rule"},"@iconify-bg-color"),a("span",{class:"token punctuation"},";")]),t("\n "),a("span",{class:"token property"},"border-radius"),a("span",{class:"token punctuation"},":"),t(" 100%"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n")])]),a("span",{class:"token tag"},[a("span",{class:"token tag"},[a("span",{class:"token punctuation"},"")]),t("\n")])])],-1),j=a("h2",{id:"图标选择器"},[a("a",{class:"header-anchor",href:"#图标选择器","aria-hidden":"true"},"#"),t(" 图标选择器")],-1),G=a("h3",{id:"图标集预生成"},[a("a",{class:"header-anchor",href:"#图标集预生成","aria-hidden":"true"},"#"),t(" 图标集预生成")],-1),R=a("p",null,"由于图标选择器这个比较特殊的存在,项目会打包一些比较多的图标,图标选择器的图标需要事先指定并生成相应的文件。",-1),E=a("h3",{id:"生成"},[a("a",{class:"header-anchor",href:"#生成","aria-hidden":"true"},"#"),t(" 生成")],-1),P=a("ul",null,[a("li",null,"执行图标生成命令")],-1),W=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token function"},"yarn"),t(" gen:icon\n")])])],-1),D=a("ul",null,[a("li",null,"这里会让你选择本地还是在线生成,两种方式各有利弊。如下图所示")],-1),F=a("p",null,"local 表示本地,online 表示在线,回车确认",-1),N=a("p",null,[a("img",{src:"/images/genIcon.png",alt:""})],-1),$=a("ul",null,[a("li",null,"选择你要生成的图标集,回车确认")],-1),A=a("p",null,[a("img",{src:"/images/selectIconSet.png",alt:""})],-1),V=a("ul",null,[a("li",null,"选择图标输出的目录(项目默认 src/components/Icon/data),可以直接回车选择默认")],-1),H=a("p",null,[a("img",{src:"/images/outDir.png",alt:""})],-1),L=a("p",null,"到这里图标集已经生成完成了,此时你的图标选择器已经是你所选的的图标集的图标了。",-1),M=a("div",{class:"danger custom-block"},[a("p",{class:"custom-block-title"},"注意不要频繁更新"),a("p",null,"如果前面选择的是本地生成的话,频繁更换图标集,可能会导致图标丢失或者显示不出来")],-1),O=a("h3",{id:"优缺点"},[a("a",{class:"header-anchor",href:"#优缺点","aria-hidden":"true"},"#"),t(" 优缺点")],-1),U=a("ul",null,[a("li",null,[a("strong",null,"在线图标(项目默认,推荐)")])],-1),B=a("p",null,"该方式会在图标选择器使用到图标的时候进行在线请求,然后缓存对应的图标到浏览器。可以有效减少代码打包体积。",-1),J=a("p",null,"如果你的项目可以访问外网,建议可以使用这种方式",-1),K=a("p",null,[a("strong",null,"缺点:"),t(" 在局域网或者无法访问到外网的环境中图标显示不出来")],-1),Q=a("ul",null,[a("li",null,[a("strong",null,"本地图标")])],-1),X=a("p",null,"该方式会在打包的时候将图标选择器的图标全部打包到 js 内。在使用的时候不会额外的请求在线图标",-1),Y=a("p",null,[a("strong",null,"缺点:"),t(" 打包体积会偏大,具体的体积增加得看前面选择图标集的时候选择的图标数量的多少决定")],-1);p.render=function(a,t,o,p,Z,nn){return n(),s("div",null,[c,e,l,u,k,i,r,g,d,f,m,y,v,w,h,S,I,b,T,x,C,q,_,z,j,G,R,E,P,W,D,F,N,$,A,V,H,L,M,O,U,B,J,K,Q,X,Y])};export default p;export{o as __pageData}; diff --git a/assets/dep_lint.md.ebbe5471.js b/assets/dep_lint.md.ebbe5471.js new file mode 100644 index 00000000..074090ea --- /dev/null +++ b/assets/dep_lint.md.ebbe5471.js @@ -0,0 +1 @@ +import{o as e,c as s,a as n}from"./app.8cddb23b.js";const a='{"title":"Lint","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"ESLint","slug":"eslint"},{"level":3,"title":"手动校验代码","slug":"手动校验代码"},{"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":"编辑器配合-1"},{"level":2,"title":"Prettier","slug":"prettier"},{"level":3,"title":"配置","slug":"配置-2"},{"level":3,"title":"编辑器配合","slug":"编辑器配合-2"},{"level":2,"title":"Git Hook","slug":"git-hook"},{"level":3,"title":"husky","slug":"husky"},{"level":3,"title":"如何关闭","slug":"如何关闭-1"},{"level":3,"title":"如何跳过某一个检查","slug":"如何跳过某一个检查"},{"level":3,"title":"lint-staged","slug":"lint-staged"}],"relativePath":"dep/lint.md","lastUpdated":1697592578593}',t={},i=n('

Lint

介绍

使用 lint 的好处

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

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

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

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

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

WARNING

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

ESLint

ESLint 是一个代码规范和错误检查工具,有以下几个特性

  • 所有东西都是可以插拔的。你可以调用任意的 rule api 或者 formatter api 去打包或者定义 rule or formatter。
  • 任意的 rule 都是独立的
  • 没有特定的 coding style,你可以自己配置

手动校验代码

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

配置项

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

编辑器配合

推荐使用 vscode 进行开发,vscode 自带 eslint 插件,可以自动修改一些错误。

同时项目内也自带了 vscode eslint 配置,具体在 .vscode/setting.json 文件夹内部。只要使用 vscode 开发不用任何设置即可使用

CommitLint

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

配置

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

Git 提交规范

  • 参考 vue 规范 (Angular)

    • feat 增加新功能
    • fix 修复问题/BUG
    • style 代码风格相关无影响运行结果的
    • perf 优化/性能提升
    • refactor 重构
    • revert 撤销修改
    • test 测试相关
    • docs 文档/注释
    • chore 依赖更新/脚手架配置修改等
    • workflow 工作流改进
    • ci 持续集成
    • mod 不确定分类的修改
    • wip 开发中
    • types 类型修改

如何关闭

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

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

示例

\ngit commit -m 'feat(home): add home page'\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

如何关闭

# 删除husky依赖即可\nyarn remove huksy\n\n

如何跳过某一个检查

# 加上 --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
',57);t.render=function(n,a,t,l,o,r){return e(),s("div",null,[i])};export default t;export{a as __pageData}; diff --git a/assets/dep_lint.md.ebbe5471.lean.js b/assets/dep_lint.md.ebbe5471.lean.js new file mode 100644 index 00000000..2f7a0b42 --- /dev/null +++ b/assets/dep_lint.md.ebbe5471.lean.js @@ -0,0 +1 @@ +import{o as e,c as s,a as n}from"./app.8cddb23b.js";const a='{"title":"Lint","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"ESLint","slug":"eslint"},{"level":3,"title":"手动校验代码","slug":"手动校验代码"},{"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":"编辑器配合-1"},{"level":2,"title":"Prettier","slug":"prettier"},{"level":3,"title":"配置","slug":"配置-2"},{"level":3,"title":"编辑器配合","slug":"编辑器配合-2"},{"level":2,"title":"Git Hook","slug":"git-hook"},{"level":3,"title":"husky","slug":"husky"},{"level":3,"title":"如何关闭","slug":"如何关闭-1"},{"level":3,"title":"如何跳过某一个检查","slug":"如何跳过某一个检查"},{"level":3,"title":"lint-staged","slug":"lint-staged"}],"relativePath":"dep/lint.md","lastUpdated":1697592578593}',t={},i=n('',57);t.render=function(n,a,t,l,o,r){return e(),s("div",null,[i])};export default t;export{a as __pageData}; diff --git a/assets/guide_auth.md.912002be.js b/assets/guide_auth.md.912002be.js new file mode 100644 index 00000000..c8189193 --- /dev/null +++ b/assets/guide_auth.md.912002be.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.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":3,"title":"实现","slug":"实现-1"},{"level":3,"title":"动态更换菜单","slug":"动态更换菜单"},{"level":3,"title":"细粒度权限","slug":"细粒度权限-1"},{"level":3,"title":"如何初始化 code","slug":"如何初始化-code"}],"relativePath":"guide/auth.md","lastUpdated":1697592578593}',p={},o=a('

权限

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

  1. 通过用户角色来过滤菜单(前端方式控制),菜单和路由分开配置
  2. 通过用户角色来过滤菜单(前端方式控制),菜单由路由配置自动生成
  3. 通过后台来动态生成路由表(后台方式控制)

前端角色权限

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

缺点: 权限相对不自由,如果后台改动角色,前台也需要跟着改动。适合角色较固定的系统

实现

  1. 项目配置将系统内权限模式改为 ROLE 模式
// ! 改动后需要清空浏览器缓存\nconst setting: ProjectConfig = {\n  // 权限模式\n  permissionMode: PermissionModeEnum.ROLE,\n};\n
  1. 在路由表配置路由所需的权限,如果不配置,默认可见(见注释)
import type { AppRouteModule } from '/@/router/types';\n\nimport { getParentLayout, LAYOUT } from '/@/router/constant';\nimport { RoleEnum } from '/@/enums/roleEnum';\nimport { t } from '/@/hooks/web/useI18n';\n\nconst permission: AppRouteModule = {\n  path: '/permission',\n  name: 'Permission',\n  component: LAYOUT,\n  redirect: '/permission/front/page',\n  meta: {\n    icon: 'ion:key-outline',\n    title: t('routes.demo.permission.permission'),\n  },\n\n  children: [\n    {\n      path: 'front',\n      name: 'PermissionFrontDemo',\n      component: getParentLayout('PermissionFrontDemo'),\n      meta: {\n        title: t('routes.demo.permission.front'),\n      },\n      children: [\n        {\n          path: 'auth-pageA',\n          name: 'FrontAuthPageA',\n          component: () => import('/@/views/demo/permission/front/AuthPageA.vue'),\n          meta: {\n            title: t('routes.demo.permission.frontTestA'),\n            roles: [RoleEnum.SUPER],\n          },\n        },\n        {\n          path: 'auth-pageB',\n          name: 'FrontAuthPageB',\n          component: () => import('/@/views/demo/permission/front/AuthPageB.vue'),\n          meta: {\n            title: t('routes.demo.permission.frontTestB'),\n            roles: [RoleEnum.TEST],\n          },\n        },\n      ],\n    },\n  ],\n};\n\nexport default permission;\n
  1. 在路由钩子内动态判断

详细代码见 src/router/guard/permissionGuard.ts

// 这里只列举了主要代码\nconst routes = await permissionStore.buildRoutesAction();\n\nroutes.forEach((route) => {\n  router.addRoute(route as unknown as RouteRecordRaw);\n});\n\nconst redirectPath = (from.query.redirect || to.path) as string;\nconst redirect = decodeURIComponent(redirectPath);\nconst nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };\npermissionStore.setDynamicAddedRoute(true);\nnext(nextData);\n

permissionStore.buildRoutesAction 用于过滤动态路由,详细代码见 src/store/modules/permission.ts

// 主要代码\nif (permissionMode === PermissionModeEnum.ROLE) {\n  const routeFilter = (route: AppRouteRecordRaw) => {\n    const { meta } = route;\n    const { roles } = meta || {};\n    if (!roles) return true;\n    return roleList.some((role) => roles.includes(role));\n  };\n  routes = filter(asyncRoutes, routeFilter);\n  routes = routes.filter(routeFilter);\n  // Convert multi-level routing to level 2 routing\n  routes = flatMultiLevelRoutes(routes);\n}\n

动态更换角色

系统提供 usePermission 方便角色相关操作

import { usePermission } from '/@/hooks/web/usePermission';\nimport { RoleEnum } from '/@/enums/roleEnum';\n\nexport default defineComponent({\n  setup() {\n    const { changeRole } = usePermission();\n    // 更换为test角色\n    // 动态更改角色,传入角色名称,可以是数组\n    changeRole(RoleEnum.TEST);\n    return {};\n  },\n});\n

细粒度权限

函数方式

usePermission 还提供了按钮级别的权限控制。

<template>\n  <a-button v-if="hasPermission([RoleEnum.TEST, RoleEnum.SUPER])" color="error" class="mx-4">\n    拥有[test,super]角色权限可见\n  </a-button>\n</template>\n<script lang="ts">\n  import { usePermission } from '/@/hooks/web/usePermission';\n  import { RoleEnum } from '/@/enums/roleEnum';\n\n  export default defineComponent({\n    setup() {\n      const { hasPermission } = usePermission();\n\n      return { hasPermission };\n    },\n  });\n</script>\n

组件方式

具体查看权限组件使用

指令方式

TIP

指令方式不能动态更改权限

<a-button v-auth="RoleEnum.SUPER" type="primary" class="mx-4"> 拥有super角色权限可见</a-button>\n

后台动态获取

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

实现

  1. 项目配置将系统内权限模式改为 BACK 模式
// ! 改动后需要清空浏览器缓存\nconst setting: ProjectConfig = {\n  // 权限模式\n  permissionMode: PermissionModeEnum.BACK,\n};\n
  1. 路由拦截,与角色权限模式一致

permissionStore.buildRoutesAction 用于过滤动态路由,详细代码见 /@/store/modules/permission.ts

// 主要代码\nif (permissionMode === PermissionModeEnum.BACK) {\n  const { createMessage } = useMessage();\n\n  createMessage.loading({\n    content: t('sys.app.menuLoading'),\n    duration: 1,\n  });\n\n  // !Simulate to obtain permission codes from the background,\n  // this function may only need to be executed once, and the actual project can be put at the right time by itself\n  let routeList: AppRouteRecordRaw[] = [];\n  try {\n    this.changePermissionCode();\n    routeList = (await getMenuList()) as AppRouteRecordRaw[];\n  } catch (error) {\n    console.error(error);\n  }\n\n  // Dynamically introduce components\n  routeList = transformObjToRoute(routeList);\n\n  //  Background routing to menu structure\n  const backMenuList = transformRouteToMenu(routeList);\n  this.setBackMenuList(backMenuList);\n\n  routeList = flatMultiLevelRoutes(routeList);\n  routes = [PAGE_NOT_FOUND_ROUTE, ...routeList];\n}\n

getMenuList 返回值格式

返回值由多个路由模块组成

注意

后端接口返回的数据中必须包含PageEnum.BASE_HOME指定的路由(path定义于src/enums/pageEnum.ts

[\n  {\n    path: '/dashboard',\n    name: 'Dashboard',\n    component: '/dashboard/welcome/index',\n    meta: {\n      title: 'routes.dashboard.welcome',\n      affix: true,\n      icon: 'ant-design:home-outlined',\n    },\n  },\n  {\n    path: '/permission',\n    name: 'Permission',\n    component: 'LAYOUT',\n    redirect: '/permission/front/page',\n    meta: {\n      icon: 'carbon:user-role',\n      title: 'routes.demo.permission.permission',\n    },\n    children: [\n      {\n        path: 'back',\n        name: 'PermissionBackDemo',\n        meta: {\n          title: 'routes.demo.permission.back',\n        },\n\n        children: [\n          {\n            path: 'page',\n            name: 'BackAuthPage',\n            component: '/demo/permission/back/index',\n            meta: {\n              title: 'routes.demo.permission.backPage',\n            },\n          },\n          {\n            path: 'btn',\n            name: 'BackAuthBtn',\n            component: '/demo/permission/back/Btn',\n            meta: {\n              title: 'routes.demo.permission.backBtn',\n            },\n          },\n        ],\n      },\n    ],\n  },\n];\n

动态更换菜单

系统提供 usePermission 方便角色相关操作

import { usePermission } from '/@/hooks/web/usePermission';\nimport { RoleEnum } from '/@/enums/roleEnum';\n\nexport default defineComponent({\n  setup() {\n    const { changeMenu } = usePermission();\n\n    // 更改菜单的实现需要自行去修改\n    changeMenu();\n    return {};\n  },\n});\n

细粒度权限

函数方式

usePermission 还提供了按钮级别的权限控制。

<template>\n  <a-button v-if="hasPermission(['20000', '2000010'])" color="error" class="mx-4">\n    拥有[20000,2000010]code可见\n  </a-button>\n</template>\n<script lang="ts">\n  import { usePermission } from '/@/hooks/web/usePermission';\n  import { RoleEnum } from '/@/enums/roleEnum';\n\n  export default defineComponent({\n    setup() {\n      const { hasPermission } = usePermission();\n      return { hasPermission };\n    },\n  });\n</script>\n

组件方式

具体查看权限组件使用

指令方式

TIP

指令方式不能动态更改权限

<a-button v-auth="'1000'" type="primary" class="mx-4"> 拥有code ['1000']权限可见 </a-button>\n

如何初始化 code

通常,如需做按钮级别权限,后台会提供相应的 code,或者类型的判断标识。这些编码只需要在登录后获取一次即可。

import { getPermCodeByUserId } from '/@/api/sys/user';\nimport { permissionStore } from '/@/store/modules/permission';\nasync function changePermissionCode(userId: string) {\n  // 从后台获取当前用户拥有的编码\n  const codeList = await getPermCodeByUserId({ userId });\n  permissionStore.commitPermCodeListState(codeList);\n}\n
',55);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.912002be.lean.js b/assets/guide_auth.md.912002be.lean.js new file mode 100644 index 00000000..6c0d6dcd --- /dev/null +++ b/assets/guide_auth.md.912002be.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.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":3,"title":"实现","slug":"实现-1"},{"level":3,"title":"动态更换菜单","slug":"动态更换菜单"},{"level":3,"title":"细粒度权限","slug":"细粒度权限-1"},{"level":3,"title":"如何初始化 code","slug":"如何初始化-code"}],"relativePath":"guide/auth.md","lastUpdated":1697592578593}',p={},o=a('',55);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.ad92b1a7.js b/assets/guide_component.md.ad92b1a7.js new file mode 100644 index 00000000..d0b2d3d2 --- /dev/null +++ b/assets/guide_component.md.ad92b1a7.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"组件注册","description":"","frontmatter":{},"headers":[{"level":2,"title":"按需引入","slug":"按需引入"},{"level":3,"title":"tsx 文件注册","slug":"tsx-文件注册"},{"level":2,"title":"全局注册","slug":"全局注册"},{"level":3,"title":"全局按需注册","slug":"全局按需注册"},{"level":3,"title":"全量注册","slug":"全量注册"}],"relativePath":"guide/component.md","lastUpdated":1697592578593}',p={},o=a('

组件注册

按需引入

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

<template>\n  <Menu>\n    <SubMenu></SubMenu>\n  <Menu>\n\n  <menu>\n    <sub-menu></sub-menu>\n  <menu>\n</template>\n<script>\nimport { Menu } from 'ant-design-vue';\nexport default defineComponent({\n  components: {\n    Menu: Menu,\n    SubMenu: Menu.SubMenu\n  },\n})\n</script>\n

tsx 文件注册

tsx 文件内不能使用全局注册组件

import { Menu } from 'ant-design-vue';\n\nexport default defineComponent({\n  setup() {\n    return () => (\n      <Menu>\n        <Menu.SubMenu></Menu.SubMenu>\n      </Menu>\n    );\n  },\n});\n

全局注册

如果不习惯按需引入方式,可以进行全局注册。全局注册也分两种方式

全局按需注册

只注册需要的组件

代码地址:src/components/registerGlobComp.ts

import {\n  // Need\n  Button as AntButton,\n  Optional,\n  Select,\n  Alert,\n  Checkbox,\n  DatePicker,\n  Radio,\n  Switch,\n  Card,\n  List,\n  Tabs,\n  Descriptions,\n  Tree,\n  Table,\n  Divider,\n  Modal,\n  Drawer,\n  Dropdown,\n  Tag,\n  Tooltip,\n  Badge,\n  Popover,\n  Upload,\n  Transfer,\n  Steps,\n  PageHeader,\n  Result,\n  Empty,\n  Avatar,\n  Menu,\n  Breadcrumb,\n  Form,\n  Input,\n  Row,\n  Col,\n  Spin,\n} from 'ant-design-vue';\n\nexport function registerGlobComp(app: App) {\n  app\n    .use(Select)\n    .use(Alert)\n    .use(Breadcrumb)\n    .use(Checkbox)\n    .use(DatePicker)\n    .use(Radio)\n    .use(Switch)\n    .use(Card)\n    .use(List)\n    .use(Descriptions)\n    .use(Tree)\n    .use(Table)\n    .use(Divider)\n    .use(Modal)\n    .use(Drawer)\n    .use(Dropdown)\n    .use(Tag)\n    .use(Tooltip)\n    .use(Badge)\n    .use(Popover)\n    .use(Upload)\n    .use(Transfer)\n    .use(Steps)\n    .use(PageHeader)\n    .use(Result)\n    .use(Empty)\n    .use(Avatar)\n    .use(Menu)\n    .use(Tabs)\n    .use(Form)\n    .use(Input)\n    .use(Row)\n    .use(Col)\n    .use(Spin);\n}\n

全量注册

  • main.ts
import { createApp } from 'vue';\nimport Antd from 'ant-design-vue';\nimport 'ant-design-vue/dist/antd.less';\nconst app = createApp(App);\napp.use(Antd);\n
  • 删除以下代码
if (import.meta.env.DEV) {\n  import('ant-design-vue/dist/antd.less');\n}\n
',18);p.render=function(a,t,p,c,e,u){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/guide_component.md.ad92b1a7.lean.js b/assets/guide_component.md.ad92b1a7.lean.js new file mode 100644 index 00000000..4b4d5558 --- /dev/null +++ b/assets/guide_component.md.ad92b1a7.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"组件注册","description":"","frontmatter":{},"headers":[{"level":2,"title":"按需引入","slug":"按需引入"},{"level":3,"title":"tsx 文件注册","slug":"tsx-文件注册"},{"level":2,"title":"全局注册","slug":"全局注册"},{"level":3,"title":"全局按需注册","slug":"全局按需注册"},{"level":3,"title":"全量注册","slug":"全量注册"}],"relativePath":"guide/component.md","lastUpdated":1697592578593}',p={},o=a('',18);p.render=function(a,t,p,c,e,u){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/guide_deploy.md.d904a197.js b/assets/guide_deploy.md.d904a197.js new file mode 100644 index 00000000..d0a43511 --- /dev/null +++ b/assets/guide_deploy.md.d904a197.js @@ -0,0 +1 @@ +import{o as n,c as s,b as a,d as t}from"./app.8cddb23b.js";const e='{"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":3,"title":"开启 gzip 压缩","slug":"开启-gzip-压缩"},{"level":3,"title":"开启 brotli 压缩","slug":"开启-brotli-压缩"},{"level":3,"title":"同时开启 gzip 与 brotli","slug":"同时开启-gzip-与-brotli"},{"level":3,"title":"gzip 与 brotli 在 nginx 内的配置","slug":"gzip-与-brotli-在-nginx-内的配置"},{"level":2,"title":"部署","slug":"部署"},{"level":3,"title":"发布","slug":"发布"},{"level":3,"title":"前端路由与服务端的结合","slug":"前端路由与服务端的结合"},{"level":3,"title":"history 路由模式下服务端配置","slug":"history-路由模式下服务端配置"},{"level":3,"title":"使用 nginx 处理跨域","slug":"使用-nginx-处理跨域"}],"relativePath":"guide/deploy.md","lastUpdated":1697592578593}',l={},o=a("h1",{id:"构建-部署"},[a("a",{class:"header-anchor",href:"#构建-部署","aria-hidden":"true"},"#"),t(" 构建&部署")],-1),c=a("div",{class:"tip custom-block"},[a("p",{class:"custom-block-title"},"前言"),a("p",null,"由于是展示项目,所以打包后相对较大,如果项目中没有用到的插件,可以删除对应的文件或者路由,不引用即可,没有引用就不会打包。"),a("p",null,[t("当然,你也可以使用精简版 "),a("a",{href:"https://github.com/vbenjs/vben-admin-thin-next",target:"_blank",rel:"noopener noreferrer"},"vue-vben-admin-thin"),t(" 进行开发。")])],-1),i=a("h2",{id:"构建"},[a("a",{class:"header-anchor",href:"#构建","aria-hidden":"true"},"#"),t(" 构建")],-1),p=a("p",null,"项目开发完成之后,执行以下命令进行构建",-1),u=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token function"},"yarn"),t(" build\n")])])],-1),r=a("p",null,"构建打包成功之后,会在根目录生成 dist 文件夹,里面就是构建打包好的文件",-1),d=a("h3",{id:"旧版浏览器兼容"},[a("a",{class:"header-anchor",href:"#旧版浏览器兼容","aria-hidden":"true"},"#"),t(" 旧版浏览器兼容")],-1),k=a("p",null,[t("在 "),a("strong",null,".env.production"),t(" 内")],-1),h=a("p",null,[t("设置 "),a("code",null,"VITE_LEGACY=true"),t(" 即可打包出兼容旧版浏览器的代码")],-1),g=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("VITE_LEGACY "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token boolean"},"true"),t("\n")])])],-1),m=a("h3",{id:"预览"},[a("a",{class:"header-anchor",href:"#预览","aria-hidden":"true"},"#"),t(" 预览")],-1),b=a("p",null,"发布之前可以在本地进行预览,有多种方式,这里介绍两种",-1),v=a("p",null,[a("strong",null,"不能直接打开构建后的 html 文件")],-1),_=a("ul",null,[a("li",null,"使用项目自定的命令进行预览(推荐)")],-1),x=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 先打包再进行预览"),t("\n"),a("span",{class:"token function"},"yarn"),t(" preview\n"),a("span",{class:"token comment"},"# 直接预览本地 dist 文件目录"),t("\n"),a("span",{class:"token function"},"yarn"),t(" preview:dist\n")])])],-1),f=a("ul",null,[a("li",null,"本地服务器预览(通过 live-server)")],-1),z=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 1.全局安装live-server"),t("\n"),a("span",{class:"token function"},"yarn"),t(" global "),a("span",{class:"token function"},"add"),t(" live-server\n"),a("span",{class:"token comment"},"# 2. 进入打包的后目录"),t("\n"),a("span",{class:"token builtin class-name"},"cd"),t(" ./dist\n"),a("span",{class:"token comment"},"# 本地预览,默认端口8080"),t("\nlive-server\n"),a("span",{class:"token comment"},"# 指定端口"),t("\nlive-server --port "),a("span",{class:"token number"},"9000"),t("\n")])])],-1),y=a("h3",{id:"分析构建文件体积"},[a("a",{class:"header-anchor",href:"#分析构建文件体积","aria-hidden":"true"},"#"),t(" 分析构建文件体积")],-1),I=a("p",null,[t("如果你的构建文件很大,可以通过项目内置 "),a("a",{href:"https://github.com/doesdev/rollup-plugin-analyzer",target:"_blank",rel:"noopener noreferrer"},"rollup-plugin-analyzer"),t(" 插件进行代码体积分析,从而优化你的代码。")],-1),T=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token function"},"yarn"),t(" report\n\n")])])],-1),P=a("p",null,"运行之后,在自动打开的页面可以看到具体的体积分布,以分析哪些依赖有问题。",-1),E=a("div",{class:"tip custom-block"},[a("p",{class:"custom-block-title"},"TIP"),a("p",null,"左上角可以切换 显示 gzip 或者 brotli")],-1),w=a("p",null,[a("img",{src:"/images/report.png",alt:""})],-1),C=a("h2",{id:"压缩"},[a("a",{class:"header-anchor",href:"#压缩","aria-hidden":"true"},"#"),t(" 压缩")],-1),j=a("h3",{id:"开启-gzip-压缩"},[a("a",{class:"header-anchor",href:"#开启-gzip-压缩","aria-hidden":"true"},"#"),t(" 开启 gzip 压缩")],-1),A=a("p",null,[t("开启 gzip,并配合 nginx 的 "),a("code",null,"gzip_static"),t(" 功能可以大大加快页面访问速度")],-1),H=a("div",{class:"tip custom-block"},[a("p",{class:"custom-block-title"},"TIP"),a("p",null,[t("只需开启 "),a("code",null,"VITE_BUILD_COMPRESS='gzip'"),t(" 即可在打包的同时生成 .gz 文件")])],-1),L=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 根据自己路径来配置更改"),t("\n"),a("span",{class:"token comment"},"# 例如部署在nginx /next/路径下 则VITE_PUBLIC_PATH=/next/"),t("\n"),a("span",{class:"token assign-left variable"},"VITE_PUBLIC_PATH"),a("span",{class:"token operator"},"="),t("/\n")])])],-1),V=a("h3",{id:"开启-brotli-压缩"},[a("a",{class:"header-anchor",href:"#开启-brotli-压缩","aria-hidden":"true"},"#"),t(" 开启 brotli 压缩")],-1),U=a("p",null,"brotli 是比 gzip 压缩率更高的算法,可以与 gzip 共存不会冲突,需要 nginx 安装指定模块并开启即可。",-1),B=a("div",{class:"tip custom-block"},[a("p",{class:"custom-block-title"},"TIP"),a("p",null,[t("只需开启 "),a("code",null,"VITE_BUILD_COMPRESS='brotli'"),t(" 即可在打包的同时生成 .br 文件")])],-1),S=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 根据自己路径来配置更改"),t("\n"),a("span",{class:"token comment"},"# 例如部署在nginx /next/路径下 则VITE_PUBLIC_PATH=/next/"),t("\n"),a("span",{class:"token assign-left variable"},"VITE_PUBLIC_PATH"),a("span",{class:"token operator"},"="),t("/\n")])])],-1),$=a("h3",{id:"同时开启-gzip-与-brotli"},[a("a",{class:"header-anchor",href:"#同时开启-gzip-与-brotli","aria-hidden":"true"},"#"),t(" 同时开启 gzip 与 brotli")],-1),M=a("p",null,[t("只需开启 "),a("code",null,"VITE_BUILD_COMPRESS='brotli,gzip'"),t(" 即可在打包的同时生成 "),a("code",null,".gz"),t(" 和 "),a("code",null,".br"),t(" 文件。")],-1),O=a("h3",{id:"gzip-与-brotli-在-nginx-内的配置"},[a("a",{class:"header-anchor",href:"#gzip-与-brotli-在-nginx-内的配置","aria-hidden":"true"},"#"),t(" gzip 与 brotli 在 nginx 内的配置")],-1),R=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("http "),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"# 开启gzip"),t("\n "),a("span",{class:"token function"},"gzip"),t(" on"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token comment"},"# 开启gzip_static"),t("\n "),a("span",{class:"token comment"},"# gzip_static 开启后可能会报错,需要安装相应的模块, 具体安装方式可以自行查询"),t("\n "),a("span",{class:"token comment"},"# 只有这个开启,vue文件打包的.gz文件才会有效果,否则不需要开启gzip进行打包"),t("\n gzip_static on"),a("span",{class:"token punctuation"},";"),t("\n gzip_proxied any"),a("span",{class:"token punctuation"},";"),t("\n gzip_min_length 1k"),a("span",{class:"token punctuation"},";"),t("\n gzip_buffers "),a("span",{class:"token number"},"4"),t(" 16k"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token comment"},"#如果nginx中使用了多层代理 必须设置这个才可以开启gzip。"),t("\n gzip_http_version "),a("span",{class:"token number"},"1.0"),a("span",{class:"token punctuation"},";"),t("\n gzip_comp_level "),a("span",{class:"token number"},"2"),a("span",{class:"token punctuation"},";"),t("\n gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png"),a("span",{class:"token punctuation"},";"),t("\n gzip_vary off"),a("span",{class:"token punctuation"},";"),t("\n gzip_disable "),a("span",{class:"token string"},'"MSIE [1-6]\\."'),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token comment"},"# 开启 brotli压缩"),t("\n "),a("span",{class:"token comment"},"# 需要安装对应的nginx模块,具体安装方式可以自行查询"),t("\n "),a("span",{class:"token comment"},"# 可以与gzip共存不会冲突"),t("\n brotli on"),a("span",{class:"token punctuation"},";"),t("\n brotli_comp_level "),a("span",{class:"token number"},"6"),a("span",{class:"token punctuation"},";"),t("\n brotli_buffers "),a("span",{class:"token number"},"16"),t(" 8k"),a("span",{class:"token punctuation"},";"),t("\n brotli_min_length "),a("span",{class:"token number"},"20"),a("span",{class:"token punctuation"},";"),t("\n brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1),W=a("h2",{id:"部署"},[a("a",{class:"header-anchor",href:"#部署","aria-hidden":"true"},"#"),t(" 部署")],-1),G=a("div",{class:"danger custom-block"},[a("p",{class:"custom-block-title"},"注意"),a("p",null,"项目默认是在生产环境开启 Mock,这样做非常不好,只是为了演示环境有数据,不建议在生产环境使用 Mock,而应该使用真实的后台接口,并将 Mock 关闭。")],-1),D=a("h3",{id:"发布"},[a("a",{class:"header-anchor",href:"#发布","aria-hidden":"true"},"#"),t(" 发布")],-1),X=a("p",null,"简单的部署只需要将最终生成的静态文件,dist 文件夹的静态文件发布到你的 cdn 或者静态服务器即可,需要注意的是其中的 index.html 通常会是你后台服务的入口页面,在确定了 js 和 css 的静态之后可能需要改变页面的引入路径。",-1),q=a("p",null,"例如上传到 nginx",-1),F=a("p",null,[a("code",null,"/srv/www/project/index.html")],-1),Y=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# nginx配置"),t("\nlocation / "),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"# 不缓存html,防止程序更新后缓存继续生效"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token variable"},"$request_filename"),t(" ~* .*"),a("span",{class:"token punctuation"},"\\"),t("."),a("span",{class:"token punctuation"},"("),t("?:htm"),a("span",{class:"token operator"},"|"),t("html"),a("span",{class:"token punctuation"},")"),t("$"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n add_header Cache-Control "),a("span",{class:"token string"},'"private, no-store, no-cache, must-revalidate, proxy-revalidate"'),a("span",{class:"token punctuation"},";"),t("\n access_log on"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n "),a("span",{class:"token comment"},"# 这里是vue打包文件dist内的文件的存放路径"),t("\n root /srv/www/project/"),a("span",{class:"token punctuation"},";"),t("\n index index.html index.htm"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n\n")])])],-1),N=a("p",null,[a("strong",null,[t("部署时可能会发现资源路径不对,只需要修改"),a("code",null,".env.production"),t("文件即可。")])],-1),J=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 根据自己路径来配置更改"),t("\n"),a("span",{class:"token comment"},"# 注意需要以 / 开头和结尾"),t("\n"),a("span",{class:"token assign-left variable"},"VITE_PUBLIC_PATH"),a("span",{class:"token operator"},"="),t("/\n"),a("span",{class:"token assign-left variable"},"VITE_PUBLIC_PATH"),a("span",{class:"token operator"},"="),t("/xxx/\n")])])],-1),K=a("h3",{id:"前端路由与服务端的结合"},[a("a",{class:"header-anchor",href:"#前端路由与服务端的结合","aria-hidden":"true"},"#"),t(" 前端路由与服务端的结合")],-1),Q=a("p",null,"项目前端路由使用的是 vue-router,所以你可以选择两种方式:history 和 hash。",-1),Z=a("ul",null,[a("li",null,[a("strong",null,"hash"),t(" 默认会在 url 后面拼接"),a("code",null,"#")]),a("li",null,[a("strong",null,"history"),t(" 则不会,不过 "),a("code",null,"history"),t(" 需要服务器配合")])],-1),nn=a("p",null,[t("可在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/router/index.ts",target:"_blank",rel:"noopener noreferrer"},"src/router/index.ts"),t(" 内进行 mode 修改")],-1),sn=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" createRouter"),a("span",{class:"token punctuation"},","),t(" createWebHashHistory"),a("span",{class:"token punctuation"},","),t(" createWebHistory "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue-router'"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token function"},"createRouter"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"{"),t("\n history"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token function"},"createWebHashHistory"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// or"),t("\n history"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token function"},"createWebHistory"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),an=a("h3",{id:"history-路由模式下服务端配置"},[a("a",{class:"header-anchor",href:"#history-路由模式下服务端配置","aria-hidden":"true"},"#"),t(" history 路由模式下服务端配置")],-1),tn=a("p",null,[t("开启 history 模式需要服务器配置,更多的服务器配置详情可以看 "),a("a",{href:"https://next.router.vuejs.org/guide/essentials/history-mode.html#html5-mode",target:"_blank",rel:"noopener noreferrer"},"history-mode")],-1),en=a("p",null,"这里以 nginx 配置为例",-1),ln=a("p",null,[a("strong",null,"部署到根目录")],-1),on=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("server "),a("span",{class:"token punctuation"},"{"),t("\n listen "),a("span",{class:"token number"},"80"),a("span",{class:"token punctuation"},";"),t("\n location / "),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"# 用于配合 History 使用"),t("\n try_files "),a("span",{class:"token variable"},"$uri"),t(),a("span",{class:"token variable"},"$uri"),t("/ /index.html"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1),cn=a("p",null,[a("strong",null,"部署到非根目录")],-1),pn=a("ol",null,[a("li",null,"首先需要在打包的时候更改配置")],-1),un=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 在.env.production内,配置子目录路径"),t("\nVITE_PUBLIC_PATH "),a("span",{class:"token operator"},"="),t(" /sub/\n")])])],-1),rn=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("server "),a("span",{class:"token punctuation"},"{"),t("\n listen "),a("span",{class:"token number"},"80"),a("span",{class:"token punctuation"},";"),t("\n server_name localhost"),a("span",{class:"token punctuation"},";"),t("\n location /sub/ "),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"# 这里是vue打包文件dist内的文件的存放路径"),t("\n "),a("span",{class:"token builtin class-name"},"alias"),t(" /srv/www/project/"),a("span",{class:"token punctuation"},";"),t("\n index index.html index.htm"),a("span",{class:"token punctuation"},";"),t("\n try_files "),a("span",{class:"token variable"},"$uri"),t(),a("span",{class:"token variable"},"$uri"),t("/ /sub/index.html"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1),dn=a("h3",{id:"使用-nginx-处理跨域"},[a("a",{class:"header-anchor",href:"#使用-nginx-处理跨域","aria-hidden":"true"},"#"),t(" 使用 nginx 处理跨域")],-1),kn=a("p",null,"使用 nginx 处理项目部署后的跨域问题",-1),hn=a("ol",null,[a("li",null,"配置前端项目接口地址")],-1),gn=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 在.env.production内,配置接口地址"),t("\n"),a("span",{class:"token assign-left variable"},"VITE_GLOB_API_URL"),a("span",{class:"token operator"},"="),t("/api\n")])])],-1),mn=a("ol",{start:"2"},[a("li",null,"在 nginx 配置请求转发到后台")],-1),bn=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("server "),a("span",{class:"token punctuation"},"{"),t("\n listen "),a("span",{class:"token number"},"8080"),a("span",{class:"token punctuation"},";"),t("\n server_name localhost"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token comment"},"# 接口代理,用于解决跨域问题"),t("\n location /api "),a("span",{class:"token punctuation"},"{"),t("\n proxy_set_header Host "),a("span",{class:"token variable"},"$host"),a("span",{class:"token punctuation"},";"),t("\n proxy_set_header X-Real-IP "),a("span",{class:"token variable"},"$remote_addr"),a("span",{class:"token punctuation"},";"),t("\n proxy_set_header X-Forwarded-For "),a("span",{class:"token variable"},"$proxy_add_x_forwarded_for"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token comment"},"# 后台接口地址"),t("\n proxy_pass http://110.110.1.1:8080/api"),a("span",{class:"token punctuation"},";"),t("\n proxy_redirect default"),a("span",{class:"token punctuation"},";"),t("\n add_header Access-Control-Allow-Origin *"),a("span",{class:"token punctuation"},";"),t("\n add_header Access-Control-Allow-Headers X-Requested-With"),a("span",{class:"token punctuation"},";"),t("\n add_header Access-Control-Allow-Methods GET,POST,OPTIONS"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1);l.render=function(a,t,e,l,vn,_n){return n(),s("div",null,[o,c,i,p,u,r,d,k,h,g,m,b,v,_,x,f,z,y,I,T,P,E,w,C,j,A,H,L,V,U,B,S,$,M,O,R,W,G,D,X,q,F,Y,N,J,K,Q,Z,nn,sn,an,tn,en,ln,on,cn,pn,un,rn,dn,kn,hn,gn,mn,bn])};export default l;export{e as __pageData}; diff --git a/assets/guide_deploy.md.d904a197.lean.js b/assets/guide_deploy.md.d904a197.lean.js new file mode 100644 index 00000000..d0a43511 --- /dev/null +++ b/assets/guide_deploy.md.d904a197.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,b as a,d as t}from"./app.8cddb23b.js";const e='{"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":3,"title":"开启 gzip 压缩","slug":"开启-gzip-压缩"},{"level":3,"title":"开启 brotli 压缩","slug":"开启-brotli-压缩"},{"level":3,"title":"同时开启 gzip 与 brotli","slug":"同时开启-gzip-与-brotli"},{"level":3,"title":"gzip 与 brotli 在 nginx 内的配置","slug":"gzip-与-brotli-在-nginx-内的配置"},{"level":2,"title":"部署","slug":"部署"},{"level":3,"title":"发布","slug":"发布"},{"level":3,"title":"前端路由与服务端的结合","slug":"前端路由与服务端的结合"},{"level":3,"title":"history 路由模式下服务端配置","slug":"history-路由模式下服务端配置"},{"level":3,"title":"使用 nginx 处理跨域","slug":"使用-nginx-处理跨域"}],"relativePath":"guide/deploy.md","lastUpdated":1697592578593}',l={},o=a("h1",{id:"构建-部署"},[a("a",{class:"header-anchor",href:"#构建-部署","aria-hidden":"true"},"#"),t(" 构建&部署")],-1),c=a("div",{class:"tip custom-block"},[a("p",{class:"custom-block-title"},"前言"),a("p",null,"由于是展示项目,所以打包后相对较大,如果项目中没有用到的插件,可以删除对应的文件或者路由,不引用即可,没有引用就不会打包。"),a("p",null,[t("当然,你也可以使用精简版 "),a("a",{href:"https://github.com/vbenjs/vben-admin-thin-next",target:"_blank",rel:"noopener noreferrer"},"vue-vben-admin-thin"),t(" 进行开发。")])],-1),i=a("h2",{id:"构建"},[a("a",{class:"header-anchor",href:"#构建","aria-hidden":"true"},"#"),t(" 构建")],-1),p=a("p",null,"项目开发完成之后,执行以下命令进行构建",-1),u=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token function"},"yarn"),t(" build\n")])])],-1),r=a("p",null,"构建打包成功之后,会在根目录生成 dist 文件夹,里面就是构建打包好的文件",-1),d=a("h3",{id:"旧版浏览器兼容"},[a("a",{class:"header-anchor",href:"#旧版浏览器兼容","aria-hidden":"true"},"#"),t(" 旧版浏览器兼容")],-1),k=a("p",null,[t("在 "),a("strong",null,".env.production"),t(" 内")],-1),h=a("p",null,[t("设置 "),a("code",null,"VITE_LEGACY=true"),t(" 即可打包出兼容旧版浏览器的代码")],-1),g=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("VITE_LEGACY "),a("span",{class:"token operator"},"="),t(),a("span",{class:"token boolean"},"true"),t("\n")])])],-1),m=a("h3",{id:"预览"},[a("a",{class:"header-anchor",href:"#预览","aria-hidden":"true"},"#"),t(" 预览")],-1),b=a("p",null,"发布之前可以在本地进行预览,有多种方式,这里介绍两种",-1),v=a("p",null,[a("strong",null,"不能直接打开构建后的 html 文件")],-1),_=a("ul",null,[a("li",null,"使用项目自定的命令进行预览(推荐)")],-1),x=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 先打包再进行预览"),t("\n"),a("span",{class:"token function"},"yarn"),t(" preview\n"),a("span",{class:"token comment"},"# 直接预览本地 dist 文件目录"),t("\n"),a("span",{class:"token function"},"yarn"),t(" preview:dist\n")])])],-1),f=a("ul",null,[a("li",null,"本地服务器预览(通过 live-server)")],-1),z=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 1.全局安装live-server"),t("\n"),a("span",{class:"token function"},"yarn"),t(" global "),a("span",{class:"token function"},"add"),t(" live-server\n"),a("span",{class:"token comment"},"# 2. 进入打包的后目录"),t("\n"),a("span",{class:"token builtin class-name"},"cd"),t(" ./dist\n"),a("span",{class:"token comment"},"# 本地预览,默认端口8080"),t("\nlive-server\n"),a("span",{class:"token comment"},"# 指定端口"),t("\nlive-server --port "),a("span",{class:"token number"},"9000"),t("\n")])])],-1),y=a("h3",{id:"分析构建文件体积"},[a("a",{class:"header-anchor",href:"#分析构建文件体积","aria-hidden":"true"},"#"),t(" 分析构建文件体积")],-1),I=a("p",null,[t("如果你的构建文件很大,可以通过项目内置 "),a("a",{href:"https://github.com/doesdev/rollup-plugin-analyzer",target:"_blank",rel:"noopener noreferrer"},"rollup-plugin-analyzer"),t(" 插件进行代码体积分析,从而优化你的代码。")],-1),T=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token function"},"yarn"),t(" report\n\n")])])],-1),P=a("p",null,"运行之后,在自动打开的页面可以看到具体的体积分布,以分析哪些依赖有问题。",-1),E=a("div",{class:"tip custom-block"},[a("p",{class:"custom-block-title"},"TIP"),a("p",null,"左上角可以切换 显示 gzip 或者 brotli")],-1),w=a("p",null,[a("img",{src:"/images/report.png",alt:""})],-1),C=a("h2",{id:"压缩"},[a("a",{class:"header-anchor",href:"#压缩","aria-hidden":"true"},"#"),t(" 压缩")],-1),j=a("h3",{id:"开启-gzip-压缩"},[a("a",{class:"header-anchor",href:"#开启-gzip-压缩","aria-hidden":"true"},"#"),t(" 开启 gzip 压缩")],-1),A=a("p",null,[t("开启 gzip,并配合 nginx 的 "),a("code",null,"gzip_static"),t(" 功能可以大大加快页面访问速度")],-1),H=a("div",{class:"tip custom-block"},[a("p",{class:"custom-block-title"},"TIP"),a("p",null,[t("只需开启 "),a("code",null,"VITE_BUILD_COMPRESS='gzip'"),t(" 即可在打包的同时生成 .gz 文件")])],-1),L=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 根据自己路径来配置更改"),t("\n"),a("span",{class:"token comment"},"# 例如部署在nginx /next/路径下 则VITE_PUBLIC_PATH=/next/"),t("\n"),a("span",{class:"token assign-left variable"},"VITE_PUBLIC_PATH"),a("span",{class:"token operator"},"="),t("/\n")])])],-1),V=a("h3",{id:"开启-brotli-压缩"},[a("a",{class:"header-anchor",href:"#开启-brotli-压缩","aria-hidden":"true"},"#"),t(" 开启 brotli 压缩")],-1),U=a("p",null,"brotli 是比 gzip 压缩率更高的算法,可以与 gzip 共存不会冲突,需要 nginx 安装指定模块并开启即可。",-1),B=a("div",{class:"tip custom-block"},[a("p",{class:"custom-block-title"},"TIP"),a("p",null,[t("只需开启 "),a("code",null,"VITE_BUILD_COMPRESS='brotli'"),t(" 即可在打包的同时生成 .br 文件")])],-1),S=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 根据自己路径来配置更改"),t("\n"),a("span",{class:"token comment"},"# 例如部署在nginx /next/路径下 则VITE_PUBLIC_PATH=/next/"),t("\n"),a("span",{class:"token assign-left variable"},"VITE_PUBLIC_PATH"),a("span",{class:"token operator"},"="),t("/\n")])])],-1),$=a("h3",{id:"同时开启-gzip-与-brotli"},[a("a",{class:"header-anchor",href:"#同时开启-gzip-与-brotli","aria-hidden":"true"},"#"),t(" 同时开启 gzip 与 brotli")],-1),M=a("p",null,[t("只需开启 "),a("code",null,"VITE_BUILD_COMPRESS='brotli,gzip'"),t(" 即可在打包的同时生成 "),a("code",null,".gz"),t(" 和 "),a("code",null,".br"),t(" 文件。")],-1),O=a("h3",{id:"gzip-与-brotli-在-nginx-内的配置"},[a("a",{class:"header-anchor",href:"#gzip-与-brotli-在-nginx-内的配置","aria-hidden":"true"},"#"),t(" gzip 与 brotli 在 nginx 内的配置")],-1),R=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("http "),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"# 开启gzip"),t("\n "),a("span",{class:"token function"},"gzip"),t(" on"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token comment"},"# 开启gzip_static"),t("\n "),a("span",{class:"token comment"},"# gzip_static 开启后可能会报错,需要安装相应的模块, 具体安装方式可以自行查询"),t("\n "),a("span",{class:"token comment"},"# 只有这个开启,vue文件打包的.gz文件才会有效果,否则不需要开启gzip进行打包"),t("\n gzip_static on"),a("span",{class:"token punctuation"},";"),t("\n gzip_proxied any"),a("span",{class:"token punctuation"},";"),t("\n gzip_min_length 1k"),a("span",{class:"token punctuation"},";"),t("\n gzip_buffers "),a("span",{class:"token number"},"4"),t(" 16k"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token comment"},"#如果nginx中使用了多层代理 必须设置这个才可以开启gzip。"),t("\n gzip_http_version "),a("span",{class:"token number"},"1.0"),a("span",{class:"token punctuation"},";"),t("\n gzip_comp_level "),a("span",{class:"token number"},"2"),a("span",{class:"token punctuation"},";"),t("\n gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png"),a("span",{class:"token punctuation"},";"),t("\n gzip_vary off"),a("span",{class:"token punctuation"},";"),t("\n gzip_disable "),a("span",{class:"token string"},'"MSIE [1-6]\\."'),a("span",{class:"token punctuation"},";"),t("\n\n "),a("span",{class:"token comment"},"# 开启 brotli压缩"),t("\n "),a("span",{class:"token comment"},"# 需要安装对应的nginx模块,具体安装方式可以自行查询"),t("\n "),a("span",{class:"token comment"},"# 可以与gzip共存不会冲突"),t("\n brotli on"),a("span",{class:"token punctuation"},";"),t("\n brotli_comp_level "),a("span",{class:"token number"},"6"),a("span",{class:"token punctuation"},";"),t("\n brotli_buffers "),a("span",{class:"token number"},"16"),t(" 8k"),a("span",{class:"token punctuation"},";"),t("\n brotli_min_length "),a("span",{class:"token number"},"20"),a("span",{class:"token punctuation"},";"),t("\n brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1),W=a("h2",{id:"部署"},[a("a",{class:"header-anchor",href:"#部署","aria-hidden":"true"},"#"),t(" 部署")],-1),G=a("div",{class:"danger custom-block"},[a("p",{class:"custom-block-title"},"注意"),a("p",null,"项目默认是在生产环境开启 Mock,这样做非常不好,只是为了演示环境有数据,不建议在生产环境使用 Mock,而应该使用真实的后台接口,并将 Mock 关闭。")],-1),D=a("h3",{id:"发布"},[a("a",{class:"header-anchor",href:"#发布","aria-hidden":"true"},"#"),t(" 发布")],-1),X=a("p",null,"简单的部署只需要将最终生成的静态文件,dist 文件夹的静态文件发布到你的 cdn 或者静态服务器即可,需要注意的是其中的 index.html 通常会是你后台服务的入口页面,在确定了 js 和 css 的静态之后可能需要改变页面的引入路径。",-1),q=a("p",null,"例如上传到 nginx",-1),F=a("p",null,[a("code",null,"/srv/www/project/index.html")],-1),Y=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# nginx配置"),t("\nlocation / "),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"# 不缓存html,防止程序更新后缓存继续生效"),t("\n "),a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"("),a("span",{class:"token variable"},"$request_filename"),t(" ~* .*"),a("span",{class:"token punctuation"},"\\"),t("."),a("span",{class:"token punctuation"},"("),t("?:htm"),a("span",{class:"token operator"},"|"),t("html"),a("span",{class:"token punctuation"},")"),t("$"),a("span",{class:"token punctuation"},")"),t(),a("span",{class:"token punctuation"},"{"),t("\n add_header Cache-Control "),a("span",{class:"token string"},'"private, no-store, no-cache, must-revalidate, proxy-revalidate"'),a("span",{class:"token punctuation"},";"),t("\n access_log on"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n "),a("span",{class:"token comment"},"# 这里是vue打包文件dist内的文件的存放路径"),t("\n root /srv/www/project/"),a("span",{class:"token punctuation"},";"),t("\n index index.html index.htm"),a("span",{class:"token punctuation"},";"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n\n")])])],-1),N=a("p",null,[a("strong",null,[t("部署时可能会发现资源路径不对,只需要修改"),a("code",null,".env.production"),t("文件即可。")])],-1),J=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 根据自己路径来配置更改"),t("\n"),a("span",{class:"token comment"},"# 注意需要以 / 开头和结尾"),t("\n"),a("span",{class:"token assign-left variable"},"VITE_PUBLIC_PATH"),a("span",{class:"token operator"},"="),t("/\n"),a("span",{class:"token assign-left variable"},"VITE_PUBLIC_PATH"),a("span",{class:"token operator"},"="),t("/xxx/\n")])])],-1),K=a("h3",{id:"前端路由与服务端的结合"},[a("a",{class:"header-anchor",href:"#前端路由与服务端的结合","aria-hidden":"true"},"#"),t(" 前端路由与服务端的结合")],-1),Q=a("p",null,"项目前端路由使用的是 vue-router,所以你可以选择两种方式:history 和 hash。",-1),Z=a("ul",null,[a("li",null,[a("strong",null,"hash"),t(" 默认会在 url 后面拼接"),a("code",null,"#")]),a("li",null,[a("strong",null,"history"),t(" 则不会,不过 "),a("code",null,"history"),t(" 需要服务器配合")])],-1),nn=a("p",null,[t("可在 "),a("a",{href:"https://github.com/vbenjs/vue-vben-admin/tree/main/src/router/index.ts",target:"_blank",rel:"noopener noreferrer"},"src/router/index.ts"),t(" 内进行 mode 修改")],-1),sn=a("div",{class:"language-ts"},[a("pre",null,[a("code",null,[a("span",{class:"token keyword"},"import"),t(),a("span",{class:"token punctuation"},"{"),t(" createRouter"),a("span",{class:"token punctuation"},","),t(" createWebHashHistory"),a("span",{class:"token punctuation"},","),t(" createWebHistory "),a("span",{class:"token punctuation"},"}"),t(),a("span",{class:"token keyword"},"from"),t(),a("span",{class:"token string"},"'vue-router'"),a("span",{class:"token punctuation"},";"),t("\n\n"),a("span",{class:"token function"},"createRouter"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},"{"),t("\n history"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token function"},"createWebHashHistory"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},","),t("\n "),a("span",{class:"token comment"},"// or"),t("\n history"),a("span",{class:"token operator"},":"),t(),a("span",{class:"token function"},"createWebHistory"),a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},","),t("\n"),a("span",{class:"token punctuation"},"}"),a("span",{class:"token punctuation"},")"),a("span",{class:"token punctuation"},";"),t("\n")])])],-1),an=a("h3",{id:"history-路由模式下服务端配置"},[a("a",{class:"header-anchor",href:"#history-路由模式下服务端配置","aria-hidden":"true"},"#"),t(" history 路由模式下服务端配置")],-1),tn=a("p",null,[t("开启 history 模式需要服务器配置,更多的服务器配置详情可以看 "),a("a",{href:"https://next.router.vuejs.org/guide/essentials/history-mode.html#html5-mode",target:"_blank",rel:"noopener noreferrer"},"history-mode")],-1),en=a("p",null,"这里以 nginx 配置为例",-1),ln=a("p",null,[a("strong",null,"部署到根目录")],-1),on=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("server "),a("span",{class:"token punctuation"},"{"),t("\n listen "),a("span",{class:"token number"},"80"),a("span",{class:"token punctuation"},";"),t("\n location / "),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"# 用于配合 History 使用"),t("\n try_files "),a("span",{class:"token variable"},"$uri"),t(),a("span",{class:"token variable"},"$uri"),t("/ /index.html"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1),cn=a("p",null,[a("strong",null,"部署到非根目录")],-1),pn=a("ol",null,[a("li",null,"首先需要在打包的时候更改配置")],-1),un=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 在.env.production内,配置子目录路径"),t("\nVITE_PUBLIC_PATH "),a("span",{class:"token operator"},"="),t(" /sub/\n")])])],-1),rn=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("server "),a("span",{class:"token punctuation"},"{"),t("\n listen "),a("span",{class:"token number"},"80"),a("span",{class:"token punctuation"},";"),t("\n server_name localhost"),a("span",{class:"token punctuation"},";"),t("\n location /sub/ "),a("span",{class:"token punctuation"},"{"),t("\n "),a("span",{class:"token comment"},"# 这里是vue打包文件dist内的文件的存放路径"),t("\n "),a("span",{class:"token builtin class-name"},"alias"),t(" /srv/www/project/"),a("span",{class:"token punctuation"},";"),t("\n index index.html index.htm"),a("span",{class:"token punctuation"},";"),t("\n try_files "),a("span",{class:"token variable"},"$uri"),t(),a("span",{class:"token variable"},"$uri"),t("/ /sub/index.html"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1),dn=a("h3",{id:"使用-nginx-处理跨域"},[a("a",{class:"header-anchor",href:"#使用-nginx-处理跨域","aria-hidden":"true"},"#"),t(" 使用 nginx 处理跨域")],-1),kn=a("p",null,"使用 nginx 处理项目部署后的跨域问题",-1),hn=a("ol",null,[a("li",null,"配置前端项目接口地址")],-1),gn=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[a("span",{class:"token comment"},"# 在.env.production内,配置接口地址"),t("\n"),a("span",{class:"token assign-left variable"},"VITE_GLOB_API_URL"),a("span",{class:"token operator"},"="),t("/api\n")])])],-1),mn=a("ol",{start:"2"},[a("li",null,"在 nginx 配置请求转发到后台")],-1),bn=a("div",{class:"language-bash"},[a("pre",null,[a("code",null,[t("server "),a("span",{class:"token punctuation"},"{"),t("\n listen "),a("span",{class:"token number"},"8080"),a("span",{class:"token punctuation"},";"),t("\n server_name localhost"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token comment"},"# 接口代理,用于解决跨域问题"),t("\n location /api "),a("span",{class:"token punctuation"},"{"),t("\n proxy_set_header Host "),a("span",{class:"token variable"},"$host"),a("span",{class:"token punctuation"},";"),t("\n proxy_set_header X-Real-IP "),a("span",{class:"token variable"},"$remote_addr"),a("span",{class:"token punctuation"},";"),t("\n proxy_set_header X-Forwarded-For "),a("span",{class:"token variable"},"$proxy_add_x_forwarded_for"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token comment"},"# 后台接口地址"),t("\n proxy_pass http://110.110.1.1:8080/api"),a("span",{class:"token punctuation"},";"),t("\n proxy_redirect default"),a("span",{class:"token punctuation"},";"),t("\n add_header Access-Control-Allow-Origin *"),a("span",{class:"token punctuation"},";"),t("\n add_header Access-Control-Allow-Headers X-Requested-With"),a("span",{class:"token punctuation"},";"),t("\n add_header Access-Control-Allow-Methods GET,POST,OPTIONS"),a("span",{class:"token punctuation"},";"),t("\n "),a("span",{class:"token punctuation"},"}"),t("\n"),a("span",{class:"token punctuation"},"}"),t("\n")])])],-1);l.render=function(a,t,e,l,vn,_n){return n(),s("div",null,[o,c,i,p,u,r,d,k,h,g,m,b,v,_,x,f,z,y,I,T,P,E,w,C,j,A,H,L,V,U,B,S,$,M,O,R,W,G,D,X,q,F,Y,N,J,K,Q,Z,nn,sn,an,tn,en,ln,on,cn,pn,un,rn,dn,kn,hn,gn,mn,bn])};export default l;export{e as __pageData}; diff --git a/assets/guide_design.md.eae80806.js b/assets/guide_design.md.eae80806.js new file mode 100644 index 00000000..f4f166b6 --- /dev/null +++ b/assets/guide_design.md.eae80806.js @@ -0,0 +1 @@ +import{o as s,c as n,a}from"./app.8cddb23b.js";const t='{"title":"样式","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"tailwindcss(2.5.0+)","slug":"tailwindcss-2-5-0"},{"level":2,"title":"windicss(2.5.0 已弃用)","slug":"windicss-2-5-0-已弃用"},{"level":2,"title":"为什么使用 Less","slug":"为什么使用-less"},{"level":2,"title":"开启 scoped","slug":"开启-scoped"},{"level":2,"title":"深度选择器","slug":"深度选择器"},{"level":2,"title":"CSS Modules","slug":"css-modules"},{"level":2,"title":"重复引用问题","slug":"重复引用问题"}],"relativePath":"guide/design.md","lastUpdated":1697592578593}',p={},e=a('

样式

介绍

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

默认使用 less 作为预处理语言,建议在使用前或者遇到疑问时学习一下 Less 的相关特性(如果想获取基础的 CSS 知识或查阅属性,请参考 MDN 文档)。

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

.\n├── ant # ant design 一些样式覆盖\n├── color.less # 颜色\n├── index.less # 入口\n├── public.less # 公共类\n├── theme.less # 主题相关\n├── config.less  # 每个组件都会自动引入样式\n├── transition # 动画相关\n└── var # 变量\n\n

全局注入

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

<style lang="less" scoped>\n  // 这里已经隐式注入了 config.less\n</style>\n

tailwindcss(2.5.0+)

项目中引用到了 tailwindcss,具体可以见文件使用说明。

语法如下:

<div class="relative w-full h-full px-4"></div>\n

windicss(2.5.0 已弃用)

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

语法如下:

<div class="relative w-full h-full px-4"></div>\n

注意事项

windcss 目前会造成本地开发内存溢出,所以后续可能会考虑切换到 TailwindCss,两者基本相同。

所以尽量少用 Windicss 新增的特性,防止后续切换成本高。

为什么使用 Less

主要是因为 Ant Design 默认使用 less 作为样式语言,使用 Less 可以跟其保持一致。

开启 scoped

没有加 scoped 属性,默认会编译成全局样式,可能会造成全局污染

<style></style>\n\n<style scoped></style>\n

温馨提醒

使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。

深度选择器

有时我们可能想明确地制定一个针对子组件的规则。

如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作符。有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下你可以使用 /deep/::v-deep 操作符取而代之——两者都是 >>> 的别名,同样可以正常工作。

详情可以查看 RFC0023-scoped-styles-changes

使用 scoped 后,父组件的样式将不会渗透到子组件中,所以可以使用以下方式解决:

<style scoped>\n  /* deep selectors */\n  ::v-deep(.foo) {\n  }\n  /* shorthand */\n  :deep(.foo) {\n  }\n\n  /* targeting slot content */\n  ::v-slotted(.foo) {\n  }\n  /* shorthand */\n  :slotted(.foo) {\n  }\n\n  /* one-off global rule */\n  ::v-global(.foo) {\n  }\n  /* shorthand */\n  :global(.foo) {\n  }\n</style>\n

CSS Modules

针对样式覆盖问题,还有一种方案是使用 CSS Modules 模块化方案。使用方式如下。

<template>\n  <span :class="$style.span1">hello</span>\n</template>\n\n<script>\n  import { useCSSModule } from 'vue';\n\n  export default {\n    setup(props, context) {\n      const $style = useCSSModule();\n      const moduleAStyle = useCSSModule('moduleA');\n      return {\n        $style,\n        moduleAStyle,\n      };\n    },\n  };\n</script>\n\n<style lang="less" module>\n  .span1 {\n    color: green;\n    font-size: 30px;\n  }\n</style>\n\n<style lang="less" module="moduleA">\n  .span1 {\n    color: green;\n    font-size: 30px;\n  }\n</style>\n

重复引用问题

加上 reference 可以解决页面内重复引用导致实际生成的 style 样式表重复的问题。

这步已经全局引入了。所以可以不写,直接使用变量

<style lang="less" scoped>\n  /* 该行代码已全局引用。可以不用单独引入 */\n  @import (reference) '../../design/config.less';\n<style>\n
',36);p.render=function(a,t,p,o,c,l){return s(),n("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/guide_design.md.eae80806.lean.js b/assets/guide_design.md.eae80806.lean.js new file mode 100644 index 00000000..56d52a2f --- /dev/null +++ b/assets/guide_design.md.eae80806.lean.js @@ -0,0 +1 @@ +import{o as s,c as n,a}from"./app.8cddb23b.js";const t='{"title":"样式","description":"","frontmatter":{},"headers":[{"level":2,"title":"介绍","slug":"介绍"},{"level":2,"title":"tailwindcss(2.5.0+)","slug":"tailwindcss-2-5-0"},{"level":2,"title":"windicss(2.5.0 已弃用)","slug":"windicss-2-5-0-已弃用"},{"level":2,"title":"为什么使用 Less","slug":"为什么使用-less"},{"level":2,"title":"开启 scoped","slug":"开启-scoped"},{"level":2,"title":"深度选择器","slug":"深度选择器"},{"level":2,"title":"CSS Modules","slug":"css-modules"},{"level":2,"title":"重复引用问题","slug":"重复引用问题"}],"relativePath":"guide/design.md","lastUpdated":1697592578593}',p={},e=a('',36);p.render=function(a,t,p,o,c,l){return s(),n("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/guide_electron.md.1fc6b47b.js b/assets/guide_electron.md.1fc6b47b.js new file mode 100644 index 00000000..98e04bfe --- /dev/null +++ b/assets/guide_electron.md.1fc6b47b.js @@ -0,0 +1 @@ +import{o as e,c as a,a as n}from"./app.8cddb23b.js";const r='{"title":"Electron","description":"","frontmatter":{},"headers":[{"level":2,"title":"URL 模式","slug":"url-模式"},{"level":3,"title":"使用","slug":"使用"},{"level":3,"title":"从 GitHub 获取代码","slug":"从-github-获取代码"},{"level":3,"title":"安装依赖","slug":"安装依赖"},{"level":3,"title":"运行","slug":"运行"},{"level":3,"title":"打包","slug":"打包"},{"level":2,"title":"标准模式","slug":"标准模式"}],"relativePath":"guide/electron.md","lastUpdated":1697592578593}',s={},t=n('

Electron

URL 模式

这种模式会先启动 vite 服务,Electron 使用 Url 地址来进行渲染

使用

从 GitHub 获取代码

Electron 代码在 electron-main 分支

# clone electron-main分支代码\ngit clone -b electron-main https://github.com/vbenjs/vue-vben-admin vben-admin-electron\n

安装依赖

yarn\n

提示

首次下载 Electron 依赖会比较慢,可以在项目根目录下新建.npmrc文件,填入下方内容即可

ELETRON_MIRROR=https://npm.taobao.org/mirrors/electron/\n

运行

yarn dev:app\n

打包

yarn build:app\n

标准模式

TODO: 待适配

',16);s.render=function(n,r,s,l,i,c){return e(),a("div",null,[t])};export default s;export{r as __pageData}; diff --git a/assets/guide_electron.md.1fc6b47b.lean.js b/assets/guide_electron.md.1fc6b47b.lean.js new file mode 100644 index 00000000..6dd8d68a --- /dev/null +++ b/assets/guide_electron.md.1fc6b47b.lean.js @@ -0,0 +1 @@ +import{o as e,c as a,a as n}from"./app.8cddb23b.js";const r='{"title":"Electron","description":"","frontmatter":{},"headers":[{"level":2,"title":"URL 模式","slug":"url-模式"},{"level":3,"title":"使用","slug":"使用"},{"level":3,"title":"从 GitHub 获取代码","slug":"从-github-获取代码"},{"level":3,"title":"安装依赖","slug":"安装依赖"},{"level":3,"title":"运行","slug":"运行"},{"level":3,"title":"打包","slug":"打包"},{"level":2,"title":"标准模式","slug":"标准模式"}],"relativePath":"guide/electron.md","lastUpdated":1697592578593}',s={},t=n('',16);s.render=function(n,r,s,l,i,c){return e(),a("div",null,[t])};export default s;export{r as __pageData}; diff --git a/assets/guide_index.md.bb37fa1c.js b/assets/guide_index.md.bb37fa1c.js new file mode 100644 index 00000000..2db7159e --- /dev/null +++ b/assets/guide_index.md.bb37fa1c.js @@ -0,0 +1 @@ +import{o as n,c as s,a as e}from"./app.8cddb23b.js";const a='{"title":"开始","description":"","frontmatter":{},"headers":[{"level":2,"title":"前言","slug":"前言"},{"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"},{"level":3,"title":"生成图标集","slug":"生成图标集"},{"level":3,"title":"重新安装依赖","slug":"重新安装依赖"},{"level":2,"title":"目录说明","slug":"目录说明"}],"relativePath":"guide/index.md","lastUpdated":1697592578593}',t={},o=e('

开始

本文会帮助你从头启动项目

前言

关于组件

项目虽然二次封装了一些组件,但是可能不能满足大部分的要求。所以,如果组件不满足你的要求,完全可以不用甚至删除代码自己写,不必坚持使用项目自带的组件。

环境准备

本地环境需要安装 pnpmNode.jsGit

注意

  • 推荐使用pnpm,否则依赖可能安装不上。
  • Node.js 版本要求14.x以上,这里推荐 20.x 及以上。
  • 推荐安装 nvm 来管理 Node.js 版本。

工具配置

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

代码获取

注意

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

从 GitHub 获取代码

# clone 代码\ngit clone https://github.com/vbenjs/vue-vben-admin.git\n\n

从 Gitee 获取代码

如果从 github clone 代码较慢的话,可以尝试用 Gitee 同步代码到自己的仓库,再 clone 下来即可。

也可以通过下方地址进行 clone

git clone https://gitee.com/annsion/vue-vben-admin.git\n

注意

Gitee的代码可能不是最新的

安装

安装 Node.js

如果您电脑未安装Node.js,请安装它。

验证

# 出现相应npm版本即可\nnpm -v\n# 出现相应node版本即可\nnode -v\n\n

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

安装依赖

pnpm 安装

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

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

# 全局安装pnpm\nnpm install -g pnpm\n# 验证\npnpm -v # 出现对应版本号即代表安装成功\n

依赖安装命令

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

# 安装依赖\npnpm i\n

安装依赖时 husky 安装失败

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

npm script

"scripts": {\n  # 安装依赖\n  "bootstrap": "pnpm install",\n  # 构建项目\n  "build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build",\n  # 生成打包分析,在电脑上执行完成后会自动打开界面\n  "build:analyze": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build --mode analyze",\n  # 构建成docker镜像\n  "build:docker": "vite build --mode docker",\n  # 清空缓存后构建项目\n  "build:no-cache": "pnpm clean:cache && npm run build",\n  "build:test": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build --mode test",\n  # 用于生成标准化的git commit message\n  "commit": "czg",\n  # 运行项目\n  "dev": "pnpm vite",\n  "preinstall": "npx only-allow pnpm",\n  "postinstall": "turbo run stub",\n  "lint": "turbo run lint",\n  # 执行 eslint 校验,并修复部分问题\n  "lint:eslint": "eslint --cache --max-warnings 0  \\"{src,mock}/**/*.{vue,ts,tsx}\\" --fix",\n  # 执行 prettier 格式化(该命令会对项目所有代码进行 prettier 格式化,请谨慎执行)\n  "lint:prettier": "prettier --write .",\n  # 执行 stylelint 格式化\n  "lint:stylelint": "stylelint \\"**/*.{vue,css,less,scss}\\" --fix --cache --cache-location node_modules/.cache/stylelint/",\n  # 安装git hooks\n  "prepare": "husky install",\n  # 预览打包后的内容(先打包在进行预览)\n  "preview": "npm run build && vite preview",\n  # 重新安装依赖\n  "reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",\n  # 运行项目\n  "serve": "npm run dev",\n  # 对打包结果进行 gzip 测试\n  "test:gzip": "npx http-server dist --cors --gzip -c-1",\n  # 类型检查\n  "type:check": "vue-tsc --noEmit --skipLibCheck"\n},\n

生成图标集

该命令会生成所选择的图标集,提供给图标选择器使用。具体使用方式请查看 图标集生成

重新安装依赖

该命令会先删除 node_modulesyarn.lockpackage.lock.json 后再进行依赖重新安装(安装速度会明显变慢)。

接下来你可以修改代码进行业务开发了。我们内建了模拟数据、HMR 实时预览、状态管理、国际化、全局路由等各种实用的功能辅助开发,请阅读其他章节了解更多。

目录说明

\n.\n├── build # 打包脚本相关\n│   ├── config # 配置文件\n│   ├── generate # 生成器\n│   ├── script # 脚本\n│   └── vite # vite配置\n├── mock # mock文件夹\n├── public # 公共静态资源目录\n├── src # 主目录\n│   ├── api # 接口文件\n│   ├── assets # 资源文件\n│   │   ├── icons # icon sprite 图标文件夹\n│   │   ├── images # 项目存放图片的文件夹\n│   │   └── svg # 项目存放svg图片的文件夹\n│   ├── components # 公共组件\n│   ├── design # 样式文件\n│   ├── directives # 指令\n│   ├── enums # 枚举/常量\n│   ├── hooks # hook\n│   │   ├── component # 组件相关hook\n│   │   ├── core # 基础hook\n│   │   ├── event # 事件相关hook\n│   │   ├── setting # 配置相关hook\n│   │   └── web # web相关hook\n│   ├── layouts # 布局文件\n│   │   ├── default # 默认布局\n│   │   ├── iframe # iframe布局\n│   │   └── page # 页面布局\n│   ├── locales # 多语言\n│   ├── logics # 逻辑\n│   ├── main.ts # 主入口\n│   ├── router # 路由配置\n│   ├── settings # 项目配置\n│   │   ├── componentSetting.ts # 组件配置\n│   │   ├── designSetting.ts # 样式配置\n│   │   ├── encryptionSetting.ts # 加密配置\n│   │   ├── localeSetting.ts # 多语言配置\n│   │   ├── projectSetting.ts # 项目配置\n│   │   └── siteSetting.ts # 站点配置\n│   ├── store # 数据仓库\n│   ├── utils # 工具类\n│   └── views # 页面\n├── types # 类型文件\n└── vite.config.ts # vite配置文件\n\n
',43);t.render=function(e,a,t,p,l,c){return n(),s("div",null,[o])};export default t;export{a as __pageData}; diff --git a/assets/guide_index.md.bb37fa1c.lean.js b/assets/guide_index.md.bb37fa1c.lean.js new file mode 100644 index 00000000..c12df29c --- /dev/null +++ b/assets/guide_index.md.bb37fa1c.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a as e}from"./app.8cddb23b.js";const a='{"title":"开始","description":"","frontmatter":{},"headers":[{"level":2,"title":"前言","slug":"前言"},{"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"},{"level":3,"title":"生成图标集","slug":"生成图标集"},{"level":3,"title":"重新安装依赖","slug":"重新安装依赖"},{"level":2,"title":"目录说明","slug":"目录说明"}],"relativePath":"guide/index.md","lastUpdated":1697592578593}',t={},o=e('',43);t.render=function(e,a,t,p,l,c){return n(),s("div",null,[o])};export default t;export{a as __pageData}; diff --git a/assets/guide_introduction.md.e458436d.js b/assets/guide_introduction.md.e458436d.js new file mode 100644 index 00000000..4beea1ec --- /dev/null +++ b/assets/guide_introduction.md.e458436d.js @@ -0,0 +1 @@ +import{o as e,c as r,a as t}from"./app.8cddb23b.js";const n='{"title":"介绍","description":"","frontmatter":{},"headers":[{"level":2,"title":"简介","slug":"简介"},{"level":2,"title":"文档","slug":"文档"},{"level":3,"title":"本地运行文档","slug":"本地运行文档"},{"level":2,"title":"需要掌握的基础知识","slug":"需要掌握的基础知识"},{"level":2,"title":"模版","slug":"模版"},{"level":2,"title":"vite 插件推荐","slug":"vite-插件推荐"},{"level":2,"title":"浏览器支持","slug":"浏览器支持"},{"level":2,"title":"如何加入我们","slug":"如何加入我们"}],"relativePath":"guide/introduction.md","lastUpdated":1697592578593}',a={},o=t('

介绍

简介

Vue-Vben-Admin 是一个基于 Vue3.0ViteAnt-Design-VueTypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3vitets 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。

文档

  • 中文文档地址为 vben-admin-doc,采用 Vitepress 开发。如发现文档有误,欢迎提 pr 帮助我们改进。
  • 英文文档暂时没有时间来写,欢迎有时间的同学来帮忙写英文文档。

本地运行文档

如需本地运行文档,请拉取代码到本地。

# 拉取代码\ngit clone https://github.com/vbenjs/vue-vben-admin-doc\n\n# 安装依赖\nyarn\n\n# 运行项目\nyarn dev\n

需要掌握的基础知识

本项目需要一定前端基础知识,请确保掌握 Vue 的基础知识,以便能处理一些常见的问题。建议在开发前先学一下以下内容,提前了解和学习这些知识,会对项目理解非常有帮助:

模版

该版本主要是提供一些 Demo 示例及插件的使用集成方式,主要用于参考。如果对项目不是很熟悉,不建议在此基础上进行开发,请使用下方提供的精简版本。

vue-vben-admin 精简版本。删除了相关示例、无用文件及功能、依赖。可以根据自身需求安装对应的依赖。因为使用的是 vite,依赖删除不会导致相关组件或者 hook 发出警告。只在需要的时候安装对应的库即可。

vite 插件推荐

如果这些插件对你有帮助,可以给一个 star 支持下

浏览器支持

本地开发推荐使用Chrome 最新版浏览器,不支持Chrome 80以下版本。

生产环境支持现代浏览器,不支持 IE。

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

如何加入我们

  • Vue-Vben-Admin 还在持续更新中,本项目欢迎您的参与,共同维护,逐步完善,将项目做得更强。同时整个项目本着一切免费的原则,原则上不会收取任何费用及版权,可以放心使用。
  • 如果你想加入我们,可以多提供一些好的建议或者提交 pr,我们会根据你的活跃度邀请你加入。
',25);a.render=function(t,n,a,i,l,s){return e(),r("div",null,[o])};export default a;export{n as __pageData}; diff --git a/assets/guide_introduction.md.e458436d.lean.js b/assets/guide_introduction.md.e458436d.lean.js new file mode 100644 index 00000000..9e33a093 --- /dev/null +++ b/assets/guide_introduction.md.e458436d.lean.js @@ -0,0 +1 @@ +import{o as e,c as r,a as t}from"./app.8cddb23b.js";const n='{"title":"介绍","description":"","frontmatter":{},"headers":[{"level":2,"title":"简介","slug":"简介"},{"level":2,"title":"文档","slug":"文档"},{"level":3,"title":"本地运行文档","slug":"本地运行文档"},{"level":2,"title":"需要掌握的基础知识","slug":"需要掌握的基础知识"},{"level":2,"title":"模版","slug":"模版"},{"level":2,"title":"vite 插件推荐","slug":"vite-插件推荐"},{"level":2,"title":"浏览器支持","slug":"浏览器支持"},{"level":2,"title":"如何加入我们","slug":"如何加入我们"}],"relativePath":"guide/introduction.md","lastUpdated":1697592578593}',a={},o=t('',25);a.render=function(t,n,a,i,l,s){return e(),r("div",null,[o])};export default a;export{n as __pageData}; diff --git a/assets/guide_lib.md.9e42530e.js b/assets/guide_lib.md.9e42530e.js new file mode 100644 index 00000000..8a909064 --- /dev/null +++ b/assets/guide_lib.md.9e42530e.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"引入外部模块","description":"","frontmatter":{},"headers":[{"level":2,"title":"安装","slug":"安装"},{"level":2,"title":"使用","slug":"使用"},{"level":3,"title":"全局使用","slug":"全局使用"},{"level":3,"title":"局部使用","slug":"局部使用"},{"level":2,"title":"注意","slug":"注意"}],"relativePath":"guide/lib.md","lastUpdated":1697592578593}',p={},e=s('

引入外部模块

除了自带组件以外,有时我们还需要引入其他外部模块。我们以 ant-design-vue 为例:

安装

安装 ant-design-vue

# 在终端输入下面的命令完成安装\nyarn add ant-design-vue\n

使用

全局使用

import { createApp } from 'vue';\nimport App from './App.vue';\nimport Antd from 'ant-design-vue';\nconst app = createApp(App);\napp.use(Antd);\napp.mount('#app');\n

局部使用

<template>\n  <Button>text</Button>\n</template>\n\n<script>\n  import { defineComponent } from 'vue';\n  import { Button } from 'ant-design-vue';\n  export default defineComponent({\n    components: {\n      Button,\n    },\n  });\n</script>\n

注意

  • 如果组件有依赖样式,则需要再引入样式文件
',12);p.render=function(s,t,p,o,c,l){return n(),a("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/guide_lib.md.9e42530e.lean.js b/assets/guide_lib.md.9e42530e.lean.js new file mode 100644 index 00000000..9fb028bd --- /dev/null +++ b/assets/guide_lib.md.9e42530e.lean.js @@ -0,0 +1 @@ +import{o as n,c as a,a as s}from"./app.8cddb23b.js";const t='{"title":"引入外部模块","description":"","frontmatter":{},"headers":[{"level":2,"title":"安装","slug":"安装"},{"level":2,"title":"使用","slug":"使用"},{"level":3,"title":"全局使用","slug":"全局使用"},{"level":3,"title":"局部使用","slug":"局部使用"},{"level":2,"title":"注意","slug":"注意"}],"relativePath":"guide/lib.md","lastUpdated":1697592578593}',p={},e=s('',12);p.render=function(s,t,p,o,c,l){return n(),a("div",null,[e])};export default p;export{t as __pageData}; diff --git a/assets/guide_menu.md.3fefed66.js b/assets/guide_menu.md.3fefed66.js new file mode 100644 index 00000000..ef43a074 --- /dev/null +++ b/assets/guide_menu.md.3fefed66.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"菜单","description":"","frontmatter":{},"headers":[{"level":2,"title":"菜单项类型","slug":"菜单项类型"},{"level":2,"title":"菜单模块","slug":"菜单模块"},{"level":2,"title":"新增菜单","slug":"新增菜单"},{"level":2,"title":"菜单排序","slug":"菜单排序"}],"relativePath":"guide/menu.md","lastUpdated":1697592578593}',p={},o=a('

菜单

项目菜单配置存放于 src/router/menus 下面

提示

菜单必须和路由匹配才能显示

菜单项类型

export interface Menu {\n  //  菜单名\n  name: string;\n  // 菜单图标,如果没有,则会尝试使用route.meta.icon\n  icon?: string;\n  // 菜单图片,如果同时传递了icon和img,则只会显示img\n  img?: string;\n  // 菜单路径\n  path: string;\n  // 是否禁用\n  disabled?: boolean;\n  // 子菜单\n  children?: Menu[];\n  // 菜单标签设置\n  tag: {\n    // 为true则显示小圆点\n    dot: boolean;\n    // 内容\n    content: string';\n    // 类型\n    type: 'error' | 'primary' | 'warn' | 'success';\n  };\n  // 是否隐藏菜单\n  hideMenu?: boolean;\n}\n

菜单模块

一个菜单文件会被当作一个模块

提示

children 的 path 字段不需要以/开头

import type { MenuModule } from '/@/router/types';\nimport { t } from '/@/hooks/web/useI18n';\nconst menu: MenuModule = {\n  orderNo: 10,\n  menu: {\n    name: t('routes.dashboard.dashboard'),\n    path: '/dashboard',\n\n    children: [\n      {\n        path: 'analysis',\n        name: t('routes.dashboard.analysis'),\n      },\n      {\n        path: 'workbench',\n        name: t('routes.dashboard.workbench'),\n      },\n    ],\n  },\n};\nexport default menu;\n

以上模块会转化成以下结构

[\n  path: '/dashboard',\n  name: t('routes.dashboard.dashboard'),\n  children: [\n    {\n      path: 'dashboard/analysis',\n      name: t('routes.dashboard.analysis'),\n    },\n    {\n      path: 'dashboard/workbench',\n      name: t('routes.dashboard.workbench'),\n    },\n  ],\n]\n

新增菜单

直接在 src/router/routes/modules 内新增一个模块文件即可。

不需要手动引入,放在src/router/routes/modules 内的文件会自动被加载。

菜单排序

在菜单模块内,设置 orderNo 变量,数值越大,排序越靠后

',16);p.render=function(a,t,p,e,c,r){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/guide_menu.md.3fefed66.lean.js b/assets/guide_menu.md.3fefed66.lean.js new file mode 100644 index 00000000..a4f79dd5 --- /dev/null +++ b/assets/guide_menu.md.3fefed66.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"菜单","description":"","frontmatter":{},"headers":[{"level":2,"title":"菜单项类型","slug":"菜单项类型"},{"level":2,"title":"菜单模块","slug":"菜单模块"},{"level":2,"title":"新增菜单","slug":"新增菜单"},{"level":2,"title":"菜单排序","slug":"菜单排序"}],"relativePath":"guide/menu.md","lastUpdated":1697592578593}',p={},o=a('',16);p.render=function(a,t,p,e,c,r){return n(),s("div",null,[o])};export default p;export{t as __pageData}; diff --git a/assets/guide_mock.md.4205a506.js b/assets/guide_mock.md.4205a506.js new file mode 100644 index 00000000..ffe28bfd --- /dev/null +++ b/assets/guide_mock.md.4205a506.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"数据 mock&联调","description":"","frontmatter":{},"headers":[{"level":2,"title":"开发环境","slug":"开发环境"},{"level":3,"title":"配置","slug":"配置"},{"level":3,"title":"跨域处理","slug":"跨域处理"},{"level":3,"title":"没有跨域时的配置","slug":"没有跨域时的配置"},{"level":3,"title":"跨域原理解析","slug":"跨域原理解析"},{"level":2,"title":"生产环境","slug":"生产环境"},{"level":2,"title":"接口请求","slug":"接口请求"},{"level":2,"title":"axios 配置","slug":"axios-配置"},{"level":3,"title":"index.ts 配置说明","slug":"index-ts-配置说明"},{"level":3,"title":"更改参数格式","slug":"更改参数格式"},{"level":3,"title":"多个接口地址","slug":"多个接口地址"},{"level":3,"title":"删除请求 URL 携带的时间戳参数","slug":"删除请求-url-携带的时间戳参数"},{"level":2,"title":"Mock 服务","slug":"mock-服务"},{"level":3,"title":"本地 Mock","slug":"本地-mock"},{"level":3,"title":"线上 mock","slug":"线上-mock"}],"relativePath":"guide/mock.md","lastUpdated":1697592578593}',p={},o=a('

数据 mock&联调

开发环境

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

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

配置

开发环境时候,接口地址在项目根目录下

.env.development 文件配置

# vite 本地跨域代理\nVITE_PROXY=[["/basic-api","http://localhost:3000"]]\n# 接口地址\nVITE_GLOB_API_URL=/api\n

TIP

  • .env 文件中的字段如果是字符串,则无需加引号,默认全部为字符串
  • VITE_PROXY 不能换行

TIP

v3.0.0开始,作者重构了vite.config.ts,新版本不再支持VITE_PROXY环境变量。

跨域处理

如果你在 src/api/ 下面的接口为下方代码,且 .env.development 文件配置如下注释,则在控制台看到的地址为 http://localhost:3100/basic-api/login

由于 /basic-api 匹配到了设置的 VITE_PROXY,所以上方实际是请求 http://localhost:3000/login,这样同时也解决了跨域问题。(3100为项目端口号,http://localhost:3000为PROXY代理的目标地址)

// .env.development\n// VITE_PROXY=[["/basic-api","http://localhost:3000"]]\n// VITE_GLOB_API_URL=/basic-api\n\nenum Api {\n  Login = '/login',\n}\n\n/**\n * @description: 用户登陆\n */\nexport function loginApi(params: LoginParams) {\n  return http.request<LoginResultModel>({\n    url: Api.Login,\n    method: 'POST',\n    params,\n  });\n}\n

没有跨域时的配置

如果没有跨域问题,可以直接忽略 VITE_PROXY 配置,直接将接口地址设置在 VITE_GLOB_API_URL

# 例如接口地址为 http://localhost:3000 则\nVITE_GLOB_API_URL=http://localhost:3000\n

如果有跨域问题,将 VITE_GLOB_API_URL 设置为跟 VITE_PROXY 内其中一个数组的第一个项一致的值即可。

下方的接口地址设置为 /basic-api,当请求发出的时候会经过 Vite 的 proxy 代理,匹配到了我们设置的 VITE_PROXY 规则,将 /basic-api 转化为 http://localhost:3000 进行请求

# 例如接口地址为 http://localhost:3000 则\nVITE_PROXY=[["/basic-api","http://localhost:3000"]]\n# 接口地址\nVITE_GLOB_API_URL=/basic-api\n

跨域原理解析

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

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

注意

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

生产环境

生产环境接口地址在项目根目录下 .env.production 文件配置。

生产环境接口地址值需要修改 VITE_GLOB_API_URL,如果出现跨域问题,可以使用 nginx 或者后台开启 cors 进行处理

打包后如何进行地址修改?

VITE_GLOB_* 开头的变量会在打包的时候注入 _app.config.js 文件内。

dist/_app.config.js 修改相应的接口地址后刷新页面即可,不需要在根据不同环境打包多次,一次打包可以用于多个不同接口环境的部署。

接口请求

在 vue-vben-admin 中:

  1. 页面交互操作;
  2. 调用统一管理的 api 请求函数;
  3. 使用封装的 axios.ts 发送请求;
  4. 获取服务端返回数据
  5. 更新 data;

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

以登陆接口为例:

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

TIP

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

import { defHttp } from '/@/utils/http/axios';\nimport { LoginParams, LoginResultModel } from './model/userModel';\n\nenum Api {\n  Login = '/login',\n}\n\nexport function loginApi(params: LoginParams) {\n  return defHttp.request<LoginResultModel>({\n    url: Api.Login,\n    method: 'POST',\n    params,\n  });\n}\n

axios 配置

axios 请求封装存放于 src/utils/http/axios 文件夹内部

index.ts 文件内容需要根据项目自行修改外,其余文件无需修改

\n├── Axios.ts // axios实例\n├── axiosCancel.ts // axiosCancel实例,取消重复请求\n├── axiosTransform.ts // 数据转换类\n├── checkStatus.ts // 返回状态值校验\n├── index.ts // 接口返回统一处理\n\n

index.ts 配置说明

const axios = new VAxios({\n  // 认证方案,例如: Bearer\n  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes\n  authenticationScheme: '',\n  // 接口超时时间 单位毫秒\n  timeout: 10 * 1000,\n  // 接口可能会有通用的地址部分,可以统一抽取出来\n  prefixUrl: prefix,\n  headers: { 'Content-Type': ContentTypeEnum.JSON },\n  // 数据处理方式,见下方说明\n  transform,\n  // 配置项,下面的选项都可以在独立的接口请求中覆盖\n  requestOptions: {\n    // 默认将prefix 添加到url\n    joinPrefix: true,\n    // 是否返回原生响应头 比如:需要获取响应头时使用该属性\n    isReturnNativeResponse: false,\n    // 需要对返回数据进行处理\n    isTransformRequestResult: true,\n    // post请求的时候添加参数到url\n    joinParamsToUrl: false,\n    // 格式化提交参数时间\n    formatDate: true,\n    // 消息提示类型\n    errorMessageMode: 'message',\n    // 接口地址\n    apiUrl: globSetting.apiUrl,\n    //  是否加入时间戳\n    joinTime: true,\n    // 忽略重复请求\n    ignoreCancelToken: true,\n  },\n});\n

transform 数据处理说明

类型定义,见 axiosTransform.ts 文件

export abstract class AxiosTransform {\n  /**\n   * @description: 请求之前处理配置\n   */\n  beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig;\n\n  /**\n   * @description: 请求成功处理\n   */\n  transformRequestData?: (res: AxiosResponse<Result>, options: RequestOptions) => any;\n\n  /**\n   * @description: 请求失败处理\n   */\n  requestCatch?: (e: Error) => Promise<any>;\n\n  /**\n   * @description: 请求之前的拦截器\n   */\n  requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig;\n\n  /**\n   * @description: 请求之后的拦截器\n   */\n  responseInterceptors?: (res: AxiosResponse<any>) => AxiosResponse<any>;\n\n  /**\n   * @description: 请求之前的拦截器错误处理\n   */\n  requestInterceptorsCatch?: (error: Error) => void;\n\n  /**\n   * @description: 请求之后的拦截器错误处理\n   */\n  responseInterceptorsCatch?: (error: Error) => void;\n}\n\n\n

项目默认 transform 处理逻辑,可以根据各自项目进行处理。一般需要更改的部分为下方代码,见代码注释说明

/**\n * @description: 数据处理,方便区分多种处理方式\n */\nconst transform: AxiosTransform = {\n  /**\n   * @description: 处理请求数据。如果数据不是预期格式,可直接抛出错误\n   */\n  transformRequestHook: (res: AxiosResponse<Result>, options: RequestOptions) => {\n    const { t } = useI18n();\n    const { isTransformResponse, isReturnNativeResponse } = options;\n    // 是否返回原生响应头 比如:需要获取响应头时使用该属性\n    if (isReturnNativeResponse) {\n      return res;\n    }\n    // 不进行任何处理,直接返回\n    // 用于页面代码可能需要直接获取code,data,message这些信息时开启\n    if (!isTransformResponse) {\n      return res.data;\n    }\n    // 错误的时候返回\n\n    const { data } = res;\n    if (!data) {\n      // return '[HTTP] Request has no return value';\n      throw new Error(t('sys.api.apiRequestFailed'));\n    }\n    //  这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式\n    const { code, result, message } = data;\n\n    // 这里逻辑可以根据项目进行修改\n    const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS;\n    if (hasSuccess) {\n      return result;\n    }\n\n    // 在此处根据自己项目的实际情况对不同的code执行不同的操作\n    // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可\n    let timeoutMsg = '';\n    switch (code) {\n      case ResultEnum.TIMEOUT:\n        timeoutMsg = t('sys.api.timeoutMessage');\n      default:\n        if (message) {\n          timeoutMsg = message;\n        }\n    }\n\n    // errorMessageMode=‘modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误\n    // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示\n    if (options.errorMessageMode === 'modal') {\n      createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg });\n    } else if (options.errorMessageMode === 'message') {\n      createMessage.error(timeoutMsg);\n    }\n\n    throw new Error(timeoutMsg || t('sys.api.apiRequestFailed'));\n  },\n\n  // 请求之前处理config\n  beforeRequestHook: (config, options) => {\n    const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true } = options;\n\n    if (joinPrefix) {\n      config.url = `${urlPrefix}${config.url}`;\n    }\n\n    if (apiUrl && isString(apiUrl)) {\n      config.url = `${apiUrl}${config.url}`;\n    }\n    const params = config.params || {};\n    if (config.method?.toUpperCase() === RequestEnum.GET) {\n      if (!isString(params)) {\n        // 给 get 请求加上时间戳参数,避免从缓存中拿数据。\n        config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));\n      } else {\n        // 兼容restful风格\n        config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;\n        config.params = undefined;\n      }\n    } else {\n      if (!isString(params)) {\n        formatDate && formatRequestDate(params);\n        config.data = params;\n        config.params = undefined;\n        if (joinParamsToUrl) {\n          config.url = setObjToUrlParams(config.url as string, config.data);\n        }\n      } else {\n        // 兼容restful风格\n        config.url = config.url + params;\n        config.params = undefined;\n      }\n    }\n    return config;\n  },\n\n  /**\n   * @description: 请求拦截器处理\n   */\n  requestInterceptors: (config, options) => {\n    // 请求之前处理config\n    const token = getToken();\n    if (token) {\n      // jwt token\n      config.headers.Authorization = options.authenticationScheme\n        ? `${options.authenticationScheme} ${token}`\n        : token;\n    }\n    return config;\n  },\n\n  /**\n   * @description: 响应拦截器处理\n   */\n  responseInterceptors: (res: AxiosResponse<any>) => {\n    return res;\n  },\n\n  /**\n   * @description: 响应错误处理\n   */\n  responseInterceptorsCatch: (error: any) => {\n    const { t } = useI18n();\n    const errorLogStore = useErrorLogStoreWithOut();\n    errorLogStore.addAjaxErrorInfo(error);\n    const { response, code, message, config } = error || {};\n    const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none';\n    const msg: string = response?.data?.error?.message ?? '';\n    const err: string = error?.toString?.() ?? '';\n    let errMessage = '';\n\n    try {\n      if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {\n        errMessage = t('sys.api.apiTimeoutMessage');\n      }\n      if (err?.includes('Network Error')) {\n        errMessage = t('sys.api.networkExceptionMsg');\n      }\n\n      if (errMessage) {\n        if (errorMessageMode === 'modal') {\n          createErrorModal({ title: t('sys.api.errorTip'), content: errMessage });\n        } else if (errorMessageMode === 'message') {\n          createMessage.error(errMessage);\n        }\n        return Promise.reject(error);\n      }\n    } catch (error) {\n      throw new Error(error);\n    }\n\n    checkStatus(error?.response?.status, msg, errorMessageMode);\n    return Promise.reject(error);\n  },\n};\n

更改参数格式

项目接口默认为 Json 参数格式,即 headers: { 'Content-Type': ContentTypeEnum.JSON },

如果需要更改为 form-data 格式,更改 headers 的 'Content-TypeContentTypeEnum.FORM_URLENCODED 即可

多个接口地址

当项目中需要用到多个接口地址时, 可以在 src/utils/http/axios/index.ts 导出多个 axios 实例

// 目前只导出一个默认实例,接口地址对应的是环境变量中的 VITE_GLOB_API_URL 接口地址\nexport const defHttp = createAxios();\n\n// 需要有其他接口地址的可以在后面添加\n\n// other api url\nexport const otherHttp = createAxios({\n  requestOptions: {\n    apiUrl: 'xxx',\n  },\n});\n

删除请求 URL 携带的时间戳参数

如果不需要 url 上面默认携带的时间戳参数 ?_t=xxx

const axios = new VAxios({\n  requestOptions: {\n    // 是否加入时间戳\n    joinTime: false,\n  },\n});\n

Mock 服务

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

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

本地 Mock

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

如何新增 mock 接口

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

在 mock 文件夹内新建文件

TIP

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

例:

import { MockMethod } from 'vite-plugin-mock';\nimport { resultPageSuccess } from '../_util';\n\nconst demoList = (() => {\n  const result: any[] = [];\n  for (let index = 0; index < 60; index++) {\n    result.push({\n      id: `${index}`,\n      beginTime: '@datetime',\n      endTime: '@datetime',\n      address: '@city()',\n      name: '@cname()',\n      'no|100000-10000000': 100000,\n      'status|1': ['正常', '启用', '停用'],\n    });\n  }\n  return result;\n})();\n\nexport default [\n  {\n    url: '/api/table/getDemoList',\n    timeout: 1000,\n    method: 'get',\n    response: ({ query }) => {\n      const { page = 1, pageSize = 20 } = query;\n      return resultPageSuccess(page, pageSize, demoList);\n    },\n  },\n] as MockMethod[];\n

TIP

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

接口格式

{\n  url: string; // mock 接口地址\n  method?: MethodType; // 请求方式\n  timeout?: number; // 延时时间\n  statusCode: number; // 响应状态码\n  response: ((opt: { // 响应结果\n      body: any;\n      query: any;\n  }) => any) | object;\n}\n

参数获取

GET 接口: ({ query }) => { }

POST 接口: ({ body }) => { }

util 说明

可在 代码 中查看

TIP

util 只作为服务处理结果数据使用。可以不用,如需使用可自行封装,需要将对应的字段改为接口的返回结构

匹配

src/api 下面,如果接口匹配到 mock,则会优先使用 mock 进行响应

import { defHttp } from '/@/utils/http/axios';\nimport { LoginParams, LoginResultModel } from './model/userModel';\n\nenum Api {\n  Login = '/login',\n}\n\n/**\n * @description: user login api\n */\nexport function loginApi(params: LoginParams) {\n  return defHttp.request<LoginResultModel>(\n    {\n      url: Api.Login,\n      method: 'POST',\n      params,\n    },\n    {\n      errorMessageMode: 'modal',\n    }\n  );\n}\n// 会匹配到上方的\nexport default [\n  {\n    url: '/api/login',\n    timeout: 1000,\n    method: 'POST',\n    response: ({ body }) => {\n      return resultPageSuccess({});\n    },\n  },\n] as MockMethod[];\n

接口有了,如何去掉 mock

当后台接口已经开发完成,只需要将相应的 mock 函数去掉即可。

以上方接口为例,假如后台接口 login 已经开发完成,则只需要删除/注释掉下方代码即可

export default [\n  {\n    url: '/api/login',\n    timeout: 1000,\n    method: 'POST',\n    response: ({ body }) => {\n      return resultPageSuccess({});\n    },\n  },\n] as MockMethod[];\n

线上 mock

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

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

线上如何开启 mock

注意

线上开启 mock 只适用于一些简单的示例网站及预览网站。一定不要在正式的生产环境开启!!!

  1. 修改 .env.production 文件内的 VITE_USE_MOCK 的值为 true
VITE_USE_MOCK = true;\n
  1. mock/_createProductionServer.ts 文件中引入需要的 mock 文件
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';\n\nconst modules = import.meta.globEager('./**/*.ts');\n\nconst mockModules: any[] = [];\nObject.keys(modules).forEach((key) => {\n  if (key.includes('/_')) {\n    return;\n  }\n  mockModules.push(...modules[key].default);\n});\n\nexport function setupProdMockServer() {\n  createProdMockServer(mockModules);\n}\n
  1. build/vite/plugin/mock.ts 里面引入
import { viteMockServe } from 'vite-plugin-mock';\n\nexport function configMockPlugin(isBuild: boolean) {\n  return viteMockServe({\n    injectCode: `\n      import { setupProdMockServer } from '../mock/_createProductionServer';\n\n      setupProdMockServer();\n      `,\n  });\n}\n

为什么通过插件注入代码而不是直接在 main.ts 内插入

在插件内通过 injectCode 插入代码,方便控制 mockjs 是否被打包到最终代码内。如果在 main.ts 内判断,如果关闭了 mock 功能,mockjs 也会打包到构建文件内,这样会增加打包体积。

到这里线上 mock 就配置完成了。线上与本地差异不大,比较大的区别是线上在控制台内看不到接口请求日志。

',96);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_mock.md.4205a506.lean.js b/assets/guide_mock.md.4205a506.lean.js new file mode 100644 index 00000000..b6bea3a0 --- /dev/null +++ b/assets/guide_mock.md.4205a506.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"数据 mock&联调","description":"","frontmatter":{},"headers":[{"level":2,"title":"开发环境","slug":"开发环境"},{"level":3,"title":"配置","slug":"配置"},{"level":3,"title":"跨域处理","slug":"跨域处理"},{"level":3,"title":"没有跨域时的配置","slug":"没有跨域时的配置"},{"level":3,"title":"跨域原理解析","slug":"跨域原理解析"},{"level":2,"title":"生产环境","slug":"生产环境"},{"level":2,"title":"接口请求","slug":"接口请求"},{"level":2,"title":"axios 配置","slug":"axios-配置"},{"level":3,"title":"index.ts 配置说明","slug":"index-ts-配置说明"},{"level":3,"title":"更改参数格式","slug":"更改参数格式"},{"level":3,"title":"多个接口地址","slug":"多个接口地址"},{"level":3,"title":"删除请求 URL 携带的时间戳参数","slug":"删除请求-url-携带的时间戳参数"},{"level":2,"title":"Mock 服务","slug":"mock-服务"},{"level":3,"title":"本地 Mock","slug":"本地-mock"},{"level":3,"title":"线上 mock","slug":"线上-mock"}],"relativePath":"guide/mock.md","lastUpdated":1697592578593}',p={},o=a('',96);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.31ce5cd8.js b/assets/guide_router.md.31ce5cd8.js new file mode 100644 index 00000000..4e4c2d84 --- /dev/null +++ b/assets/guide_router.md.31ce5cd8.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"路由","description":"","frontmatter":{},"headers":[{"level":2,"title":"配置","slug":"配置"},{"level":3,"title":"模块说明","slug":"模块说明"},{"level":3,"title":"多级路由","slug":"多级路由"},{"level":3,"title":"Meta 配置说明","slug":"meta-配置说明"},{"level":3,"title":"外部页面嵌套","slug":"外部页面嵌套"},{"level":3,"title":"外链","slug":"外链"},{"level":3,"title":"动态路由Tab自动关闭功能","slug":"动态路由tab自动关闭功能"},{"level":2,"title":"图标","slug":"图标"},{"level":2,"title":"新增路由","slug":"新增路由"},{"level":3,"title":"如何新增一个路由模块","slug":"如何新增一个路由模块"},{"level":3,"title":"验证","slug":"验证"},{"level":2,"title":"路由刷新","slug":"路由刷新"},{"level":3,"title":"实现","slug":"实现"},{"level":3,"title":"Redirect","slug":"redirect"},{"level":2,"title":"页面跳转","slug":"页面跳转"},{"level":3,"title":"方式","slug":"方式"},{"level":2,"title":"多标签页","slug":"多标签页"},{"level":3,"title":"如何开启页面缓存","slug":"如何开启页面缓存"},{"level":3,"title":"如何让某个页面不缓存","slug":"如何让某个页面不缓存"},{"level":2,"title":"如何更改首页路由","slug":"如何更改首页路由"}],"relativePath":"guide/router.md","lastUpdated":1697592578593}',p={},o=a('

路由

项目路由配置存放于 src/router/routes 下面。 src/router/routes/modules用于存放路由模块,在该目录下的文件会自动注册。

配置

模块说明

src/router/routes/modules 内的 .ts 文件会被视为一个路由模块。

一个路由模块包含以下结构

import type { AppRouteModule } from '/@/router/types';\n\nimport { LAYOUT } from '/@/router/constant';\nimport { t } from '/@/hooks/web/useI18n';\n\nconst dashboard: AppRouteModule = {\n  path: '/dashboard',\n  name: 'Dashboard',\n  component: LAYOUT,\n  redirect: '/dashboard/analysis',\n  meta: {\n    icon: 'ion:grid-outline',\n    title: t('routes.dashboard.dashboard'),\n  },\n  children: [\n    {\n      path: 'analysis',\n      name: 'Analysis',\n      component: () => import('/@/views/dashboard/analysis/index.vue'),\n      meta: {\n        affix: true,\n        title: t('routes.dashboard.analysis'),\n      },\n    },\n    {\n      path: 'workbench',\n      name: 'Workbench',\n      component: () => import('/@/views/dashboard/workbench/index.vue'),\n      meta: {\n        title: t('routes.dashboard.workbench'),\n      },\n    },\n  ],\n};\nexport default dashboard;\n

多级路由

注意事项

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

示例

import type { AppRouteModule } from '/@/router/types';\nimport { getParentLayout, LAYOUT } from '/@/router/constant';\nimport { t } from '/@/hooks/web/useI18n';\nconst permission: AppRouteModule = {\n  path: '/level',\n  name: 'Level',\n  component: LAYOUT,\n  redirect: '/level/menu1/menu1-1/menu1-1-1',\n  meta: {\n    icon: 'ion:menu-outline',\n    title: t('routes.demo.level.level'),\n  },\n\n  children: [\n    {\n      path: 'tabs/:id', \n      name: 'TabsParams',\n      component: getParentLayout('TabsParams'),\n      meta: {\n        carryParam: true,\n        hidePathForChildren: true, // 本级path将会在子级菜单中合成完整path时会忽略这一层级\n      },\n      children: [\n        path: 'tabs/id1', // 其上级有标记hidePathForChildren,所以本级在生成菜单时最终的path为  /level/tabs/id1\n        name: 'TabsParams',\n        component: getParentLayout('TabsParams'),\n        meta: {\n          carryParam: true,\n          ignoreRoute: true,  // 本路由仅用于菜单生成,不会在实际的路由表中出现\n        },\n      ]\n    },\n    {\n      path: 'menu1',\n      name: 'Menu1Demo',\n      component: getParentLayout('Menu1Demo'),\n      meta: {\n        title: 'Menu1',\n      },\n      redirect: '/level/menu1/menu1-1/menu1-1-1',\n      children: [\n        {\n          path: 'menu1-1',\n          name: 'Menu11Demo',\n          component: getParentLayout('Menu11Demo'),\n          meta: {\n            title: 'Menu1-1',\n          },\n          redirect: '/level/menu1/menu1-1/menu1-1-1',\n          children: [\n            {\n              path: 'menu1-1-1',\n              name: 'Menu111Demo',\n              component: () => import('/@/views/demo/level/Menu111.vue'),\n              meta: {\n                title: 'Menu111',\n              },\n            },\n          ],\n        },\n      ],\n    },\n  ],\n};\n\nexport default permission;\n

Meta 配置说明

export interface RouteMeta {\n  // 路由title  一般必填\n  title: string;\n  // 动态路由可打开Tab页数\n  dynamicLevel?: number;\n  // 动态路由的实际Path, 即去除路由的动态部分;\n  realPath?: string;\n  // 是否忽略权限,只在权限模式为Role的时候有效\n  ignoreAuth?: boolean;\n  // 可以访问的角色,只在权限模式为Role的时候有效\n  roles?: RoleEnum[];\n  // 是否忽略KeepAlive缓存\n  ignoreKeepAlive?: boolean;\n  // 是否固定标签\n  affix?: boolean;\n  // 图标,也是菜单图标\n  icon?: string;\n  // 内嵌iframe的地址\n  frameSrc?: string;\n  // 指定该路由切换的动画名\n  transitionName?: string;\n  // 隐藏该路由在面包屑上面的显示\n  hideBreadcrumb?: boolean;\n  // 如果该路由会携带参数,且需要在tab页上面显示。则需要设置为true\n  carryParam?: boolean;\n  // 隐藏所有子菜单\n  hideChildrenInMenu?: boolean;\n  // 当前激活的菜单。用于配置详情页时左侧激活的菜单路径\n  currentActiveMenu?: string;\n  // 当前路由不再标签页显示\n  hideTab?: boolean;\n  // 当前路由不再菜单显示\n  hideMenu?: boolean;\n  // 菜单排序,只对第一级有效\n  orderNo?: number;\n  // 忽略路由。用于在ROUTE_MAPPING以及BACK权限模式下,生成对应的菜单而忽略路由。2.5.3以上版本有效\n  ignoreRoute?: boolean;\n  // 是否在子级菜单的完整path中忽略本级path。2.5.3以上版本有效\n  hidePathForChildren?: boolean;\n}\n

外部页面嵌套

只需要将 frameSrc 设置为需要跳转的地址即可

const IFrame = () => import('/@/views/sys/iframe/FrameBlank.vue');\n{\n  path: 'doc',\n  name: 'Doc',\n  component: IFrame,\n  meta: {\n    frameSrc: 'https://vvbin.cn/doc-next/',\n    title: t('routes.demo.iframe.doc'),\n  },\n},\n

外链

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

{\n  path: 'https://vvbin.cn/doc-next/',\n  name: 'DocExternal',\n  component: IFrame,\n  meta: {\n    title: t('routes.demo.iframe.docExternal'),\n  },\n}\n

动态路由Tab自动关闭功能

若需要开启该功能,需要在动态路由的meta中设置如下两个参数:

  • dynamicLevel 最大能打开的Tab标签页数
  • realPath 动态路由实际路径(考虑到动态路由有时候可能存在N层的情况, 例:/:id/:subId/:...), 为了减少计算开销, 使用配置方式事先规定好路由的实际路径(注意: 该参数若不设置,将无法使用该功能)
{\n  path: 'detail/:id',\n  name: 'TabDetail',\n  component: () => import('/@/views/demo/feat/tabs/TabDetail.vue'),\n  meta: {\n    currentActiveMenu: '/feat/tabs',\n    title: t('routes.demo.feat.tabDetail'),\n    hideMenu: true,\n    dynamicLevel: 3,\n    realPath: '/feat/tabs/detail',\n  },\n}\n

图标

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

新增路由

如何新增一个路由模块

  1. src/router/routes/modules 内新增一个模块文件。

示例,新增 test.ts 文件

import type { AppRouteModule } from '/@/router/types';\nimport { LAYOUT } from '/@/router/constant';\nimport { t } from '/@/hooks/web/useI18n';\n\nconst dashboard: AppRouteModule = {\n  path: '/about',\n  name: 'About',\n  component: LAYOUT,\n  redirect: '/about/index',\n  meta: {\n    icon: 'simple-icons:about-dot-me',\n    title: t('routes.dashboard.about'),\n  },\n  children: [\n    {\n      path: 'index',\n      name: 'AboutPage',\n      component: () => import('/@/views/sys/about/index.vue'),\n      meta: {\n        title: t('routes.dashboard.about'),\n        icon: 'simple-icons:about-dot-me',\n      },\n    },\n  ],\n};\n\nexport default dashboard;\n

此时路由已添加完成,不需要手动引入,放在src/router/routes/modules 内的文件会自动被加载。

验证

访问 ip:端口/about/index 出现对应组件内容即代表成功

路由刷新

项目中采用的是重定向方式

实现

import { useRedo } from '/@/hooks/web/usePage';\nimport { defineComponent } from 'vue';\nexport default defineComponent({\n  setup() {\n    const redo = useRedo();\n    // 执行刷新\n    redo();\n    return {};\n  },\n});\n

Redirect

src/views/sys/redirect/index.vue

import { defineComponent, unref } from 'vue';\nimport { useRouter } from 'vue-router';\nexport default defineComponent({\n  name: 'Redirect',\n  setup() {\n    const { currentRoute, replace } = useRouter();\n    const { params, query } = unref(currentRoute);\n    const { path } = params;\n    const _path = Array.isArray(path) ? path.join('/') : path;\n    replace({\n      path: '/' + _path,\n      query,\n    });\n    return {};\n  },\n});\n

页面跳转

页面跳转建议采用项目提供的 useGo

方式

import { useGo } from '/@/hooks/web/usePage';\nimport { defineComponent } from 'vue';\nexport default defineComponent({\n  setup() {\n    const go = useGo();\n\n    // 执行刷新\n    go();\n    go(PageEnum.Home);\n    return {};\n  },\n});\n

多标签页

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

如何开启页面缓存

开启缓存有 3 个条件

  1. src/settings/projectSetting.ts 内将openKeepAlive 设置为 true
  2. 路由设置 name,且不能重复
  3. 路由对应的组件加上 name,与路由设置的 name 保持一致
 {\n   ...,\n    // name\n    name: 'Login',\n    // 对应组件组件的name\n    component: () => import('/@/views/sys/login/index.vue'),\n    ...\n  },\n\n  // /@/views/sys/login/index.vue\n  export default defineComponent({\n    // 需要和路由的name一致\n    name:"Login"\n  });\n

注意

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

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

如何让某个页面不缓存

可在 router.meta 下配置

可以将 ignoreKeepAlive 配置成 true 即可关闭缓存。

export interface RouteMeta {\n  // 是否忽略KeepAlive缓存\n  ignoreKeepAlive?: boolean;\n}\n

如何更改首页路由

首页路由指的是应用程序中的默认路由,当不输入其他任何路由时,会自动重定向到该路由下,并且该路由在Tab上是固定的,即使设置affix: false也不允许关闭

例:首页路由配置的是/dashboard/analysis,那么当直接访问 http://localhost:3100/ 会自动跳转到http://localhost:3100/#/dashboard/analysis 上(用户已登录的情况下)

可以将pageEnum.ts中的BASE_HOME更改为需要你想设置的首页即可

export enum PageEnum {\n    // basic home path\n    // 更改此处即可\n    BASE_HOME = '/dashboard',\n}\n\n
',60);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.31ce5cd8.lean.js b/assets/guide_router.md.31ce5cd8.lean.js new file mode 100644 index 00000000..5dc82807 --- /dev/null +++ b/assets/guide_router.md.31ce5cd8.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"路由","description":"","frontmatter":{},"headers":[{"level":2,"title":"配置","slug":"配置"},{"level":3,"title":"模块说明","slug":"模块说明"},{"level":3,"title":"多级路由","slug":"多级路由"},{"level":3,"title":"Meta 配置说明","slug":"meta-配置说明"},{"level":3,"title":"外部页面嵌套","slug":"外部页面嵌套"},{"level":3,"title":"外链","slug":"外链"},{"level":3,"title":"动态路由Tab自动关闭功能","slug":"动态路由tab自动关闭功能"},{"level":2,"title":"图标","slug":"图标"},{"level":2,"title":"新增路由","slug":"新增路由"},{"level":3,"title":"如何新增一个路由模块","slug":"如何新增一个路由模块"},{"level":3,"title":"验证","slug":"验证"},{"level":2,"title":"路由刷新","slug":"路由刷新"},{"level":3,"title":"实现","slug":"实现"},{"level":3,"title":"Redirect","slug":"redirect"},{"level":2,"title":"页面跳转","slug":"页面跳转"},{"level":3,"title":"方式","slug":"方式"},{"level":2,"title":"多标签页","slug":"多标签页"},{"level":3,"title":"如何开启页面缓存","slug":"如何开启页面缓存"},{"level":3,"title":"如何让某个页面不缓存","slug":"如何让某个页面不缓存"},{"level":2,"title":"如何更改首页路由","slug":"如何更改首页路由"}],"relativePath":"guide/router.md","lastUpdated":1697592578593}',p={},o=a('',60);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.560c3ce7.js b/assets/guide_settings.md.560c3ce7.js new file mode 100644 index 00000000..810a21da --- /dev/null +++ b/assets/guide_settings.md.560c3ce7.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"项目配置项","description":"","frontmatter":{},"headers":[{"level":2,"title":"环境变量配置","slug":"环境变量配置"},{"level":3,"title":"配置项说明","slug":"配置项说明"},{"level":3,"title":".env","slug":"env"},{"level":3,"title":".env.development","slug":"env-development"},{"level":3,"title":".env.production","slug":"env-production"},{"level":2,"title":"生产环境动态配置","slug":"生产环境动态配置"},{"level":3,"title":"说明","slug":"说明"},{"level":3,"title":"作用","slug":"作用"},{"level":3,"title":"如何获取全局变量","slug":"如何获取全局变量"},{"level":3,"title":"如何新增(新增一个可动态修改的配置项)","slug":"如何新增-新增一个可动态修改的配置项"},{"level":2,"title":"项目配置","slug":"项目配置"},{"level":3,"title":"配置文件路径","slug":"配置文件路径"},{"level":3,"title":"说明","slug":"说明-1"},{"level":2,"title":"缓存配置","slug":"缓存配置"},{"level":2,"title":"多语言配置","slug":"多语言配置"},{"level":2,"title":"主题色配置","slug":"主题色配置"},{"level":2,"title":"样式配置","slug":"样式配置"},{"level":3,"title":"css 前缀设置","slug":"css-前缀设置"},{"level":3,"title":"前缀使用","slug":"前缀使用"},{"level":2,"title":"颜色配置","slug":"颜色配置"},{"level":2,"title":"组件默认参数配置","slug":"组件默认参数配置"}],"relativePath":"guide/settings.md","lastUpdated":1697592578593}',p={},o=a('

项目配置项

用于修改项目的配色、布局、缓存、多语言、组件默认配置

环境变量配置

项目的环境变量配置位于项目根目录下的 .env.env.development.env.production

具体可以参考 Vite 文档

.env                # 在所有的环境中被载入\n.env.local          # 在所有的环境中被载入,但会被 git 忽略\n.env.[mode]         # 只在指定的模式中被载入\n.env.[mode].local   # 只在指定的模式中被载入,但会被 git 忽略\n\n

温馨提醒

  • 只有以 VITE_ 开头的变量会被嵌入到客户端侧的包中,你可以在项目代码中这样访问它们:
console.log(import.meta.env.VITE_PROT);\n
  • VITE_GLOB_* 开头的的变量,在打包的时候,会被加入_app.config.js配置文件当中.

配置项说明

.env

所有环境适用

# 端口号\nVITE_PORT=3100\n# 网站标题\nVITE_GLOB_APP_TITLE=vben admin\n# 简称,用于配置文件名字 不要出现空格、数字开头等特殊字符\nVITE_GLOB_APP_SHORT_NAME=vben_admin\n

.env.development

开发环境适用

# 是否开启mock数据,关闭时需要自行对接后台接口\nVITE_USE_MOCK=true\n# 资源公共路径,需要以 /开头和结尾\nVITE_PUBLIC_PATH=/\n# 是否删除Console.log\nVITE_DROP_CONSOLE=false\n# 本地开发代理,可以解决跨域及多地址代理\n# 如果接口地址匹配到,则会转发到http://localhost:3000,防止本地出现跨域问题\n# 可以有多个,注意多个不能换行,否则代理将会失效\nVITE_PROXY=[["/api","http://localhost:3000"],["api1","http://localhost:3001"],["/upload","http://localhost:3001/upload"]]\n\n::: tip\nv3.0.0开始,作者重构了vite.config.ts,新版本不再支持VITE_PROXY环境变量。\n:::\n\n# 接口地址\n# 如果没有跨域问题,直接在这里配置即可\nVITE_GLOB_API_URL=/api\n# 文件上传接口  可选\nVITE_GLOB_UPLOAD_URL=/upload\n# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换\nVITE_GLOB_API_URL_PREFIX=\n

注意

这里配置的 VITE_PROXY 以及 VITE_GLOB_API_URL, /api 需要是唯一的,不要和接口有的名字冲突

如果你的接口是 http://localhost:3000/api 之类的,请考虑将 VITE_GLOB_API_URL=/xxxx 换成别的名字

.env.production

生产环境适用

# 是否开启mock\nVITE_USE_MOCK=true\n# 接口地址 可以由nginx做转发或者直接写实际地址\nVITE_GLOB_API_URL=/api\n# 文件上传地址 可以由nginx做转发或者直接写实际地址\nVITE_GLOB_UPLOAD_URL=/upload\n# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换\nVITE_GLOB_API_URL_PREFIX=\n# 是否删除Console.log\nVITE_DROP_CONSOLE=true\n# 资源公共路径,需要以 / 开头和结尾\nVITE_PUBLIC_PATH=/\n# 打包是否输出gz|br文件\n# 可选: gzip | brotli | none\n# 也可以有多个, 例如 ‘gzip’|'brotli',这样会同时生成 .gz和.br文件\nVITE_BUILD_COMPRESS = 'gzip'\n# 打包是否压缩图片\nVITE_USE_IMAGEMIN = false\n# 打包是否开启pwa功能\nVITE_USE_PWA = false\n# 是否兼容旧版浏览器。开启后打包时间会慢一倍左右。会多打出旧浏览器兼容包,且会根据浏览器兼容性自动使用相应的版本\nVITE_LEGACY = false\n

生产环境动态配置

说明

当执行yarn build构建项目之后,会自动生成 _app.config.js 文件并插入 index.html

注意: 开发环境不会生成

// _app.config.js\n// 变量名命名规则  __PRODUCTION__xxx_CONF__   xxx:为.env配置的VITE_GLOB_APP_SHORT_NAME\nwindow.__PRODUCTION__VUE_VBEN_ADMIN__CONF__ = {\n  VITE_GLOB_APP_TITLE: 'vben admin',\n  VITE_GLOB_APP_SHORT_NAME: 'vue_vben_admin',\n  VITE_GLOB_API_URL: '/app',\n  VITE_GLOB_API_URL_PREFIX: '/',\n  VITE_GLOB_UPLOAD_URL: '/upload',\n};\n

作用

_app.config.js 用于项目在打包后,需要动态修改配置的需求,如接口地址。不用重新进行打包,可在打包后修改 /dist/_app.config.js 内的变量,刷新即可更新代码内的局部变量。

如何获取全局变量

想要获取 _app.config.js 内的变量,可以使用 src/hooks/setting/index.ts 提供的函数来进行获取

如何新增(新增一个可动态修改的配置项)

  1. 首先在 .env 或者对应的开发环境配置文件内,新增需要可动态配置的变量,需要以 VITE_GLOB_开头

  2. VITE_GLOB_ 开头的变量会自动加入环境变量,通过在 types/config.d.ts 内修改 GlobEnvConfigGlobConfig 两个环境变量的值来定义新添加的类型

  3. useGlobSetting 函数中添加刚新增的返回值即可

const {\n  VITE_GLOB_APP_TITLE,\n  VITE_GLOB_API_URL,\n  VITE_GLOB_APP_SHORT_NAME,\n  VITE_GLOB_API_URL_PREFIX,\n  VITE_GLOB_UPLOAD_URL,\n} = ENV;\n\nexport const useGlobSetting = (): SettingWrap => {\n  // Take global configuration\n  const glob: Readonly<GlobConfig> = {\n    title: VITE_GLOB_APP_TITLE,\n    apiUrl: VITE_GLOB_API_URL,\n    shortName: VITE_GLOB_APP_SHORT_NAME,\n    urlPrefix: VITE_GLOB_API_URL_PREFIX,\n    uploadUrl: VITE_GLOB_UPLOAD_URL\n  };\n  return glob as Readonly<GlobConfig>;\n};\n\n

项目配置

WARNING

项目配置文件用于配置项目内展示的内容、布局、文本等效果,存于localStorage中。如果更改了项目配置,需要手动清空 localStorage 缓存,刷新重新登录后方可生效。

配置文件路径

src/settings/projectSetting.ts

说明

// ! 改动后需要清空浏览器缓存\nconst setting: ProjectConfig = {\n  // 是否显示SettingButton\n  showSettingButton: true,\n\n  // 是否显示主题切换按钮\n  showDarkModeToggle: true,\n\n  // 设置按钮位置 可选项\n  // SettingButtonPositionEnum.AUTO: 自动选择\n  // SettingButtonPositionEnum.HEADER: 位于头部\n  // SettingButtonPositionEnum.FIXED: 固定在右侧\n  settingButtonPosition: SettingButtonPositionEnum.AUTO,\n\n  // 权限模式,默认前端角色权限模式\n  // ROUTE_MAPPING: 前端模式(菜单由路由生成,默认)\n  // ROLE:前端模式(菜单路由分开)\n  permissionMode: PermissionModeEnum.ROUTE_MAPPING,\n  // 权限缓存存放位置。默认存放于localStorage\n  permissionCacheType: CacheTypeEnum.LOCAL,\n  // 会话超时处理方案\n  // SessionTimeoutProcessingEnum.ROUTE_JUMP: 路由跳转到登录页\n  // SessionTimeoutProcessingEnum.PAGE_COVERAGE: 生成登录弹窗,覆盖当前页面\n  sessionTimeoutProcessing: SessionTimeoutProcessingEnum.ROUTE_JUMP,\n  // 项目主题色\n  themeColor: primaryColor,\n  // 网站灰色模式,用于可能悼念的日期开启\n  grayMode: false,\n  // 色弱模式\n  colorWeak: false,\n  // 是否取消菜单,顶部,多标签页显示, 用于可能内嵌在别的系统内\n  fullContent: false,\n  // 主题内容宽度\n  contentMode: ContentEnum.FULL,\n  // 是否显示logo\n  showLogo: true,\n  // 是否显示底部信息 copyright\n  showFooter: true,\n  // 头部配置\n  headerSetting: {\n    // 背景色\n    bgColor: '#ffffff',\n    // 固定头部\n    fixed: true,\n    // 是否显示顶部\n    show: true,\n    // 主题\n    theme: MenuThemeEnum.LIGHT,\n    // 开启锁屏功能\n    useLockPage: true,\n    // 显示全屏按钮\n    showFullScreen: true,\n    // 显示文档按钮\n    showDoc: true,\n    // 显示消息中心按钮\n    showNotice: true,\n    // 显示菜单搜索按钮\n    showSearch: true,\n  },\n  // 菜单配置\n  menuSetting: {\n    // 背景色\n    bgColor: '#273352',\n    // 是否固定住菜单\n    fixed: true,\n    // 菜单折叠\n    collapsed: false,\n    // 折叠菜单时候是否显示菜单名\n    collapsedShowTitle: false,\n    // 是否可拖拽\n    canDrag: true,\n    // 是否显示\n    show: true,\n    // 菜单宽度\n    menuWidth: 180,\n    // 菜单模式\n    mode: MenuModeEnum.INLINE,\n    // 菜单类型\n    type: MenuTypeEnum.SIDEBAR,\n    // 菜单主题\n    theme: MenuThemeEnum.DARK,\n    // 分割菜单\n    split: false,\n    // 顶部菜单布局\n    topMenuAlign: 'start',\n    // 折叠触发器的位置\n    trigger: TriggerEnum.HEADER,\n    // 手风琴模式,只展示一个菜单\n    accordion: true,\n    // 在路由切换的时候关闭左侧混合菜单展开菜单\n    closeMixSidebarOnChange: false,\n    // 左侧混合菜单模块切换触发方式\n    mixSideTrigger: MixSidebarTriggerEnum.CLICK,\n    // 是否固定左侧混合菜单\n    mixSideFixed: false,\n  },\n  // 多标签\n  multiTabsSetting: {\n    // 刷新后是否保留已经打开的标签页\n    cache: false,\n    // 开启\n    show: true,\n    // 开启快速操作\n    showQuick: true,\n    // 是否可以拖拽\n    canDrag: true,\n    // 是否显示刷新按钮\n    showRedo: true,\n    // 是否显示折叠按钮\n    showFold: true,\n  },\n\n  // 动画配置\n  transitionSetting: {\n    //  是否开启切换动画\n    enable: true,\n    // 动画名\n    basicTransition: RouterTransitionEnum.FADE_SIDE,\n    // 是否打开页面切换loading\n    openPageLoading: true,\n    // 是否打开页面切换顶部进度条\n    openNProgress: false,\n  },\n\n  // 是否开启KeepAlive缓存  开发时候最好关闭,不然每次都需要清除缓存\n  openKeepAlive: true,\n  // 自动锁屏时间,为0不锁屏。 单位分钟 默认1个小时\n  lockTime: 0,\n  // 显示面包屑\n  showBreadCrumb: true,\n  // 显示面包屑图标\n  showBreadCrumbIcon: false,\n  // 是否使用全局错误捕获\n  useErrorHandle: false,\n  // 是否开启回到顶部\n  useOpenBackTop: true,\n  //  是否可以嵌入iframe页面\n  canEmbedIFramePage: true,\n  // 切换界面的时候是否删除未关闭的message及notify\n  closeMessageOnSwitch: true,\n  // 切换界面的时候是否取消已经发送但是未响应的http请求。\n  // 如果开启,想对单独接口覆盖。可以在单独接口设置\n  removeAllHttpPending: true,\n};\n

缓存配置

用于配置缓存内容加密信息,对缓存到浏览器的信息进行 AES 加密

/@/settings/encryptionSetting.ts 内可以配置 localStoragesessionStorage 缓存信息

前提: 使用项目自带的缓存工具类 /@/utils/cache 来进行缓存操作

import { isDevMode } from '/@/utils/env';\n\n// 缓存默认过期时间\nexport const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;\n\n// 开启缓存加密后,加密密钥。采用aes加密\nexport const cacheCipher = {\n  key: '_11111000001111@',\n  iv: '@11111000001111_',\n};\n\n// 是否加密缓存,默认生产环境加密\nexport const enableStorageEncryption = !isDevMode();\n

多语言配置

用于配置多语言信息

src/settings/localeSetting.ts 内配置

export const LOCALE: { [key: string]: LocaleType } = {\n  ZH_CN: 'zh_CN',\n  EN_US: 'en',\n};\n\nexport const localeSetting: LocaleSetting = {\n  // 是否显示语言选择器\n  showPicker: true,\n  // 当前语言\n  locale: LOCALE.ZH_CN,\n  // 默认语言\n  fallback: LOCALE.ZH_CN,\n  // 允许的语言\n  availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US],\n};\n\n// 语言列表\nexport const localeList: DropMenu[] = [\n  {\n    text: '简体中文',\n    event: LOCALE.ZH_CN,\n  },\n  {\n    text: 'English',\n    event: LOCALE.EN_US,\n  },\n];\n

主题色配置

默认全局主题色配置位于 build/config/glob/themeConfig.ts

只需要修改 primaryColor 为您需要的配色,然后重新执行 yarn serve 即可

/**\n * less global variable\n */\nexport const primaryColor = '#0960bd';\n

样式配置

css 前缀设置

用于修改项目内组件 class 的统一前缀

export const prefixCls = 'vben';\n
@namespace: vben;\n

前缀使用

在 css 内

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

在 vue/ts 内

import { useDesign } from '/@/hooks/web/useDesign';\n\nconst { prefixCls } = useDesign('app-logo');\n\n// prefixCls => vben-app-logo\n

颜色配置

用于预设一些颜色数组

src/settings/designSetting.ts 内配置

//  app主题色预设\nexport const APP_PRESET_COLOR_LIST: string[] = [\n  '#0960bd',\n  '#0084f4',\n  '#009688',\n  '#536dfe',\n  '#ff5c93',\n  '#ee4f12',\n  '#0096c7',\n  '#9c27b0',\n  '#ff9800',\n];\n\n// 顶部背景色预设\nexport const HEADER_PRESET_BG_COLOR_LIST: string[] = [\n  '#ffffff',\n  '#009688',\n  '#5172DC',\n  '#1E9FFF',\n  '#018ffb',\n  '#409eff',\n  '#4e73df',\n  '#e74c3c',\n  '#24292e',\n  '#394664',\n  '#001529',\n  '#383f45',\n];\n\n// 左侧菜单背景色预设\nexport const SIDE_BAR_BG_COLOR_LIST: string[] = [\n  '#001529',\n  '#273352',\n  '#ffffff',\n  '#191b24',\n  '#191a23',\n  '#304156',\n  '#001628',\n  '#28333E',\n  '#344058',\n  '#383f45',\n];\n

组件默认参数配置

src/settings/componentSetting.ts 内配置

// 用于配置某些组件的常规配置,而无需修改组件\nimport type { SorterResult } from '../components/Table';\n\nexport default {\n  // 表格配置\n  table: {\n    // 表格接口请求通用配置,可在组件prop覆盖\n    // 支持 xxx.xxx.xxx格式\n    fetchSetting: {\n      // 传给后台的当前页字段\n      pageField: 'page',\n      // 传给后台的每页显示多少条的字段\n      sizeField: 'pageSize',\n      // 接口返回表格数据的字段\n      listField: 'items',\n      // 接口返回表格总数的字段\n      totalField: 'total',\n    },\n    // 可选的分页选项\n    pageSizeOptions: ['10', '50', '80', '100'],\n    //默认每页显示多少条\n    defaultPageSize: 10,\n    // 默认排序方法\n    defaultSortFn: (sortInfo: SorterResult) => {\n      const { field, order } = sortInfo;\n      return {\n        // 排序字段\n        field,\n        // 排序方式 asc/desc\n        order,\n      };\n    },\n    // 自定义过滤方法\n    defaultFilterFn: (data: Partial<Recordable<string[]>>) => {\n      return data;\n    },\n  },\n  // 滚动组件配置\n  scrollbar: {\n    // 是否使用原生滚动样式\n    // 开启后,菜单,弹窗,抽屉会使用原生滚动条组件\n    native: false,\n  },\n};\n
',68);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.560c3ce7.lean.js b/assets/guide_settings.md.560c3ce7.lean.js new file mode 100644 index 00000000..e7c13e03 --- /dev/null +++ b/assets/guide_settings.md.560c3ce7.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"项目配置项","description":"","frontmatter":{},"headers":[{"level":2,"title":"环境变量配置","slug":"环境变量配置"},{"level":3,"title":"配置项说明","slug":"配置项说明"},{"level":3,"title":".env","slug":"env"},{"level":3,"title":".env.development","slug":"env-development"},{"level":3,"title":".env.production","slug":"env-production"},{"level":2,"title":"生产环境动态配置","slug":"生产环境动态配置"},{"level":3,"title":"说明","slug":"说明"},{"level":3,"title":"作用","slug":"作用"},{"level":3,"title":"如何获取全局变量","slug":"如何获取全局变量"},{"level":3,"title":"如何新增(新增一个可动态修改的配置项)","slug":"如何新增-新增一个可动态修改的配置项"},{"level":2,"title":"项目配置","slug":"项目配置"},{"level":3,"title":"配置文件路径","slug":"配置文件路径"},{"level":3,"title":"说明","slug":"说明-1"},{"level":2,"title":"缓存配置","slug":"缓存配置"},{"level":2,"title":"多语言配置","slug":"多语言配置"},{"level":2,"title":"主题色配置","slug":"主题色配置"},{"level":2,"title":"样式配置","slug":"样式配置"},{"level":3,"title":"css 前缀设置","slug":"css-前缀设置"},{"level":3,"title":"前缀使用","slug":"前缀使用"},{"level":2,"title":"颜色配置","slug":"颜色配置"},{"level":2,"title":"组件默认参数配置","slug":"组件默认参数配置"}],"relativePath":"guide/settings.md","lastUpdated":1697592578593}',p={},o=a('',68);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/index.md.4d6d00ec.js b/assets/index.md.4d6d00ec.js new file mode 100644 index 00000000..787f4c64 --- /dev/null +++ b/assets/index.md.4d6d00ec.js @@ -0,0 +1 @@ +import{o as t,c as e}from"./app.8cddb23b.js";const i='{"title":"Home","description":"","frontmatter":{"home":true,"heroImage":"/logo.png","actionText":"快速开始 →","actionLink":"/guide/introduction","altActionText":"在线预览","altActionLink":"https://vben.vvbin.cn/","features":[{"title":"💡 最新技术栈","details":"基于Vue3、Vite、TypeScript等最新技术栈开发"},{"title":"⚡️ 轻量快速的热重载","details":"无论应用程序大小如何,都始终极快的模块热重载(HMR)"},{"title":"🛠️ 丰富的示例","details":"常见的Web端插件示例实现"},{"title":"📦 组件封装","details":"对日常使用频率较高的组件二次封装,满足基础工作需求"},{"title":"🔩 主题配置","details":"丰富的主题配置及黑暗主题适配"},{"title":"🔑 权限管理","details":"完善的前后端权限管理方案"}],"footer":"MIT Licensed | Copyright © 2021-present Vben"},"relativePath":"index.md","lastUpdated":1697592578593}',n={};n.render=function(i,n,o,a,r,d){return t(),e("div")};export default n;export{i as __pageData}; diff --git a/assets/index.md.4d6d00ec.lean.js b/assets/index.md.4d6d00ec.lean.js new file mode 100644 index 00000000..787f4c64 --- /dev/null +++ b/assets/index.md.4d6d00ec.lean.js @@ -0,0 +1 @@ +import{o as t,c as e}from"./app.8cddb23b.js";const i='{"title":"Home","description":"","frontmatter":{"home":true,"heroImage":"/logo.png","actionText":"快速开始 →","actionLink":"/guide/introduction","altActionText":"在线预览","altActionLink":"https://vben.vvbin.cn/","features":[{"title":"💡 最新技术栈","details":"基于Vue3、Vite、TypeScript等最新技术栈开发"},{"title":"⚡️ 轻量快速的热重载","details":"无论应用程序大小如何,都始终极快的模块热重载(HMR)"},{"title":"🛠️ 丰富的示例","details":"常见的Web端插件示例实现"},{"title":"📦 组件封装","details":"对日常使用频率较高的组件二次封装,满足基础工作需求"},{"title":"🔩 主题配置","details":"丰富的主题配置及黑暗主题适配"},{"title":"🔑 权限管理","details":"完善的前后端权限管理方案"}],"footer":"MIT Licensed | Copyright © 2021-present Vben"},"relativePath":"index.md","lastUpdated":1697592578593}',n={};n.render=function(i,n,o,a,r,d){return t(),e("div")};export default n;export{i as __pageData}; diff --git a/assets/other_donate.md.36952460.js b/assets/other_donate.md.36952460.js new file mode 100644 index 00000000..09eae285 --- /dev/null +++ b/assets/other_donate.md.36952460.js @@ -0,0 +1 @@ +import{o as a,c as e,b as t,d as r}from"./app.8cddb23b.js";const n='{"title":"赞助","description":"","frontmatter":{},"relativePath":"other/donate.md","lastUpdated":1697592578593}',p={},l=t("h1",{id:"赞助"},[t("a",{class:"header-anchor",href:"#赞助","aria-hidden":"true"},"#"),r(" 赞助")],-1),d=t("p",null,"如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!",-1),o=t("p",null,[t("img",{src:"https://anncwb.github.io/anncwb/images/sponsor.png",alt:"donate"})],-1),i=t("p",null,[t("a",{style:{display:"block",width:"100px",height:"50px","line-height":"50px",color:"#fff","text-align":"center",background:"#408aed","border-radius":"4px"},href:"https://www.paypal.com/paypalme/cvvben"},"Paypal Me")],-1);p.render=function(t,r,n,p,s,c){return a(),e("div",null,[l,d,o,i])};export default p;export{n as __pageData}; diff --git a/assets/other_donate.md.36952460.lean.js b/assets/other_donate.md.36952460.lean.js new file mode 100644 index 00000000..09eae285 --- /dev/null +++ b/assets/other_donate.md.36952460.lean.js @@ -0,0 +1 @@ +import{o as a,c as e,b as t,d as r}from"./app.8cddb23b.js";const n='{"title":"赞助","description":"","frontmatter":{},"relativePath":"other/donate.md","lastUpdated":1697592578593}',p={},l=t("h1",{id:"赞助"},[t("a",{class:"header-anchor",href:"#赞助","aria-hidden":"true"},"#"),r(" 赞助")],-1),d=t("p",null,"如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!",-1),o=t("p",null,[t("img",{src:"https://anncwb.github.io/anncwb/images/sponsor.png",alt:"donate"})],-1),i=t("p",null,[t("a",{style:{display:"block",width:"100px",height:"50px","line-height":"50px",color:"#fff","text-align":"center",background:"#408aed","border-radius":"4px"},href:"https://www.paypal.com/paypalme/cvvben"},"Paypal Me")],-1);p.render=function(t,r,n,p,s,c){return a(),e("div",null,[l,d,o,i])};export default p;export{n as __pageData}; diff --git a/assets/other_doubt.md.d791cebd.js b/assets/other_doubt.md.d791cebd.js new file mode 100644 index 00000000..83f87f5d --- /dev/null +++ b/assets/other_doubt.md.d791cebd.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"常见疑点说明","description":"","frontmatter":{},"headers":[{"level":2,"title":"项目别名","slug":"项目别名"},{"level":2,"title":"为什么在本地没有按需引入组件库样式,在生产才引入","slug":"为什么在本地没有按需引入组件库样式,在生产才引入"},{"level":2,"title":"为什么单独把 moment 放到 dataUtil 内","slug":"为什么单独把-moment-放到-datautil-内"}],"relativePath":"other/doubt.md","lastUpdated":1697592578593}',p={},o=a('

常见疑点说明

该分类主要说明一些地方为什么这样做,以及原因是什么

项目别名

/@/vite 内配置的别名

/@/settings 等同于 src/settings

为什么是/@/

因为项目是从 vite1.0 过渡过来的,vite1.0 只能以 / 开头,所以有一部分从 webpack 用户转过来的可能不习惯。

为什么在本地没有按需引入组件库样式,在生产才引入

在 main.ts 内可以看到,本地开发会全量引入 antd.less,vite-plugin-style-import 在本地是没有作用的。

这样做的原因主要是加快本地开发刷新速度。如果在本地开发中也按需按需引入,则在浏览器控制台内可以看到,平均一个页面大概增加了 100 次 http 请求。如果全量引入,只增加了一个请求,所以为了减少请求数量,才这样种。

// src/main.ts\nif (import.meta.env.DEV) {\n  import('ant-design-vue/dist/antd.less');\n}\n\n// build/vite/plugin/styleImport\nimport styleImport from 'vite-plugin-style-import';\nexport function configStyleImportPlugin(isBuild: boolean) {\n  if (!isBuild) return [];\n  const styleImportPlugin = styleImport({\n    libs: [\n      {\n        libraryName: 'ant-design-vue',\n        esModule: true,\n        resolveStyle: (name) => {\n          return `ant-design-vue/es/${name}/style/index`;\n        },\n      },\n    ],\n  });\n  return styleImportPlugin;\n}\n

为什么单独把 moment 放到 dataUtil 内

src/utils/dataUtil 内,使用的是 moment,其次在页面中对时间的操作也是使用 dateUtil,而不是直接 import moment from 'moment'

这样做主要是方便后续切换到 dayjs,因为 api 一样,所以在后续切换中,只需更改 dataUtil 内的 import 即可,而不用全部更改。

',13);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/other_doubt.md.d791cebd.lean.js b/assets/other_doubt.md.d791cebd.lean.js new file mode 100644 index 00000000..81efd959 --- /dev/null +++ b/assets/other_doubt.md.d791cebd.lean.js @@ -0,0 +1 @@ +import{o as n,c as s,a}from"./app.8cddb23b.js";const t='{"title":"常见疑点说明","description":"","frontmatter":{},"headers":[{"level":2,"title":"项目别名","slug":"项目别名"},{"level":2,"title":"为什么在本地没有按需引入组件库样式,在生产才引入","slug":"为什么在本地没有按需引入组件库样式,在生产才引入"},{"level":2,"title":"为什么单独把 moment 放到 dataUtil 内","slug":"为什么单独把-moment-放到-datautil-内"}],"relativePath":"other/doubt.md","lastUpdated":1697592578593}',p={},o=a('',13);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/other_faq.md.f27f546c.js b/assets/other_faq.md.f27f546c.js new file mode 100644 index 00000000..1b64b3b2 --- /dev/null +++ b/assets/other_faq.md.f27f546c.js @@ -0,0 +1 @@ +import{o as a,c as n,a as s}from"./app.8cddb23b.js";const e='{"title":"常见问题","description":"","frontmatter":{},"headers":[{"level":2,"title":"前言","slug":"前言"},{"level":2,"title":"关于缓存更新问题","slug":"关于缓存更新问题"},{"level":2,"title":"关于修改配置文件的问题","slug":"关于修改配置文件的问题"},{"level":2,"title":"esbuild 模式下开启 LEGACY 打包失败","slug":"esbuild-模式下开启-legacy-打包失败"},{"level":2,"title":"ant-design-vue 控制台警告","slug":"ant-design-vue-控制台警告"},{"level":2,"title":"添加菜单后没显示","slug":"添加菜单后没显示"},{"level":2,"title":"本地运行报错","slug":"本地运行报错"},{"level":2,"title":"tab 页切换后页面空白","slug":"tab-页切换后页面空白"},{"level":2,"title":"组件命名问题","slug":"组件命名问题"},{"level":2,"title":"我的代码本地开发可以,打包就不行了","slug":"我的代码本地开发可以,打包就不行了"},{"level":2,"title":"safari 问题","slug":"safari-问题"},{"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":"为什么是 moment.js","slug":"为什么是-moment-js"},{"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":"更灵活的菜单路由权限控制"}],"relativePath":"other/faq.md","lastUpdated":1697592578593}',t={},o=s('

常见问题

TIP

列举了一些常见的问题。有问题可以先来这里寻找,如果没有可以在 issue 提。

前言

遇到问题,可以先从以下几个方面查找

  1. 对应模块的 GitHub 仓库 issue 搜索
  2. google搜索问题
  3. 百度搜索问题
  4. 在下面列表找不到问题可以到 issue 提问 issues
  5. 如果不是问题类型的,需要讨论的,请到 discussions 讨论

关于缓存更新问题

vben-admin 的项目配置默认是缓存在 localStorage 内,所以版本更新后可能有些配置没改变。

解决方式是每次更新代码的时候修改 package.json 内的 version 版本号. 因为 localStorage 的 key 是根据版本号来的。所以更新后版本不同前面的配置会失效。重新登录即可

VUE_VBEN_ADMIN__DEVELOPMENT__2.0.3__COMMON__LOCAL__KEY__ key 的组成是 [项目名]+[开发环境]+[版本号]+[key]

关于修改配置文件的问题

当修改 .env 等环境文件及 vite.config.ts 文件时,vite 会自动重启服务。

自动重启有几率出现问题,请重新运行项目即可解决.

esbuild 模式下开启 LEGACY 打包失败

如果将 \b build.minify 设置为 'esbuild',且不能启用 LEGACY,否则打包将会报错,两者选其一即可打包。

ant-design-vue 控制台警告

在控制台看到以下警告的原因是 ant-design-vue 会检测是否使用了 babel-plugin-import 来判断是否进行了组件库的按需引入。

但是项目使用的是 vite 的插件 vite-plugin-style-import 来进行按需引入。在 vite 内没必要使用 babel 在转换一次代码了。

所以想关闭这个警告,得等 ant-design-vue 提供可以关闭该警告的配置。

You are using a whole package of antd, please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundle size. Not support Vite !!!\n

添加菜单后没显示

菜单必须和路由匹配才会显示在界面上,所以得确保菜单和对应的路由存在即可显示.

本地运行报错

由于 vite 在本地没有转换代码,且代码中用到了可选链等比较新的语法。所以本地开发需要使用版本较高的浏览器(Chrome 85+)进行开发

tab 页切换后页面空白

这是由于开启了路由切换动画,且对应的页面组件存在多个根节点导致的,在页面最外层添加<div></div>即可

错误示例

<template>\n  <!-- 注释也算一个节点 -->\n  <h1>text h1</h1>\n  <h2>text h2</h2>\n</template>\n

正确示例

<template>\n  <div>\n    <h1>text h1</h1>\n    <h2>text h2</h2>\n  </div>\n</template>\n

提示

  • 如果想使用多个根标签,可以禁用路由切换动画
  • template 下面的根注释节点也算一个节点

组件命名问题

目前在 vite+vue3.0.5 版本中,如果组件命名携带关键字,则可能会导致内存溢出。例如 ImportExcel excel 导入组件。

我的代码本地开发可以,打包就不行了

目前发现这个原因可能有以下,可以从以下原因来排查,如果还有别的可能,可以提交 pr 来告诉我

  1. 使用了 ctx 这个变量,ctx 本身未暴露出在实例类型内,尤大也是说了不要用这个属性。这个属性只是用于内部使用。
import { getCurrentInstance } from 'vue';\ngetCurrentInstance().ctx.xxxx;\n

safari 问题

目前在 safari 上面本地开发运行样式会有问题,还未找到原因,有知道的也可以告诉我。

模版区别

  • Vue-Vben-Admin - 是包含 Demo 示例的,个人建议不要在这上面进行开发。当然,你如果动手能力强的话可以直接改。
  • Vue-Vben-Admin-Thin-Next 精简了代码后的模版项目。适合在这基础上进行二次开发。

环境问题

如果出现依赖安装报错,启动报错等。先检查电脑环境有没有安装齐全。

  • Node 版本必须大于12.0.0不支持 13, 推荐 14 版本。
  • Git
  • Yarn 最新版

依赖安装问题

  • 如果依赖安装不了或者启动报错可以先尝试 删除 yarn.locknode_modules,然后重新运行 yarn install
  • 如果依赖安装不了或者报错,可以尝试切换手机热点来进行依赖安装。
  • 如果还是不行,可以自行配置国内镜像安装。
  • 也可以在项目根目录创建 .npmrc 文件,内容如下
# .npmrc\nregistry = https://registry.npm.taobao.org\n

然后重新执行yarn run reinstall等待安装完成即可

如何保证我的代码能更新到最新代码

如果你使用了该项目进行项目开发。开发之中想同步最新的代码。你可以设置多个源的方式

  1. 克隆代码
git clone https://github.com/vbenjs/vben-admin-thin-next.git\n
  1. 添加自己的公司 git 源地址
# up 为源名称,可以随意设置\n# gitUrl为开源最新代码\ngit remote add up gitUrl;\n
  1. 提交代码到自己公司 git
# 提交代码到自己公司\n# main为分支名 需要自行根据情况修改\ngit push up main\n\n# 同步公司的代码\n# main为分支名 需要自行根据情况修改\ngit pull up main\n
  1. 如何同步开源最新代码
git pull origin main\n

TIP

同步代码的时候会出现冲突。只需要把冲突解决即可

打包文件过大

  • 首先,完整版由于引用了比较多的库文件,所以打包会比较大。可以使用精简版来进行开发

  • 其次建议开启 gzip,使用之后体积会只有原先 1/3 左右。

gzip 可以由服务器直接开启。如果是这样,前端不需要构建 .gz 格式的文件

如果前端构建了 .gz 文件,以 nginx 为例,nginx 需要开启 gzip_static: on 这个选项。

  • 开启 gzip 的同时还可以同时开启 brotli,比 gzip 更好的压缩。两者可以共存

注意

  • gzip_static: 这个模块需要 nginx 另外安装,默认的 nginx 没有安装这个模块。

  • 开启 brotli 也需要 nginx 另外安装模块

运行错误

如果出现类似以下错误,请检查项目全路径(包含所有父级路径)不能出现中文、日文、韩文。否则将会出现路径访问 404 导致以下问题

[vite] Failed to resolve module import "ant-design-vue/dist/antd.css-vben-adminode_modulesant-design-vuedistantd.css". (imported by /@/setup/ant-design-vue/index.ts)\n

为什么是 moment.js

很多人问为什么不用dayjs。在项目依赖中可以看到,它是 Ant-Design-Vue 内部自带的。

目前还没有基于 Vite 的 dayjs 替换 momentjs 方案,webpack 已经有了。等以后出现了在进行替换。

控制台路由警告问题

如果看到控制台有如下警告,且页面能正常打开 可以忽略该警告。

后续 vue-router 可能会提供配置项来关闭警告

2.6.1 及以上版本已移除此警告

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

启动报错

当出现以下错误信息时,请检查你的 nodejs 版本号是否符合要求

TypeError: str.matchAll is not a function\nat Object.extractor (vue-vben-admin-main\\node_modules@purge-icons\\core\\dist\\index.js:146:27)\nat Extract (vue-vben-admin-main\\node_modules@purge-icons\\core\\dist\\index.js:173:54)\n\n

页面报错

当页面出现以下报错,是因为 /xxx 对应的路由组件内部出现了错误。

 Uncaught (in promise) Error: Couldn't resolve component "default" at "/xxx"\n\n

可以尝试从以下几点排查

  1. 检查对应组件内部 import 的所有文件是否正确
  2. 检查引入方式是否错误。
// 正确的\nimport { cloneDeep } from 'lodash-es';\n\n// 报错\nimport _ from 'lodash-es';\n
  1. 检查样式是否使用变量及有没有引入对应的变量文件
  2. 检查代码明显的语法错误

这样就不会是使用的取值忘记 xxx.value 来进行数据获取

跨域问题

参考跨域问题

接口请求问题

proxy 代理不成功,没有代理到实际地址?

代理只是服务请求代理,这个地址是不会变的。 原理可以简单的理解为,在本地启了一个服务,你先请求了本地的服务,本地的服务转发了你的请求到实际服务器。所以你在浏览器上看到的请求地址还是 http://localhost:8000/xxx。以服务端是否收到请求为准。

组件库问题

跟组件库相关的问题可以查看常见问题

动态调整菜单问题

菜单数据的值被存放在 store/modules/permission store 中, 你可以在这里进行修改

更灵活的菜单路由权限控制

你可以在 store/modules/permission下, 修改 routeFilter 方法来进行更灵活的菜单路由权限控制

 const routeFilter = (route: AppRouteRecordRaw) => {\n    const { meta } = route;\n    // 抽出角色\n    const { roles } = meta || {};\n\n    // 添加你的自定义逻辑来过滤路由和菜单\n    if (xxx) {\n      return false;\n    }\n\n    if (!roles) return true;\n    // 进行角色权限判断\n    return roleList.some((role) => roles.includes(role));\n  };\n\n
',99);t.render=function(s,e,t,p,l,c){return a(),n("div",null,[o])};export default t;export{e as __pageData}; diff --git a/assets/other_faq.md.f27f546c.lean.js b/assets/other_faq.md.f27f546c.lean.js new file mode 100644 index 00000000..a6e18059 --- /dev/null +++ b/assets/other_faq.md.f27f546c.lean.js @@ -0,0 +1 @@ +import{o as a,c as n,a as s}from"./app.8cddb23b.js";const e='{"title":"常见问题","description":"","frontmatter":{},"headers":[{"level":2,"title":"前言","slug":"前言"},{"level":2,"title":"关于缓存更新问题","slug":"关于缓存更新问题"},{"level":2,"title":"关于修改配置文件的问题","slug":"关于修改配置文件的问题"},{"level":2,"title":"esbuild 模式下开启 LEGACY 打包失败","slug":"esbuild-模式下开启-legacy-打包失败"},{"level":2,"title":"ant-design-vue 控制台警告","slug":"ant-design-vue-控制台警告"},{"level":2,"title":"添加菜单后没显示","slug":"添加菜单后没显示"},{"level":2,"title":"本地运行报错","slug":"本地运行报错"},{"level":2,"title":"tab 页切换后页面空白","slug":"tab-页切换后页面空白"},{"level":2,"title":"组件命名问题","slug":"组件命名问题"},{"level":2,"title":"我的代码本地开发可以,打包就不行了","slug":"我的代码本地开发可以,打包就不行了"},{"level":2,"title":"safari 问题","slug":"safari-问题"},{"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":"为什么是 moment.js","slug":"为什么是-moment-js"},{"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":"更灵活的菜单路由权限控制"}],"relativePath":"other/faq.md","lastUpdated":1697592578593}',t={},o=s('',99);t.render=function(s,e,t,p,l,c){return a(),n("div",null,[o])};export default t;export{e as __pageData}; diff --git a/assets/other_project.md.dbd0bef8.js b/assets/other_project.md.dbd0bef8.js new file mode 100644 index 00000000..f8701e5f --- /dev/null +++ b/assets/other_project.md.dbd0bef8.js @@ -0,0 +1 @@ +import{o as e,c as r,a as t}from"./app.8cddb23b.js";const o='{"title":"相关项目","description":"","frontmatter":{},"headers":[{"level":2,"title":"golang","slug":"golang"},{"level":2,"title":"php","slug":"php"},{"level":2,"title":"java","slug":"java"},{"level":2,"title":".net","slug":"net"}],"relativePath":"other/project.md","lastUpdated":1697592578593}',n={},a=t('

相关项目

对接vben的项目地址,非官方项目,vben用户开源,开源协议请自行查看

golang

  1. 后端https://github.com/vbenjs/gf-vben 前端https://github.com/vbenjs/gf-vben-admin

php

  1. 后端https://github.com/Joyboo/Joyboo-admin-easyswoole 前端https://github.com/Joyboo/Joyboo-vben-admin-thin
  2. 后端: https://github.com/wcz0/laravel-vben 前端: https://github.com/wcz0/laravel-vben-admin

java

  1. 后端SpringCloudhttps://github.com/zuihou/lamp-cloud 后端SpringBoothttps://github.com/zuihou/lamp-boot 前端https://github.com/zuihou/lamp-web-plus
  2. 后端https://gitee.com/skysong/coffee-boot
  3. 后端https://gitee.com/battcn/wemirr-platform 前端https://gitee.com/battcn/wemirr-platform-ui
  4. 后端https://gitee.com/zsvg/vboot-java 前端https://gitee.com/zsvg/vboot-vben
  5. 后端SpringBoothttps://github.com/uncarbon97/helio-boot 后端SpringCloudhttps://github.com/uncarbon97/helio-cloud 前端https://github.com/uncarbon97/helio-admin-vue-vben
  6. 后端SpringBoothttps://gitee.com/zhijiantianya/ruoyi-vue-pro 后端SpringCloudhttps://gitee.com/zhijiantianya/yudao-cloud 前端https://gitee.com/yudaocode/yudao-ui-admin-vben

.net

  1. 对接Osharp 前端https://github.com/zionLZH/osharp-vben-admin
  2. 后端https://gitee.com/zsvg/vboot-net 前端https://gitee.com/zsvg/vboot-vben
  3. Admin.NET 源码地址https://gitee.com/zuohuaijun/Admin.NET
',10);n.render=function(t,o,n,i,h,g){return e(),r("div",null,[a])};export default n;export{o as __pageData}; diff --git a/assets/other_project.md.dbd0bef8.lean.js b/assets/other_project.md.dbd0bef8.lean.js new file mode 100644 index 00000000..31223e16 --- /dev/null +++ b/assets/other_project.md.dbd0bef8.lean.js @@ -0,0 +1 @@ +import{o as e,c as r,a as t}from"./app.8cddb23b.js";const o='{"title":"相关项目","description":"","frontmatter":{},"headers":[{"level":2,"title":"golang","slug":"golang"},{"level":2,"title":"php","slug":"php"},{"level":2,"title":"java","slug":"java"},{"level":2,"title":".net","slug":"net"}],"relativePath":"other/project.md","lastUpdated":1697592578593}',n={},a=t('',10);n.render=function(t,o,n,i,h,g){return e(),r("div",null,[a])};export default n;export{o as __pageData}; diff --git a/assets/other_server.md.0ae59f36.js b/assets/other_server.md.0ae59f36.js new file mode 100644 index 00000000..e1483813 --- /dev/null +++ b/assets/other_server.md.0ae59f36.js @@ -0,0 +1 @@ +import{o as e,c as a,a as n}from"./app.8cddb23b.js";const s='{"title":"测试服务器","description":"","frontmatter":{},"headers":[{"level":2,"title":"使用","slug":"使用"}],"relativePath":"other/server.md","lastUpdated":1697592578593}',t={},r=n('

测试服务器

在项目 /test/server 内有简单的 Node.js 测试后台接口服务,用 Koa2 实现

使用

\ncd ./test/server\n\n# 安装依赖\nyarn\n\n# 运行服务\nyarn start\n\n

服务运行成功之后,就可以访问测试上传接口及 websocket 接口服务

',5);t.render=function(n,s,t,o,c,d){return e(),a("div",null,[r])};export default t;export{s as __pageData}; diff --git a/assets/other_server.md.0ae59f36.lean.js b/assets/other_server.md.0ae59f36.lean.js new file mode 100644 index 00000000..1bbfc25a --- /dev/null +++ b/assets/other_server.md.0ae59f36.lean.js @@ -0,0 +1 @@ +import{o as e,c as a,a as n}from"./app.8cddb23b.js";const s='{"title":"测试服务器","description":"","frontmatter":{},"headers":[{"level":2,"title":"使用","slug":"使用"}],"relativePath":"other/server.md","lastUpdated":1697592578593}',t={},r=n('',5);t.render=function(n,s,t,o,c,d){return e(),a("div",null,[r])};export default t;export{s as __pageData}; diff --git a/assets/style.84beecbd.css b/assets/style.84beecbd.css new file mode 100644 index 00000000..7927bb18 --- /dev/null +++ b/assets/style.84beecbd.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-ffb90d4a]{font-size:1.3rem;font-weight:600;color:var(--c-text)}.nav-bar-title[data-v-ffb90d4a]:hover{text-decoration:none}.logo[data-v-ffb90d4a]{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-2332dbaa]{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-2332dbaa]{padding:.7rem 1.5rem}}.flex-grow[data-v-2332dbaa]{flex-grow:1}.nav[data-v-2332dbaa]{display:none}@media (min-width:720px){.nav[data-v-2332dbaa]{display:flex}.navbar__dark-mode[data-v-2332dbaa]{display:none}}.nav-icons[data-v-2332dbaa]{display:flex;padding:2px 0 0;align-items:center;border-bottom:0;margin-left:12px}.nav-icons .item[data-v-2332dbaa]{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}}.border{border-width:1px}.flex{display:-webkit-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.inline-flex{display:-webkit-inline-box;display:-ms-inline-flexbox;display:-webkit-inline-flex;display:inline-flex}.table{display:table}.justify-center{-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center}.h-full{height:100%}.text-color-base{font-size:1rem;line-height:1.5rem}.m-3{margin:.75rem}.m-4{margin:1rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mx-4{margin-left:1rem;margin-right:1rem}.mt-4{margin-top:1rem}.mr-4{margin-right:1rem}.mr-2{margin-right:.5rem}.mb-2{margin-bottom:.5rem}.ml-4{margin-left:1rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-10{padding:2.5rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-4{padding-left:1rem;padding-right:1rem}.absolute{position:absolute}.relative{position:relative}.content{content:""}.w-full{width:100%}.transition{-webkit-transition-property:background-color,border-color,color,fill,stroke,opacity,-webkit-box-shadow,-webkit-transform,filter,backdrop-filter;-o-transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,-webkit-box-shadow,transform,-webkit-transform,filter,backdrop-filter;-webkit-transition-timing-function:cubic-bezier(.4,0,.2,1);-o-transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1);-webkit-transition-duration:150ms;-o-transition-duration:150ms;transition-duration:150ms}[duration~="8000"]{-webkit-transition-duration:8s;-o-transition-duration:8s;transition-duration:8s}:root{--c-brand:#4569d4;--c-brand-light:#4f73dd;--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/auth.html b/components/auth.html new file mode 100644 index 00000000..dc1820b3 --- /dev/null +++ b/components/auth.html @@ -0,0 +1,45 @@ + + + + + + Authority | Vben Admin + + + + + + + + + + + + + + + + +

Authority

用于项目权限的组件,一般用于按钮级等细粒度权限管理

Usage

<template>
+  <div>
+    <Authority :value="RoleEnum.ADMIN">
+      <a-button type="primary" block> 只有admin角色可见 </a-button>
+    </Authority>
+  </div>
+</template>
+<script>
+  import { Authority } from '/@/components/Authority';
+  import { defineComponent } from 'vue';
+  export default defineComponent({
+    components: { Authority },
+  });
+</script>
+

Props

属性类型默认值说明
valueRoleEnum,RoleEnum[],string,string[]-角色信息或者权限编码。会自动区分权限模式
+ + + + \ No newline at end of file diff --git a/components/basic.html b/components/basic.html new file mode 100644 index 00000000..c5b39d02 --- /dev/null +++ b/components/basic.html @@ -0,0 +1,69 @@ + + + + + + Basic 基础组件 | Vben Admin + + + + + + + + + + + + + + + + +

Basic 基础组件

一些比较基础的通用组件使用方式

BasicTitle

用于显示标题,可以显示帮助按钮及文本

Usage

<template>
+  <div>
+    <BasicTitle helpMessage="提示1">标题</BasicTitle>
+    <BasicTitle :helpMessage="['提示1', '提示2']">标题</BasicTitle>
+  </div>
+</template>
+<script>
+  import { BasicTitle } from '/@/components/Basic/index';
+  import { defineComponent } from 'vue';
+  export default defineComponent({
+    components: { BasicTitle },
+  });
+</script>
+

Props

属性类型默认值说明
helpMessagestring|string[]-标题右侧帮助按钮信息
spanbooleanfalse是否显示标题左侧蓝色色块
normalbooleanfalse将文字默认化,不加粗

Slots

名称说明
default标题文本

BasicArrow

带动画的箭头组件

Usage

<template>
+  <div>
+    <BasicArrow :expand="false" />
+  </div>
+</template>
+<script>
+  import { BasicArrow } from '/@/components/Basic/index';
+  import { defineComponent } from 'vue';
+  export default defineComponent({
+    components: { BasicArrow },
+  });
+</script>
+

Props

属性类型默认值说明
expandbooleanfalse箭头展开状态
topbooleanfalse箭头默认向上
bottombooleanfalse箭头默认向下
insetbooleanfalse取消 padding/margin,用于内嵌

BasicHelp

帮助按钮组件

Usage

<template>
+  <div>
+    <BasicHelp :text="['提示1', '提示2']" />
+    <BasicHelp text="提示" />
+  </div>
+</template>
+<script>
+  import { BasicHelp } from '/@/components/Basic/index';
+  import { defineComponent } from 'vue';
+  export default defineComponent({
+    components: { BasicHelp },
+  });
+</script>
+

Props

属性类型默认值可选值说明
fontSizestring14px-字体大小
colorstring#fff-颜色
textstring|string[]--文本列表
showIndexbooleantrue-是否显示序号,在 text 为 string[]情况下生效
maxWidthstring600px-最大宽度
placementstringright-显示方向,参考 Tooltip 组件

Slots

名称说明
default默认图标
+ + + + \ No newline at end of file diff --git a/components/click-out-side.html b/components/click-out-side.html new file mode 100644 index 00000000..b0bbfea9 --- /dev/null +++ b/components/click-out-side.html @@ -0,0 +1,53 @@ + + + + + + ClickOutSide | Vben Admin + + + + + + + + + + + + + + + + +

ClickOutSide

用于监听包裹的元素点击外部触发事件

Usage

<template>
+  <div>
+    <ClickOutSide @clickOutside="() => (showRef = false)">
+      <div @click="() => (showRef = true)">
+        {{ showRef ? '鼠标点击那部(点击边框外可以恢复)' : '点击该区域状态(初始状态)' }}
+      </div>
+    </ClickOutSide>
+  </div>
+</template>
+<script>
+  import { defineComponent, ref } from 'vue';
+  import { ClickOutSide } from '/@/components/ClickOutSide/';
+  export default defineComponent({
+    components: { ClickOutSide },
+    setup() {
+      const showRef = ref(false);
+      return {
+        showRef,
+      };
+    },
+  });
+</script>
+

Events

事件回调参数说明
clickOutside()=>void点击包裹元素外部区域触发

Slots

名称说明
default被包裹的元素
+ + + + \ No newline at end of file diff --git a/components/code-editor.html b/components/code-editor.html new file mode 100644 index 00000000..6bd6d0dc --- /dev/null +++ b/components/code-editor.html @@ -0,0 +1,44 @@ + + + + + + CodeEditor | Vben Admin + + + + + + + + + + + + + + + + +

CodeEditor

代码编辑器

Usage

<template>
+  <CodeEditor v-model:value="value" :mode="modeValue" />
+</template>
+<script>
+  import { defineComponent, ref } from 'vue';
+  export default defineComponent({
+    components: { CodeEditor },
+    setup() {
+      const modeValue = ref('application/json');
+      return { value, modeValue };
+    },
+  });
+</script>
+

Props

属性类型默认值可选值说明
value(v-model:value)any--绑定值
modestringapplication/json'application/json','htmlmixed','javascript'代码类型
readonlyboolean--是否只读
+ + + + \ No newline at end of file diff --git a/components/collapse-container.html b/components/collapse-container.html new file mode 100644 index 00000000..45dfdbd2 --- /dev/null +++ b/components/collapse-container.html @@ -0,0 +1,46 @@ + + + + + + CollapseContainer | Vben Admin + + + + + + + + + + + + + + + + +

CollapseContainer

区域折叠卡片容器

Usage

<template>
+  <div>
+    <CollapseContainer> content </CollapseContainer>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { CollapseContainer } from '/@/components/Container/index';
+
+  export default defineComponent({
+    components: {
+      CollapseContainer,
+    },
+  });
+</script>
+

Props

属性类型默认值可选值说明
titlestring--标题
canExpanbooleantrue-是否可以展开,为true显示折叠按钮
helpMessagestring[],string--标题右侧温馨提醒
triggerWindowResizebooleanfalse-展开收缩的时候是否触发 window.resize
loadingbooleanfalse-显示加载骨架屏
lazyTimenumber0-延迟加载时间

Slots

名称说明
title自定义标题
action自定义右侧操作按钮
default默认区域
footer自定义底部区域
+ + + + \ No newline at end of file diff --git a/components/count-down.html b/components/count-down.html new file mode 100644 index 00000000..8539b3a8 --- /dev/null +++ b/components/count-down.html @@ -0,0 +1,53 @@ + + + + + + CountDown | Vben Admin + + + + + + + + + + + + + + + + +

CountDown

倒计时组件

CountButton

倒计时按钮组件

Usage

<template>
+  <CountButton />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { CountButton } from '/@/components/CountDown';
+
+  export default defineComponent({
+    components: { CountButton },
+  });
+</script>
+

Props

属性类型默认值可选值说明
valueany--绑定值
countnumber60-倒计时时间
beforeStartFunc()=>promise--倒计时之前执行的函数,返回 true 才会开始执行

CountDownInput

倒计时输入框按钮组件

Usage

<template>
+  <CountdownInput />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { CountdownInput } from '/@/components/CountDown';
+
+  export default defineComponent({
+    components: { CountdownInput },
+  });
+</script>
+

Props

属性类型默认值可选值说明
valueany--绑定值
sizestring'default', 'large', 'small'-输入框即按钮大小
countnumber60-倒计时时间
sendCodeApi()=>promise--倒计时之前执行的函数,返回 true 才会开始执行
+ + + + \ No newline at end of file diff --git a/components/count-to.html b/components/count-to.html new file mode 100644 index 00000000..35809824 --- /dev/null +++ b/components/count-to.html @@ -0,0 +1,44 @@ + + + + + + CountTo | Vben Admin + + + + + + + + + + + + + + + + +

CountTo

数字动画组件

该组件对 vue-countTo 进行了重构,改造成适配 vue3 语法的组件。

Usage

<template>
+  <CountTo prefix="$" :color="'#409EFF'" :startVal="1" :endVal="200000" :duration="8000" />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { CountTo } from '/@/components/CountTo/index';
+
+  export default defineComponent({
+    components: {
+      CountTo,
+    },
+  });
+</script>
+

Props

属性类型默认值说明
startValnumber0起始值
endValnumber2021结束值
durationnumber1500动画持续时间
autoplaybooleantrue自动执行
prefixstring-前缀
suffixstring-后缀
separatorstring,分隔符
colorstring-字体颜色
useEasingbooleantrue是否开启动画
transitionstringlinear动画效果
decimalsnumber0保留小数点位数

Methods

名称回调参数说明
start()=>void开始执行动画
reset()=>void重置
+ + + + \ No newline at end of file diff --git a/components/cropper.html b/components/cropper.html new file mode 100644 index 00000000..adbcaf65 --- /dev/null +++ b/components/cropper.html @@ -0,0 +1,97 @@ + + + + + + Cropper | Vben Admin + + + + + + + + + + + + + + + + +

Cropper

图片裁剪组件

CropperImage

图片裁剪组件

Usage

<template>
+  <CropperImage ref="refCropper" :src="img" @cropend="handleCropend" style="width: 40vw" />
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { CropperImage } from '/@/components/Cropper';
+  import img from '/@/assets/images/header.jpg';
+
+  export default defineComponent({
+    components: {
+      CropperImage,
+    },
+    setup() {
+      const info = ref('');
+      const cropperImg = ref('');
+
+      function handleCropend({ imgBase64, imgInfo }) {
+        info.value = imgInfo;
+        cropperImg.value = imgBase64;
+      }
+
+      return {
+        img,
+        info,
+        cropperImg,
+        handleCropend,
+      };
+    },
+  });
+</script>
+

Props

属性类型默认值说明
srcstring-图片源
altstring-图片 alt
circledbooleanfalse圆形裁剪框
realTimePreviewbooleantrue实时触发预览
heightstring360px高度
crossoriginstring-crossorigin
imageStyleobject``图片样式
optionsobjectDefaultOptionscorpperjs 配置项

DefaultOptions

{
+  aspectRatio: 1,
+  zoomable: true,
+  zoomOnTouch: true,
+  zoomOnWheel: true,
+  cropBoxMovable: true,
+  cropBoxResizable: true,
+  toggleDragModeOnDblclick: true,
+  autoCrop: true,
+  background: true,
+  highlight: true,
+  center: true,
+  responsive: true,
+  restore: true,
+  checkCrossOrigin: true,
+  checkOrientation: true,
+  scalable: true,
+  modal: true,
+  guides: true,
+  movable: true,
+  rotatable: true,
+}
+

CropperAvatar

头像裁剪组件

Usage

<template>
+  <CropperAvatar :uploadApi="uploadApi" />
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { CropperAvatar } from '/@/components/Cropper';
+  import { uploadApi } from '/@/api/sys/upload';
+
+  export default defineComponent({
+    components: {
+      CropperAvatar,
+    },
+  });
+</script>
+

Props

属性类型默认值说明版本
widthstring,number200px图片源
uploadApi({ file: Blob, name: string }) => Promise<void>-图片上传接口
valueString-当前头像地址(v-model)2.5.3
showBtnBooleantrue是否显示按钮2.5.3
btnTextString-按钮文案2.5.3
btnPropsButtonProps-按钮的其它属性2.5.3

Events

名称参数说明版本
changevalue: String当头像上传完成时触发2.5.3

Methods

名称定义说明版本
openModal()=>void打开上传Modal2.5.3
closeModal()=>void关闭上传Modal2.5.3
+ + + + \ No newline at end of file diff --git a/components/desc.html b/components/desc.html new file mode 100644 index 00000000..035cbd75 --- /dev/null +++ b/components/desc.html @@ -0,0 +1,96 @@ + + + + + + Description 详情组件 | Vben Admin + + + + + + + + + + + + + + + + +

Description 详情组件

antv 的 Descriptions 组件进行封装

Usage

<template>
+  <div class="p-4">
+    <Description
+      title="基础示例"
+      :collapseOptions="{ canExpand: true, helpMessage: 'help me' }"
+      :column="3"
+      :data="mockData"
+      :schema="schema"
+    />
+    <Description @register="register" class="mt-4" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Alert } from 'ant-design-vue';
+  import { Description, DescItem, useDescription } from '/@/components/Description/index';
+  const mockData: any = {
+    username: 'test',
+    nickName: 'VB',
+    age: 123,
+    phone: '15695909xxx',
+    email: '190848757@qq.com',
+    addr: '厦门市思明区',
+    sex: '男',
+    certy: '3504256199xxxxxxxxx',
+    tag: 'orange',
+  };
+  const schema: DescItem[] = [
+    {
+      field: 'username',
+      label: '用户名',
+    },
+    {
+      field: 'nickName',
+      label: '昵称',
+      render: (curVal, data) => {
+        return `${data.username}-${curVal}`;
+      },
+    },
+    {
+      field: 'phone',
+      label: '联系电话',
+    },
+    {
+      field: 'email',
+      label: '邮箱',
+    },
+    {
+      field: 'addr',
+      label: '地址',
+    },
+  ];
+  export default defineComponent({
+    components: { Description, Alert },
+    setup() {
+      const [register] = useDescription({
+        title: 'useDescription',
+        data: mockData,
+        schema: schema,
+      });
+      return { mockData, schema, register };
+    },
+  });
+</script>
+

useDescription

参考以上示例

const [register] = useDescription(Props);
+

Props

温馨提醒

除以下参数外,官方文档内的 props 也都支持,具体可以参考 antv Description

属性类型默认值可选值说明
titlestring--标题
sizestringsmall-大小
borderedbooleantrue-是否展示边框
columnNumber, Object{ xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }-一行的 DescriptionItems 数量
useCollapseboolean--是否包裹 CollapseContainer 组件
collapseOptionsObject--CollapseContainer 组件属性
schemaDescItem[]--详情项配置,见下方 DescItem 配置
dataobject--数据源

DescItem

属性类型默认值可选值说明
fieldstring--字段名
labelstring--标签名
labelMinWidthnumber--label 最小宽度
contentMinWidthnumber--content 最小宽度
labelStyleany--label 样式
spannumber--和并列数量
show(data)=>boolean--动态判断当前组件是否显示
render(val: string, data: any)=>VNode,undefined,Element,string,number--自定义渲染 content
+ + + + \ No newline at end of file diff --git a/components/drawer.html b/components/drawer.html new file mode 100644 index 00000000..5c398a9f --- /dev/null +++ b/components/drawer.html @@ -0,0 +1,96 @@ + + + + + + Drawer 抽屉组件 | Vben Admin + + + + + + + + + + + + + + + + +

Drawer 抽屉组件

antv 的 drawer 组件进行封装,扩展拖拽,全屏,自适应高度等功能。

Usage

由于 drawer 内部代码一般独立成单独文件,推荐独立成组件来进行开发,所以示例都是以独立的文件来进行说明

独立组件代码,用于写组件内部的内容

<template>
+  <BasicDrawer v-bind="$attrs" title="Drawer Title" width="50%"> Drawer Info. </BasicDrawer>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicDrawer } from '/@/components/Drawer';
+  export default defineComponent({
+    components: { BasicDrawer },
+  });
+</script>
+

页面引用弹窗

<template>
+  <div>
+    <Drawer @register="register" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Alert } from 'ant-design-vue';
+  import { useDrawer } from '/@/components/Drawer';
+  import Drawer from './Drawer.vue';
+
+  export default defineComponent({
+    components: { Drawer },
+    setup() {
+      const [register, { openDrawer }] = useDrawer();
+      return {
+        register,
+        openDrawer,
+      };
+    },
+  });
+</script>
+

useDrawer

useDrawer 用于操作组件

const [register, { openDrawer, setDrawerProps }] = useDrawer();
+

register

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

原理其实很简单,就是 vue 的组件子传父通信,内部通过 emit("register",instance) 实现。

同时,独立出去的组件需要将 attrs 绑定到 Drawer 的上面。

<BasicDrawer v-bind="$attrs"> Drawer Info. </BasicDrawer>
+

openDrawer

用于打开/关闭弹窗

// true/false: 打开关闭弹窗
+// data: 传递到子组件的数据
+openDrawer(true, data);
+

closeDrawer

用于关闭弹窗

closeDrawer();
+

setDrawerProps

用于更改 drawer 的 props 参数因为 drawer 内容独立成组件,如果在外部页面需要更改 props 可能比较麻烦,所以提供 setDrawerProps 方便更改内部 drawer 的 props

Props 内容可以见下方

setDrawerProps(props);
+

useDrawerInner

用于独立的 Drawer 内部调用

<template>
+  <BasicDrawer v-bind="$attrs" @register="register" title="Drawer Title" width="50%">
+    Drawer Info.
+    <a-button type="primary" @click="closeDrawer">内部关闭drawer</a-button>
+  </BasicDrawer>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+  export default defineComponent({
+    components: { BasicDrawer },
+    setup() {
+      const [register, { closeDrawer }] = useDrawerInner();
+      return { register, closeDrawer };
+    },
+  });
+</script>
+

useModalInner用于操作独立组件

const [register, { closeModal, setModalProps }] = useModalInner(callback);
+

callback

type: (data:any)=>void

回调函数用于接收 openDrawer 第二个参数传递的值

openDrawer((data: any) => {
+  console.log(data);
+});
+

closeDrawer

用于关闭抽屉

closeDrawer();
+

changeOkLoading

用于修改确认按钮的 loading 状态

// true or false
+changeOkLoading(true);
+

changeLoading

用于修改 modal 的 loading 状态

// true or false
+changeLoading(true);
+

setDrawerProps

用于更改 drawer 的 props 参数因为 modal 内容独立成组件,如果在外部页面需要更改 props 可能比较麻烦,所以提供setDrawerProps 方便更改内部 drawer 的 props

Props 内容可以见下方

Props

温馨提醒

除以下参数外,官方文档内的 props 也都支持,具体可以参考 antv drawer

属性类型默认值可选值说明
isDetailbooleanfalse-是否为详情模式
loadingbooleanfalse-loading 状态
loadingTextstring``-loading 文本 s
showDetailBackbooleantrue-isDetail=true 状态下是否显示返回按钮
closeFunc() => Promise<boolean>--自定义关闭函数,返回true关闭,否则不关闭
showFooterboolean--是否显示底部
footerHeightnumber60-底部区域高度

Events

事件回调参数说明
close(e)=>void点击关闭回调
visible-change(visible:boolean)=>void弹窗打开关闭时触发
ok(e)=>void点击确定回调
+ + + + \ No newline at end of file diff --git a/components/excel.html b/components/excel.html new file mode 100644 index 00000000..4b8c6891 --- /dev/null +++ b/components/excel.html @@ -0,0 +1,106 @@ + + + + + + Excel 组件 | Vben Admin + + + + + + + + + + + + + + + + +

Excel 组件

excel 导入导出操作

项目中使用到的是 XLSX,具体文档可以参考XLSX 文档

Import

Usage

<template>
+  <ImpExcel @success="loadDataSuccess">
+    <a-button class="m-3">导入Excel</a-button>
+  </ImpExcel>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { ImpExcel, ExcelData } from '/@/components/Excel';
+  export default defineComponent({
+    components: { ImpExcel },
+    setup() {
+      function loadDataSuccess(excelDataList: ExcelData[]) {
+        tableListRef.value = [];
+        console.log(excelDataList);
+        for (const excelData of excelDataList) {
+          const {
+            header,
+            results,
+            meta: { sheetName },
+          } = excelData;
+          const columns: BasicColumn[] = [];
+          for (const title of header) {
+            columns.push({ title, dataIndex: title });
+          }
+          tableListRef.value.push({ title: sheetName, dataSource: results, columns });
+        }
+      }
+      return {
+        loadDataSuccess,
+      };
+    },
+  });
+</script>
+

Events

事件回调参数说明
success(res:ExcelData)=>void导入成功回调
error()=>void导出错误

ExcelData

属性类型默认值说明
header:string[];table 表头
results:T[];table 数据
meta:{ sheetName: string };table title

Export

具体详情可以参考完整版示例

import { jsonToSheetXlsx, aoaToSheetXlsx } from '/@/components/Excel';
+

数组格式数据导出

import { aoaToSheetXlsx } from '/@/components/Excel';
+// 保证data顺序与header一致
+aoaToSheetXlsx({
+  data: [],
+  header: [],
+  filename: '二维数组方式导出excel.xlsx',
+});
+

自定义导出格式

import { jsonToSheetXlsx } from '/@/components/Excel';
+
+jsonToSheetXlsx({
+  data,
+  filename,
+  write2excelOpts: {
+    // 可以是 xlsx/html/csv/txt
+    bookType,
+  },
+});
+

json 格式导出

import { jsonToSheetXlsx } from '/@/components/Excel';
+
+jsonToSheetXlsx({
+  data,
+  filename: '使用key作为默认头部.xlsx',
+});
+
+jsonToSheetXlsx({
+  data,
+  header: {
+    id: 'ID',
+    name: '姓名',
+    age: '年龄',
+    no: '编号',
+    address: '地址',
+    beginTime: '开始时间',
+    endTime: '结束时间',
+  },
+  filename: '自定义头部.xlsx',
+  json2sheetOpts: {
+    // 指定顺序
+    header: ['name', 'id'],
+  },
+});
+

Function

方法回调参数返回值说明
jsonToSheetXlsxFunction(JsonToSheet)json 格式数据,导出到 excel
aoaToSheetXlsxFunction(AoAToSheet)数组格式,导出到 excel

JsonToSheet Type

属性类型默认值说明
dataT[]JSON 对象数组
header?:T;表头未设置则取 JSON 对象的 key 作为 header
filename?:stringexcel-list.xlsx导出的文件名
json2sheetOpts?:JSON2SheetOpts调用 XLSX.utils.json_to_sheet 的可选参数
write2excelOpts?:WritingOptions{ bookType: 'xlsx' }调用 XLSX.writeFile 的可选参数,具体参 XLSX 文档

AoAToSheet Type

属性类型默认值说明
dataT[][];二维数组
header?:T;表头 ;未设置则没有表头
filename?:string;excel-list.xlsx导出的文件名
write2excelOpts?:WritingOptions;{ bookType: 'xlsx' }调用 XLSX.writeFile 的可选参数
+ + + + \ No newline at end of file diff --git a/components/flow-chart.html b/components/flow-chart.html new file mode 100644 index 00000000..5c09c582 --- /dev/null +++ b/components/flow-chart.html @@ -0,0 +1,47 @@ + + + + + + FlowChart | Vben Admin + + + + + + + + + + + + + + + + +

FlowChart

流程图组件,基于 didi/LogicFlow 的简单封装。详细配置请参考文档 FlowChart

Usage

<template>
+  <FlowChart :data="demoData" />
+</template>
+
+<script lang="ts">
+  import { FlowChart } from '/@/components/FlowChart';
+  import { PageWrapper } from '/@/components/Page';
+
+  import demoData from './dataTurbo.json';
+  export default {
+    components: { FlowChart, PageWrapper },
+    setup() {
+      return { demoData };
+    },
+  };
+</script>
+

Props

属性类型默认值可选值说明
flowOptionsobject--FlowCharts 配置项
dataobject--流程数据
toolbarbooleantrue-是否显示工具栏
patternItems[]--左侧拖拽列表数据
+ + + + \ No newline at end of file diff --git a/components/form.html b/components/form.html new file mode 100644 index 00000000..0d4d4dd9 --- /dev/null +++ b/components/form.html @@ -0,0 +1,463 @@ + + + + + + Form 表单组件 | Vben Admin + + + + + + + + + + + + + + + + +

Form 表单组件

antv 的 form 组件进行封装,扩展一些常用的功能

如果文档内没有,可以尝试在在线示例内寻找

Usage

useForm 方式

下面是一个使用简单表单的示例,只有一个输入框

<template>
+  <div class="m-4">
+    <BasicForm
+      :labelWidth="100"
+      :schemas="schemas"
+      :actionColOptions="{ span: 24 }"
+      @submit="handleSubmit"
+    />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicForm, FormSchema } from '/@/components/Form';
+  import { CollapseContainer } from '/@/components/Container';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  const schemas: FormSchema[] = [
+    {
+      field: 'field',
+      component: 'Input',
+      label: '字段1',
+      colProps: {
+        span: 8,
+      },
+      defaultValue: '1',
+      componentProps: {
+        placeholder: '自定义placeholder',
+        onChange: (e) => {
+          console.log(e);
+        },
+      },
+    },
+  ];
+
+  export default defineComponent({
+    components: { BasicForm, CollapseContainer },
+    setup() {
+      const { createMessage } = useMessage();
+      return {
+        schemas,
+        handleSubmit: (values: any) => {
+          createMessage.success('click search,values:' + JSON.stringify(values));
+        },
+      };
+    },
+  });
+</script>
+

template 方式

所有可调用函数见下方 Methods 说明

<template>
+  <div class="m-4">
+    <BasicForm
+      :schemas="schemas"
+      ref="formElRef"
+      :labelWidth="100"
+      @submit="handleSubmit"
+      :actionColOptions="{ span: 24 }"
+    />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { BasicForm, FormSchema, FormActionType, FormProps } from '/@/components/Form';
+  import { CollapseContainer } from '/@/components/Container';
+  const schemas: FormSchema[] = [];
+
+  export default defineComponent({
+    components: { BasicForm, CollapseContainer },
+    setup() {
+      const formElRef = ref<Nullable<FormActionType>>(null);
+      return {
+        formElRef,
+        schemas,
+        setProps(props: FormProps) {
+          const formEl = formElRef.value;
+          if (!formEl) return;
+          formEl.setProps(props);
+        },
+      };
+    },
+  });
+</script>
+

useForm

form 组件还提供了 useForm,方便调用函数内部方法

示例

<template>
+  <BasicForm @register="register" @submit="handleSubmit" />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { CollapseContainer } from '/@/components/Container/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  const schemas: FormSchema[] = [
+    {
+      field: 'field1',
+      component: 'Input',
+      label: '字段1',
+      colProps: {
+        span: 8,
+      },
+      componentProps: {
+        placeholder: '自定义placeholder',
+        onChange: (e: any) => {
+          console.log(e);
+        },
+      },
+    },
+  ];
+
+  export default defineComponent({
+    components: { BasicForm, CollapseContainer },
+    setup() {
+      const { createMessage } = useMessage();
+      const [register, { setProps }] = useForm({
+        labelWidth: 120,
+        schemas,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      return {
+        register,
+        schemas,
+        handleSubmit: (values: any) => {
+          createMessage.success('click search,values:' + JSON.stringify(values));
+        },
+        setProps,
+      };
+    },
+  });
+</script>
+

参数介绍

const [register, methods] = useForm(props);
+

参数 props 内的值可以是 computed 或者 ref 类型

register

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

<template>
+  <BasicForm @register="register" @submit="handleSubmit" />
+</template>
+<script>
+  export default defineComponent({
+    components: { BasicForm },
+    setup() {
+      const [register] = useForm();
+      return {
+        register,
+      };
+    },
+  });
+</script>
+

Methods见下方说明

Methods

getFieldsValue

类型: () => Recordable;

说明: 获取表单值

setFieldsValue

类型: <T>(values: T) => Promise<void>

说明: 设置表单字段值

resetFields

类型: ()=> Promise<void>

说明: 重置表单值

validateFields

类型: (nameList?: NamePath[]) => Promise<any>

说明: 校验指定表单项

validate

类型: (nameList?: NamePath[]) => Promise<any>

说明: 校验整个表单

submit

类型: () => Promise<void>

说明: 提交表单

scrollToField

类型: (name: NamePath, options?: ScrollOptions) => Promise<void>

说明: 滚动到对应字段位置

clearValidate

类型: (name?: string | string[]) => Promise<void>

说明: 清空校验

setProps

TIP

设置表单的 props 可以直接在标签上传递,也可以使用 setProps,或者初始化直接写 useForm(props)

类型: (formProps: Partial<FormProps>) => Promise<void>

说明: 设置表单 Props

removeSchemaByField

类型: (field: string | string[]) => Promise<void>

说明: 根据 field 删除 Schema

appendSchemaByField

类型: ( schema: FormSchema, prefixField: string | undefined, first?: boolean | undefined ) => Promise<void>

说明: 插入到指定 filed 后面,如果没传指定 field,则插入到最后,当 first = true 时插入到第一个位置

updateSchema

类型: (data: Partial<FormSchema> | Partial<FormSchema>[]) => Promise<void>

说明: 更新表单的 schema, 只更新函数所传的参数

e.g

updateSchema({ field: 'filed', componentProps: { disabled: true } });
+updateSchema([
+  { field: 'filed', componentProps: { disabled: true } },
+  { field: 'filed1', componentProps: { disabled: false } },
+]);
+

Props

温馨提醒

除以下参数外,官方文档内的 props 也都支持,具体可以参考 antv form

属性类型默认值可选值说明版本
schemasSchema[]--表单配置,见下方 FormSchema 配置
submitOnResetbooleanfalse-重置时是否提交表单
labelColPartial<ColEx>--整个表单通用 LabelCol 配置
wrapperColPartial<ColEx>--整个表单通用 wrapperCol 配置
baseColPropsPartial<ColEx>--配置所有选子项的 ColProps,不需要逐个配置,子项也可单独配置优先与全局
baseRowStyleobject--配置所有 Row 的 style 样式
labelWidthnumber , string--扩展 form 组件,增加 label 宽度,表单内所有组件适用,可以单独在某个项覆盖或者禁用
labelAlignstring-left,rightlabel 布局
mergeDynamicDataobject--额外传递到子组件的参数 values
autoFocusFirstItembooleanfalse-是否聚焦第一个输入框,只在第一个表单项为 input 的时候作用
compactbooleanfalsetrue/false紧凑类型表单,减少 margin-bottom
sizestringdefault'default' , 'small' , 'large'向表单内所有组件传递 size 参数,自定义组件需自行实现 size 接收
disabledbooleanfalsetrue/false向表单内所有组件传递 disabled 属性,自定义组件需自行实现 disabled 接收
autoSetPlaceHolderbooleantrue true/false自动设置表单内组件的 placeholder,自定义组件需自行实现
autoSubmitOnEnterbooleanfalse true/false在 input 中输入时按回车自动提交2.4.0
rulesMessageJoinLabelbooleanfalsetrue/false如果表单项有校验,会自动生成校验信息,该参数控制是否将字段中文名字拼接到自动生成的信息后方
showAdvancedButtonbooleanfalsetrue/false是否显示收起展开按钮
emptySpannumber , Partial<ColEx>0-空白行格,可以是数值或者 col 对象 数
autoAdvancedLinenumber3-如果 showAdvancedButton 为 true,超过指定行数行默认折叠
alwaysShowLinesnumber1-折叠时始终保持显示的行数2.7.1
showActionButtonGroupbooleantruetrue/false是否显示操作按钮(重置/提交)
actionColOptionsPartial<ColEx>--操作按钮外层 Col 组件配置,如果开启 showAdvancedButton,则不用设置,具体见下方 actionColOptions
showResetButtonbooleantrue-是否显示重置按钮
resetButtonOptionsobject-重置按钮配置见下方 ActionButtonOption
showSubmitButtonbooleantrue-是否显示提交按钮
submitButtonOptionsobject-确认按钮配置见下方 ActionButtonOption
resetFunc () => Promise<void>-重置表单行为前执行自定义重置按钮逻辑() => Promise<void>;
submitFunc () => Promise<void>-自定义提交按钮逻辑() => Promise<void>;
fieldMapToTime[string, [string, string], string?][]'timestamp' ,'timestampStartDay' ,momentjs 时间格式用于将表单内时间区域的应设成 2 个字段,见下方说明

ColEx

src/components/Form/src/types/index.ts

ActionButtonOption

BasicButtonProps

export interface ButtonProps extends BasicButtonProps {
+  text?: string;
+}
+

fieldMapToTime

将表单内时间区域的值映射成 2 个字段

如果表单内有时间区间组件,获取到的值是一个数组,但是往往我们传递到后台需要是 2 个字段

useForm({
+  fieldMapToTime: [
+    // data为时间组件在表单内的字段,startTime,endTime为转化后的开始时间与结束时间
+    // 'YYYY-MM-DD'为时间格式,参考moment
+    ['datetime', ['startTime', 'endTime'], 'YYYY-MM-DD'],
+    // 支持多个字段
+    ['datetime1', ['startTime1', 'endTime1'], 'YYYY-MM-DD HH:mm:ss'],
+  ],
+});
+
+// fieldMapToTime没写的时候表单获取到的值
+{
+  datetime: [Date(),Date()]
+}
+//  ['datetime', ['startTime', 'endTime'], 'YYYY-MM-DD'],等同于 dayjs(Date()).format('YYYY-MM-DD'). 之后
+{
+    startTime: '2020-08-12',
+    endTime: '2020-08-15',
+}
+
+// ['datetime', ['startTime', 'endTime'], 'timestamp'],等同于 dayjs(Date()).unix(). 之后
+{
+    startTime: 1597190400,
+    endTime: 1597449600,
+}
+
+// ['datetime', ['startTime', 'endTime'], 'timestampStartDay'],等同于 dayjs(Date()).startOf('day').unix(). 之后
+{
+    startTime: 1597190400,
+    endTime: 1597449600,
+}
+

FormSchema

属性类型默认值可选值说明
fieldstring--字段名
labelstring--标签名
subLabelstring--二级标签名灰色
suffixstring , number , ((values: RenderCallbackParams) => string / number);--组件后面的内容
changeEventstring--表单更新事件名称
helpMessagestring , string[]--标签名右侧温馨提示
helpComponentPropsHelpComponentProps--标签名右侧温馨提示组件 props,见下方 HelpComponentProps
labelWidthstring , number--覆盖统一设置的 labelWidth
disabledLabelWidthbooleanfalsetrue/false禁用 form 全局设置的 labelWidth,自己手动设置 labelCol 和 wrapperCol
componentstring--组件类型,见下方 ComponentType
componentPropsany,()=>{}--所渲染的组件的 props
rulesValidationRule[]--校验规则,见下方 ValidationRule
requiredboolean--简化 rules 配置,为 true 则转化成 [{required:true}]。2.4.0之前的版本只支持 string 类型的值
rulesMessageJoinLabelbooleanfalse-校验信息是否加入 label
itemPropsany--参考下方 FormItem
colPropsColEx--参考上方 actionColOptions
defaultValueobject--所渲渲染组件的初始值
render(renderCallbackParams: RenderCallbackParams) => VNode / VNode[] / string--自定义渲染组件
renderColContent(renderCallbackParams: RenderCallbackParams) => VNode / VNode[] / string--自定义渲染组件(需要自行包含 formItem)
renderComponentContent(renderCallbackParams: RenderCallbackParams) => any / string--自定义渲染组内部的 slot
slotstring--自定义 slot,渲染组件
colSlotstring--自定义 slot,渲染组件 (需要自行包含 formItem)
show boolean / ((renderCallbackParams: RenderCallbackParams) => boolean)--动态判断当前组件是否显示,css 控制,不会删除 dom
ifShow boolean / ((renderCallbackParams: RenderCallbackParams) => boolean)--动态判断当前组件是否显示,js 控制,会删除 dom
dynamicDisabledboolean / ((renderCallbackParams: RenderCallbackParams) => boolean) --动态判断当前组件是否禁用
dynamicRulesboolean / ((renderCallbackParams: RenderCallbackParams) => boolean)--动态判返当前组件你校验规则

RenderCallbackParams

export interface RenderCallbackParams {
+  schema: FormSchema;
+  values: any;
+  model: any;
+  field: string;
+}
+

componentProps

  • 当值为对象类型时,该对象将作为component所对应组件的的 props 传入组件

  • 当值为一个函数时候

参数有 4 个

schema: 表单的整个 schemas

formActionType: 操作表单的函数。与 useForm 返回的操作函数一致

formModel: 表单的双向绑定对象,这个值是响应式的。所以可以方便处理很多操作

tableAction: 操作表格的函数,与 useTable 返回的操作函数一致。注意该参数只在表格内开启搜索表单的时候有值,其余情况为null,

{
+  // 简单例子,值改变的时候操作表格或者修改表单内其他元素的值
+  component:'Input',
+  componentProps: ({ schema, tableAction, formActionType, formModel }) => {
+    return {
+      // xxxx props
+      onChange:e=>{
+        const {reload}=tableAction
+        reload()
+        // or
+        formModel.xxx='123'
+      }
+    };
+  };
+}
+

HelpComponentProps

export interface HelpComponentProps {
+  maxWidth: string;
+  // 是否显示序号
+  showIndex: boolean;
+  // 文本列表
+  text: any;
+  // 颜色
+  color: string;
+  // 字体大小
+  fontSize: string;
+  icon: string;
+  absolute: boolean;
+  // 定位
+  position: any;
+}
+

ComponentType

schema 内组件的可选类型

export type ComponentType =
+  | 'Input'
+  | 'InputGroup'
+  | 'InputPassword'
+  | 'InputSearch'
+  | 'InputTextArea'
+  | 'InputNumber'
+  | 'InputCountDown'
+  | 'Select'
+  | 'ApiSelect'
+  | 'TreeSelect'
+  | 'RadioButtonGroup'
+  | 'RadioGroup'
+  | 'Checkbox'
+  | 'CheckboxGroup'
+  | 'AutoComplete'
+  | 'Cascader'
+  | 'DatePicker'
+  | 'MonthPicker'
+  | 'RangePicker'
+  | 'WeekPicker'
+  | 'TimePicker'
+  | 'Switch'
+  | 'StrengthMeter'
+  | 'Upload'
+  | 'IconPicker'
+  | 'Render'
+  | 'Slider'
+  | 'Rate'
+  | 'Divider'; // v2.7.2新增
+

Divider schema 说明

Divider类型用于在schemas中占位,将会渲染成一个分割线(始终占一整行的版面),可以用于较长表单的版面分隔。请只将 Divider 类型的 schema 当作一个分割线,而不是一个常规的表单字段。

  • Divider仅在showAdvancedButton为 false 时才会显示(也就是说如果启用了表单收起和展开功能,Divider将不会显示)
  • Divider 使用schema中的label以及helpMessage来渲染分割线中的提示内容
  • Divider 可以使用componentProps来设置除type之外的 props
  • Divider 不会渲染AFormItem,因此schema中除labelcomponentPropshelpMessagehelpComponentProps以外的属性不会被用到

自行添加需要的组件类型

src/components/Form/src/componentMap.ts 内,添加需要的组件,并在上方 ComponentType 添加相应的类型 key

方式 1

这种写法适用与适用频率较高的组件

componentMap.set('componentName', 组件);
+
+// ComponentType
+export type ComponentType = xxxx | 'componentName';
+

方式 2

使用 useComponentRegister 进行注册

这种写法只能在当前页使用,页面销毁之后会从 componentMap 删除相应的组件

import { useComponentRegister } from '@/components/form/index';
+
+import { StrengthMeter } from '@/components/strength-meter/index';
+
+useComponentRegister('StrengthMeter', StrengthMeter);
+

提示

方式 2 出现的原因是为了减少打包体积,如果某个组件体积很大,用方式 1 的话可能会使首屏体积增加

render

自定义渲染内容

<template>
+  <div class="m-4">
+    <BasicForm @register="register" @submit="handleSubmit" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, h } from 'vue';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { Input } from 'ant-design-vue';
+  const schemas: FormSchema[] = [
+    {
+      field: 'field1',
+      component: 'Input',
+      label: '字段1',
+      colProps: {
+        span: 8,
+      },
+      rules: [{ required: true }],
+      render: ({ model, field }) => {
+        return h(Input, {
+          placeholder: '请输入',
+          value: model[field],
+          onChange: (e: ChangeEvent) => {
+            model[field] = e.target.value;
+          },
+        });
+      },
+    },
+    {
+      field: 'field2',
+      component: 'Input',
+      label: '字段2',
+      colProps: {
+        span: 8,
+      },
+      rules: [{ required: true }],
+      renderComponentContent: () => {
+        return {
+          suffix: () => 'suffix',
+        };
+      },
+    },
+  ];
+  export default defineComponent({
+    components: { BasicForm },
+    setup() {
+      const { createMessage } = useMessage();
+      const [register, { setProps }] = useForm({
+        labelWidth: 120,
+        schemas,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      return {
+        register,
+        schemas,
+        handleSubmit: (values: any) => {
+          createMessage.success('click search,values:' + JSON.stringify(values));
+        },
+        setProps,
+      };
+    },
+  });
+</script>
+

slot

自定义渲染内容

提示

使用插槽自定义表单域时,请注意 antdv 有关 FormItem 的相关说明

<template>
+  <div class="m-4">
+    <BasicForm @register="register">
+      <template #customSlot="{ model, field }">
+        <a-input v-model:value="model[field]" />
+      </template>
+    </BasicForm>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'compatible-vue';
+  import { BasicForm, useForm } from '@/components/Form/index';
+  import { BasicModal } from '@/components/modal/index';
+  export default defineComponent({
+    name: 'FormDemo',
+    setup(props) {
+      const [register] = useForm({
+        labelWidth: 100,
+        actionColOptions: {
+          span: 24,
+        },
+        schemas: [
+          {
+            field: 'field1',
+            label: '字段1',
+            slot: 'customSlot',
+          },
+        ],
+      });
+      return {
+        register,
+      };
+    },
+  });
+</script>
+

ifShow/show/dynamicDisabled

自定义显示/禁用

<template>
+  <div class="m-4">
+    <BasicForm @register="register" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  const schemas: FormSchema[] = [
+    {
+      field: 'field1',
+      component: 'Input',
+      label: '字段1',
+      colProps: {
+        span: 8,
+      },
+      show: ({ values }) => {
+        return !!values.field5;
+      },
+    },
+    {
+      field: 'field2',
+      component: 'Input',
+      label: '字段2',
+      colProps: {
+        span: 8,
+      },
+      ifShow: ({ values }) => {
+        return !!values.field6;
+      },
+    },
+    {
+      field: 'field3',
+      component: 'DatePicker',
+      label: '字段3',
+      colProps: {
+        span: 8,
+      },
+      dynamicDisabled: ({ values }) => {
+        return !!values.field7;
+      },
+    },
+  ];
+
+  export default defineComponent({
+    components: { BasicForm },
+    setup() {
+      const [register, { setProps }] = useForm({
+        labelWidth: 120,
+        schemas,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      return {
+        register,
+        schemas,
+        setProps,
+      };
+    },
+  });
+</script>
+

antv form

Slots

名称说明
formFooter表单底部区域
formHeader表单顶部区域
resetBefore重置按钮前
submitBefore提交按钮前
advanceBefore展开按钮前
advanceAfter展开按钮后

ApiSelect

远程下拉加载组件,该组件可以用于学习参考如何自定义组件集成到 Form 组件内,将自定义组件交由 Form 去管理

Usage

const schemas: FormSchema[] = [
+  {
+    field: 'field',
+    component: 'ApiSelect',
+    label: '字段',
+  },
+];
+

Props

属性类型默认值说明
numberToStringbooleanfalse是否将number值转化为string
api()=>Promise<{ label: string; value: string; disabled?: boolean }[]>-数据接口,接受一个 Promise 对象
paramsobject-接口参数。此属性改变时会自动重新加载接口数据
resultFieldstring-接口返回的字段,如果接口返回数组,可以不填。支持x.x.x格式
labelFieldstringlabel下拉数组项内label显示文本的字段,支持x.x.x格式
valueFieldstringvalue下拉数组项内value实际值的字段,支持x.x.x格式
immediatebooleantrue是否立即请求接口,否则将在第一次点击时候触发请求

ApiTreeSelect

远程下拉树加载组件,和ApiSelect类似,2.6.1 以上版本

Props

属性类型默认值说明
api()=>Promise<{ label: string; value: string; children?: any[] }[]>-数据接口,接受一个 Promise 对象
paramsobject-接口参数。此属性改变时会自动重新加载接口数据
resultFieldstring-接口返回的字段,如果接口返回数组,可以不填。支持x.x.x格式
immediatebooleantrue是否立即请求接口。

RadioButtonGroup

Radio Button 风格的选择按钮

Usage

const schemas: FormSchema[] = [
+  {
+    field: 'field',
+    component: 'RadioButtonGroup',
+    label: '字段',
+  },
+];
+

Props

属性类型默认值说明
options{ label: string; value: string; disabled?: boolean }[]-数据字段
+ + + + \ No newline at end of file diff --git a/components/functional/context-menu.html b/components/functional/context-menu.html new file mode 100644 index 00000000..f05d9477 --- /dev/null +++ b/components/functional/context-menu.html @@ -0,0 +1,71 @@ + + + + + + ContextMenu | Vben Admin + + + + + + + + + + + + + + + + +

ContextMenu

函数式创建右键菜单组件, 只要能拿到 dom 的 event 对象就能为其创建右键菜单。

Usage

<template>
+  <div>
+    <a-button type="primary" @contextmenu="handleContext">Right Click on me</a-button>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { useContextMenu } from '/@/hooks/web/useContextMenu';
+  import { CollapseContainer } from '/@/components/Container';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  export default defineComponent({
+    components: { CollapseContainer },
+    setup() {
+      const [createContextMenu] = useContextMenu();
+      const { createMessage } = useMessage();
+      function handleContext(e: MouseEvent) {
+        createContextMenu({
+          event: e,
+          items: [
+            {
+              label: 'New',
+              icon: 'ant-design:plus-outlined',
+              handler: () => {
+                createMessage.success('click new');
+              },
+            },
+            {
+              label: 'Open',
+              icon: 'ant-design:folder-open-filled',
+              handler: () => {
+                createMessage.success('click open');
+              },
+            },
+          ],
+        });
+      }
+      return { handleContext };
+    },
+  });
+</script>
+

createContextMenu

Options

属性类型默认值可选值说明
eventEvent--需要创建的 dom 的 Event 对象
itemsContextMenuItem[]--右键菜单列表,ContextMenuItem见下方说明

ContextMenuItem

属性类型说明
labelstring文本
iconstring图标,参考图标组件
disabledboolean是否禁用
handler()=>void点击触发函数
+ + + + \ No newline at end of file diff --git a/components/functional/loading.html b/components/functional/loading.html new file mode 100644 index 00000000..67e6daf0 --- /dev/null +++ b/components/functional/loading.html @@ -0,0 +1,84 @@ + + + + + + Loading | Vben Admin + + + + + + + + + + + + + + + + +

Loading

Usage

<template>
+  <div class="p-5" ref="wrapEl" v-loading="loadingRef" loading-tip="加载中...">
+    <a-alert message="函数方式" />
+
+    <a-button class="my-4 mr-4" type="primary" @click="openFnFullLoading">全屏 Loading</a-button>
+    <a-button class="my-4" type="primary" @click="openFnWrapLoading">容器内 Loading</a-button>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, reactive, toRefs, ref } from 'vue';
+  import { Loading, useLoading } from '/@/components/Loading';
+  export default defineComponent({
+    components: { Loading },
+    setup() {
+      const [openFullLoading, closeFullLoading] = useLoading({
+        tip: '加载中...',
+      });
+
+      const [openWrapLoading, closeWrapLoading] = useLoading({
+        target: wrapEl,
+        props: {
+          tip: '加载中...',
+          absolute: true,
+        },
+      });
+
+      function openFnFullLoading() {
+        openFullLoading();
+
+        setTimeout(() => {
+          closeFullLoading();
+        }, 2000);
+      }
+
+      function openFnWrapLoading() {
+        openWrapLoading();
+
+        setTimeout(() => {
+          closeWrapLoading();
+        }, 2000);
+      }
+
+      return {
+        openFnFullLoading,
+        openFnWrapLoading,
+        ...toRefs(compState),
+      };
+    },
+  });
+</script>
+

useLoading

使用

import { useLoading } from '/@/components/Loading';
+
+const [open, close, setTip] = useLoading(opt: Partial<LoadingProps> | Partial<UseLoadingOptions>);
+

UseLoadingOptions

属性类型默认值可选值说明
targetHTMLElement or Ref<HTMLElement>--挂载的 dom 节点
propsLoadingProps--loading 组件参数

LoadingProps

属性类型默认值可选值说明
tipstring--加载文本
sizedefault, small , largedefault-大小
absolutebooleanfalse-绝对定位,为 false 时可以全屏
loadingboolean--当前加载状态
backgroundstring--背景色,
theme'dark' or 'light'light-背景色主题 ,当背景色不为空时使用背景色

返回值

open

打开 loading

close

关闭 loading

setTip

设置加在提示文案(v2.6.2以上版本)

+ + + + \ No newline at end of file diff --git a/components/functional/preview.html b/components/functional/preview.html new file mode 100644 index 00000000..dc637e70 --- /dev/null +++ b/components/functional/preview.html @@ -0,0 +1,78 @@ + + + + + + Preview | Vben Admin + + + + + + + + + + + + + + + + +

Preview

将图片预览组件组件函数化。通过函数方便创建组件

Usage

<template>
+  <div class="p-4">
+    <Alert message="有预览图" type="info" />
+    <div class="flex justify-center mt-4">
+      <img :src="img" v-for="img in imgList" :key="img" class="mr-2" @click="handleClick(img)" />
+    </div>
+    <Alert message="无预览图" type="info" />
+    <a-button @click="handlePreview" type="primary" class="mt-4">预览图片</a-button>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Alert } from 'ant-design-vue';
+  import { createImgPreview } from '/@/components/Preview/index';
+  const imgList: string[] = [
+    'https://picsum.photos/id/66/346/216',
+    'https://picsum.photos/id/67/346/216',
+    'https://picsum.photos/id/68/346/216',
+  ];
+  export default defineComponent({
+    components: { Alert },
+    setup() {
+      function handleClick(img: string) {
+        createImgPreview({ imageList: [img] });
+      }
+
+      function handlePreview() {
+        createImgPreview({ imageList: imgList });
+      }
+      return { imgList, handleClick, handlePreview };
+    },
+  });
+</script>
+

createImgPreview

参数/Options

属性类型默认值可选值说明
imgListstring[]--图片列表
indexnumber0-初始预览时的图片索引
scaleStepnumber--缩放步进值(每次缩放的幅度)。默认为自动(当前缩放值的10%)
defaultWidthnumber--默认宽度(单位px)。当提供此值时,所有图片初始时都会被缩放至此宽度
maskClosablebooleanfalsetrue/false点击遮罩时是否自动关闭预览
rememberStatebooleanfalsetrue/false是否记住每张图片各自的缩放状态
onImgLoad({ index: number, url: string, dom: HTMLImageElement }) => void--图片加载成功时的回调函数
onImgError({ index: number, url: string, dom: HTMLImageElement }) => void--图片加载失败时的回调函数

返回值/PreviewActions

可用于控制当前预览状态

interface PreviewActions {
+  // 重置状态
+  resume: () => void;
+  // 关闭预览
+  close: () => void;
+  // 显示前一张
+  prev: () => void;
+  // 显示后一张
+  next: () => void;
+  // 设置缩放比例(针对当前图片)
+  setScale: (scale: number) => void;
+  // 设置旋转角度(针对当前图片)
+  setRotate: (rotate: number) => void;
+}
+
+ + + + \ No newline at end of file diff --git a/components/glob/button.html b/components/glob/button.html new file mode 100644 index 00000000..3dbc49ee --- /dev/null +++ b/components/glob/button.html @@ -0,0 +1,36 @@ + + + + + + button 按钮 | Vben Admin + + + + + + + + + + + + + + + + +

button 按钮

二次封装按钮组件,且使用相同的组件名替换全局的 a-button 组件

TIP

  • 按钮不需要 import,已经全局注册,直接使用 a-button 标签即可
  • 如果是 Tsx 文件,需要手动 import

Usage

<template>
+  <a-button color="success">成功按钮</a-button>
+  <a-button color="error">错误按钮</a-button>
+  <a-button color="warning">警告按钮</a-button>
+</template>
+

Props

提示

保持 ant design button 组件 原有功能的情况下扩展以下属性

属性类型默认值说明
color'error','warning', 'success'-按钮的颜色场景状态颜色,
preIconstring-按钮文本前图标,参考 Icon 组件
postIconstring-按钮文本后图标,参考 Icon 组件
iconSizenumber14按钮图标大小
+ + + + \ No newline at end of file diff --git a/components/icon.html b/components/icon.html new file mode 100644 index 00000000..1bbcdb92 --- /dev/null +++ b/components/icon.html @@ -0,0 +1,66 @@ + + + + + + icon 图标组件 | Vben Admin + + + + + + + + + + + + + + + + +

icon 图标组件

Icon

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

icon 组件位于 src/components/Icon

TIP

icon 的值可以在 IconifyNetlify 上查询

Usage

<template>
+  <Icon icon="gg:loadbar-doc"></Icon>
+</template>
+
+<script>
+  import { defineComponent } from 'vue';
+  import { Icon } from '/@/components/Icon';
+  export default defineComponent({
+    components: { Icon },
+  });
+</script>
+

Props

属性类型默认值说明
iconstring-图标名
colorstring-图标颜色
sizenumber16图标大小
prefixstring-图标前缀

提示

如果 icon 值以 |svg 结尾,则会渲染成 SvgIcon 组件

SvgIcon

用于使用项目 svg 雪碧图

Usage

<template>
+  <div>
+    <SvgIcon name="test"> </SvgIcon>
+  </div>
+</template>
+<script>
+  import { SvgIcon } from '/@/components/Icon';
+  import { defineComponent } from 'vue';
+  export default defineComponent({
+    components: { SvgIcon },
+  });
+</script>
+

Props

属性类型默认值说明
namestring-svg 图标名
sizenumber16图标大小

IconPicker

本组件详细说明请参阅图标选择器

Usage

<template>
+  <div>
+    <IconPicker />
+  </div>
+</template>
+<script>
+  import { IconPicker } from '/@/components/Icon';
+  import { defineComponent } from 'vue';
+  export default defineComponent({
+    components: { IconPicker },
+  });
+</script>
+

Props

属性类型默认值说明
widthstring100%宽度
pageSizenumber140每页显示的图标数
copybooleanfalse是否可以复制
modestringiconify备选图标池,为 svg 时,会读取所有 svg sprite 图标。详见下方说明

mode 说明

  • modeiconify时,会使用预生成的图标集数据作为备选图标池
  • modesvg时,会使用 /src/assets/icons 下的所有svg图标(可包含一级子目录)作为备选图标池,详见vite-plugin-svg-icons
+ + + + \ No newline at end of file diff --git a/components/introduction.html b/components/introduction.html new file mode 100644 index 00000000..74cd075b --- /dev/null +++ b/components/introduction.html @@ -0,0 +1,45 @@ + + + + + + 前言 | Vben Admin + + + + + + + + + + + + + + + + +

前言

注意事项

组件的 defaultXXX 属性不要使用,ant-design-vue 2.2 版本之后将会逐步移除。二次封装的组件也不兼容 defaultXXX 属性。

Usage

该项目的组件大部分没有进行全局注册。采用了按需引入注册方式,如下

<template>
+  <ConfigProvider>
+    <router-view />
+  </ConfigProvider>
+</template>
+
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { ConfigProvider } from 'ant-design-vue';
+  export default defineComponent({
+    name: 'App',
+    components: { ConfigProvider },
+  });
+</script>
+
+ + + + \ No newline at end of file diff --git a/components/json-preview.html b/components/json-preview.html new file mode 100644 index 00000000..65ad85d6 --- /dev/null +++ b/components/json-preview.html @@ -0,0 +1,47 @@ + + + + + + JsonPreview | Vben Admin + + + + + + + + + + + + + + + + +

JsonPreview

json 数据预览组件

Usage

<template>
+  <JsonPreview :data="data" />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { JsonPreview } from '/@/components/CodeEditor';
+
+  export default defineComponent({
+    components: { JsonPreview },
+    setup() {
+      return {
+        data: {},
+      };
+    },
+  });
+</script>
+

Props

属性类型默认值可选值说明
dataobject--需要预览的 Json 数据
+ + + + \ No newline at end of file diff --git a/components/lazy-container.html b/components/lazy-container.html new file mode 100644 index 00000000..5c7a92fe --- /dev/null +++ b/components/lazy-container.html @@ -0,0 +1,73 @@ + + + + + + LazyContainer | Vben Admin + + + + + + + + + + + + + + + + +

LazyContainer

延时加载/懒加载组件, 只在组件可见或者延迟一段时间才进行加载

Usage

<template>
+  <div class="p-4 lazy-base-demo">
+    <div class="lazy-base-demo-wrap">
+      <h1>向下滚动</h1>
+      <LazyContainer @init="() => {}">
+        <TargetContent />
+        <template #skeleton>
+          <Skeleton :rows="10" />
+        </template>
+      </LazyContainer>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Skeleton } from 'ant-design-vue';
+  import TargetContent from './TargetContent.vue';
+  import { LazyContainer } from '/@/components/Container/index';
+  export default defineComponent({
+    components: { LazyContainer, TargetContent, Skeleton },
+  });
+</script>
+<style lang="less" scoped>
+  .lazy-base-demo {
+    &-wrap {
+      display: flex;
+      width: 50%;
+      height: 2000px;
+      margin: 20px auto;
+      text-align: center;
+      background: #fff;
+      justify-content: center;
+      flex-direction: column;
+      align-items: center;
+    }
+
+    h1 {
+      height: 1300px;
+      margin: 20px 0;
+    }
+  }
+</style>
+

Props

属性类型默认值可选值说明
timeoutnumber--等待时间,如果指定了时间,不论可见与否,在指定时间之后自动加载
viewportHTMLElement--组件所在的视口,如果组件是在页面容器内滚动,视口就是该容器
thresholdstring0px-预加载阈值, css 单位
direction'vertical', 'horizontal' , vertical-视口的滚动方向, vertical 代表垂直方向,horizontal 代表水平方向
tagstring'div-包裹组件的外层容器的标签名
transitionNamestring'lazy-container-transition 动画 name
maxWaitingTimenumber'80-最大等待时间

Events

事件回调参数说明
init()=>void初始化之后

Slots

名称说明
default默认区域
skeleton懒加载骨架屏
+ + + + \ No newline at end of file diff --git a/components/loading.html b/components/loading.html new file mode 100644 index 00000000..02c10083 --- /dev/null +++ b/components/loading.html @@ -0,0 +1,74 @@ + + + + + + Loading | Vben Admin + + + + + + + + + + + + + + + + +

Loading

Usage

<template>
+  <div class="p-5" ref="wrapEl" v-loading="loadingRef" loading-tip="加载中...">
+    <a-button class="my-4 mr-4" type="primary" @click="openCompFullLoading">全屏 Loading</a-button>
+    <a-button class="my-4" type="primary" @click="openCompAbsolute">容器内 Loading</a-button>
+    <Loading :loading="loading" :absolute="absolute" :tip="tip" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, reactive, toRefs, ref } from 'vue';
+  import { Loading } from '/@/components/Loading';
+  export default defineComponent({
+    components: { Loading },
+    setup() {
+      const compState = reactive({
+        absolute: false,
+        loading: false,
+        tip: '加载中...',
+      });
+
+      function openLoading(absolute: boolean) {
+        compState.absolute = absolute;
+        compState.loading = true;
+        setTimeout(() => {
+          compState.loading = false;
+        }, 2000);
+      }
+
+      function openCompFullLoading() {
+        openLoading(false);
+      }
+
+      function openCompAbsolute() {
+        openLoading(true);
+      }
+
+      return {
+        openCompFullLoading,
+        openCompAbsolute,
+        ...toRefs(compState),
+      };
+    },
+  });
+</script>
+

Props

属性类型默认值可选值说明
tipstring--加载文本
sizedefault, small , largedefault-大小
absolutebooleanfalse-绝对定位,为 false 时可以全屏
loadingboolean--当前加载状态
backgroundstring--背景色
theme'dark' or 'light'light-背景色主题,当背景色不为空时使用背景色
+ + + + \ No newline at end of file diff --git a/components/markdown.html b/components/markdown.html new file mode 100644 index 00000000..e468bcdf --- /dev/null +++ b/components/markdown.html @@ -0,0 +1,64 @@ + + + + + + Markdown | Vben Admin + + + + + + + + + + + + + + + + +

Markdown

基于 Vditor 的 MarkDown 编辑器

Usage

<template>
+  <div class="p-4">
+    <a-button @click="toggleTheme" class="mb-2" type="primary">黑暗主题</a-button>
+    <MarkDown v-model:value="value" ref="markDownRef" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  import { MarkDown, MarkDownActionType } from '/@/components/Markdown';
+  export default defineComponent({
+    components: { MarkDown },
+    setup() {
+      const markDownRef = ref<Nullable<MarkDownActionType>>(null);
+      const valueRef = ref(`
+# title
+
+# content
+`);
+
+      function toggleTheme() {
+        const markDown = unref(markDownRef);
+        if (!markDown) return;
+        const vditor = markDown.getVditor();
+        vditor.setTheme('dark');
+      }
+      return {
+        value: valueRef,
+        toggleTheme,
+        markDownRef,
+      };
+    },
+  });
+</script>
+

Props

TIP

除以下两个外,props 还可以传入 vidtor 的所有属性。可用 v-bind 统一绑定

属性类型默认值可选值说明
v-modelstring--双向绑定文本值
heightnumber--高度

Methods

名称回调参数说明
getVditorFunction获取 vditor 实例
+ + + + \ No newline at end of file diff --git a/components/modal.html b/components/modal.html new file mode 100644 index 00000000..23d634ff --- /dev/null +++ b/components/modal.html @@ -0,0 +1,114 @@ + + + + + + Modal 弹窗 | Vben Admin + + + + + + + + + + + + + + + + +

Modal 弹窗

对 antv 的 modal 组件进行封装,扩展拖拽,全屏,自适应高度等功能

代码路径 src/components/Modal

Usage

由于弹窗内代码一般作为单文件组件存在,也推荐这样做,所以示例都为单文件组件形式

TIP

注意 v-bind="$attrs"记得写,用于将弹窗组件的 attribute 传入 BasicModal 组件

// Modal.vue
+<template>
+  <BasicModal v-bind="$attrs" title="Modal Title" :helpMessage="['提示1', '提示2']">
+    Modal Info.
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicModal } from '/@/components/Modal';
+  export default defineComponent({
+    components: { BasicModal },
+    setup() {
+      return {};
+    },
+  });
+</script>
+

页面引用弹窗

// Page.vue
+<template>
+  <div class="px-10">
+    <Modal @register="register" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { useModal } from '/@/components/Modal';
+  import Modal from './Modal.vue';
+  export default defineComponent({
+    components: { Modal },
+    setup() {
+      const [register, { openModal }] = useModal();
+      return {
+        register,
+        openModal,
+      };
+    },
+  });
+</script>
+

useModal

用于外部组件调用

useModal 用于操作组件

const [register, { openModal, setModalProps }] = useModal();
+

register

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

原理其实很简单,就是 vue 的组件子传父通信,内部通过 emit("register",instance) 实现。

同时独立出去的组件需要将 attrs 绑定到 BasicModal 上面。

<template>
+  <BasicModal v-bind="$attrs"></BasicModal>
+</template>
+

openModal

用于打开/关闭弹窗

// true/false: 打开关闭弹窗
+// data: 传递到子组件的数据
+openModal(true, data);
+

closeModal

用于关闭弹窗

closeModal();
+

setModalProps

用于更改 modal 的 props 参数因为 modal 内容独立成组件,如果在外部页面需要更改 props 可能比较麻烦,所以提供 setModalProps 方便更改内部 modal 的 props

Props 内容可以见下方

setModalProps(props);
+

useModalInner

用于独立的 Modal 内部调用

Usage

<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="Modal Title"
+    :helpMessage="['提示1', '提示2']"
+  >
+    <a-button type="primary" @click="closeModal" class="mr-2">从内部关闭弹窗</a-button>
+
+    <a-button type="primary" @click="setModalProps">从内部修改title</a-button>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  export default defineComponent({
+    components: { BasicModal },
+    setup() {
+      const [register, { closeModal, setModalProps }] = useModalInner();
+      return {
+        register,
+        closeModal,
+        setModalProps: () => {
+          setModalProps({ title: 'Modal New Title' });
+        },
+      };
+    },
+  });
+</script>
+

useModalInner用于操作独立组件

const [register, { closeModal, setModalProps }] = useModalInner(callback);
+

callback

type: (data:any)=>void

回调函数用于接收 openModal 第二个参数传递的值

useModal((data: any) => {
+  console.log(data);
+});
+

closeModal

用于关闭弹窗

closeModal();
+

changeOkLoading

用于修改确认按钮的 loading 状态

changeOkLoading(true);
+

changeLoading

用于修改 modal 的 loading 状态

// true or false
+changeLoading(true);
+

setModalProps

用于更改 modal 的 props 参数因为 modal 内容独立成组件,如果在外部页面需要更改 props 可能比较麻烦,所以提供 setModalProps 方便更改内部 modal 的 props

Props 内容可以见下方

Props

TIP

除以下参数外,组件库文档内的 props 也都支持,具体可以参考 antv modal

属性类型默认值可选值说明
titlestring--modal 标题
heightnumber--固定 modal 的高度
minHeightnumber--设置 modal 的最小高度
draggablebooleantruetrue/false是否开启拖拽
useWrapperbooleantruetrue/false是否开启自适应高度,开启后会跟随屏幕变化自适应内容,并出现滚动条
wrapperFooterOffsetnumber0-开启是适应高度后,如果超过屏幕高度,底部和顶部会保持一样的间距,该参数可以用来缩小底部的间距
canFullscreenbooleantruetrue/false是否可以进行全屏
defaultFullscreenbooleanfalsetrue/false默认全屏
loadingbooleanfalsetrue/falseloading 状态
loadingTipstring--loading 文本
showCancelBtnbooleantruetrue/false显示关闭按钮
showOkBtnbooleantruetrue/false显示确认按钮
helpMessagestring , string[]--标题右侧提示文本
centeredbooleanfalsetrue/false是否居中弹窗
cancelTextstring'关闭'-关闭按钮文本
okTextstring'保存'-确认按钮文本
closeFunc() => Promise<boolean>关闭函数-关闭前执行,返回 true 则关闭,否则不关闭

Events

事件回调参数说明
okfunction(e)点击确定回调
cancelfunction(e)点击取消回调
visible-change(visible:boolean)=>{}打开或者关闭触发

Slots

名称说明
default默认区域
footer底部区域(会替换掉默认的按钮)
insertFooter关闭按钮的左边(不使用footer插槽时有效)
centerFooter关闭按钮和确认按钮的中间(不使用footer插槽时有效)
appendFooter确认按钮的右边(不使用footer插槽时有效)
+ + + + \ No newline at end of file diff --git a/components/page.html b/components/page.html new file mode 100644 index 00000000..2ed47303 --- /dev/null +++ b/components/page.html @@ -0,0 +1,67 @@ + + + + + + Page | Vben Admin + + + + + + + + + + + + + + + + +

Page

页面相关组件

PageWrapper

用于包裹页面组件

Usage

<template>
+  <div>
+    <PageWrapper>
+      <template #left>left</template>
+      <template #right>right</template>
+    </PageWrapper>
+  </div>
+</template>
+<script>
+  import { PageWrapper } from '/@/components/Page';
+  import { defineComponent } from 'vue';
+  export default defineComponent({
+    components: { PageWrapper },
+    setup() {
+      return {};
+    },
+  });
+</script>
+

Props

属性类型默认值说明
titlestring-pageHeader title
dense是否缩小主体区域false为 true 将会取消 padding/margin
contentstring-pageHeader Content 内容
contentStyleobject-主体区域样式
contentClassstring-主体区域 class
contentBackgroundboolean-主体区域背景
contentFullHeightbooleanfalse主体区域是否占满整个屏幕高度
fixedHeightbooleanfalse固定主体区域高度

Slots

pageHeader 的 slot 都支持

名称说明
leftFooterPageFooter 左侧区域
rightFooterPageFooter 右侧区域
headerContentpageHeader 主体内容
default主体区域

用于页面底部工具栏

使用

<template>
+  <div>
+    <PageFooter>
+      <template #left>left</template>
+      <template #right>right</template>
+    </PageFooter>
+  </div>
+</template>
+<script>
+  import { PageFooter } from '/@/components/Page';
+  import { defineComponent } from 'vue';
+  export default defineComponent({
+    components: { PageFooter },
+    setup() {
+      return {};
+    },
+  });
+</script>
+

Slots

名称说明
left左侧区域
right右侧区域
+ + + + \ No newline at end of file diff --git a/components/pop-confirm-button.html b/components/pop-confirm-button.html new file mode 100644 index 00000000..3e77a99c --- /dev/null +++ b/components/pop-confirm-button.html @@ -0,0 +1,42 @@ + + + + + + PopConfirmButton 按钮 | Vben Admin + + + + + + + + + + + + + + + + +

PopConfirmButton 按钮

带有 PopConfirm 下拉菜单功能的按钮

Usage

<template>
+  <PopConfirmButton>按钮文本</PopConfirmButton>
+</template>
+
+<script>
+  import { defineComponent } from 'vue';
+  import { PopConfirmButton } from '/@/components/Button';
+  export default defineComponent({
+    components: { PopConfirmButton },
+  });
+</script>
+

Props

提示

保持 anv design popconfirm 组件 原有功能的情况下扩展以下属性

属性类型默认值说明
enablebooleantrue是否启用下拉菜单,为 false 则显示默认按钮
+ + + + \ No newline at end of file diff --git a/components/qrcode.html b/components/qrcode.html new file mode 100644 index 00000000..90370be8 --- /dev/null +++ b/components/qrcode.html @@ -0,0 +1,122 @@ + + + + + + QrCode | Vben Admin + + + + + + + + + + + + + + + + +

QrCode

用于生成二维码的组件

Usage

<template>
+  <QrCode :value="qrCodeUrl" />
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  import { QrCode, QrCodeActionType } from '/@/components/Qrcode/index';
+  import LogoImg from '/@/assets/images/logo.png';
+  const qrCodeUrl = 'https://www.vvbin.cn';
+  export default defineComponent({
+    components: { QrCode },
+    setup() {
+      const qrRef = ref<Nullable<QrCodeActionType>>(null);
+      function download() {
+        const qrEl = unref(qrRef);
+        if (!qrEl) return;
+        qrEl.download('文件名');
+      }
+      return {
+        qrCodeUrl,
+        LogoImg,
+        download,
+        qrRef,
+      };
+    },
+  });
+</script>
+<style scoped>
+  .qrcode-demo-item {
+    width: 30%;
+    margin-right: 1%;
+  }
+</style>
+

Props

属性类型默认值可选值说明
valuestring--二维码地址
optionsQRCodeRenderersOptions--二维码配置 ,见 QRCodeRenderersOptions
widthnumber2-宽度
logostring|LogoType--中间 logo 配置,见 LogoType
tag渲染标签canvascanvas | imgimg 不支持内嵌 logo

QRCodeRenderersOptions

/**
+ * 定义margin的宽度。.
+ * Default: 4
+ */
+margin?: number;
+/**
+ * 比例因子。值1表示每个模块1像素(黑点)。
+ * Default: 4
+ */
+scale?: number;
+/**
+ * 为输出图像强制指定宽度。
+ * 如果宽度太小而不能包含qr符号,则此选项将被忽略。
+ * 优先于规模。
+ */
+width?: number;
+color?: {
+  /**
+   * 暗模块的颜色。值必须为十六进制格式(RGBA).
+   * 注意:深色应始终比color.light暗。.
+   * Default: #000000ff
+   */
+  dark?: string;
+  /**
+   * 照明模块的颜色。值必须为十六进制格式(RGBA).
+   * Default: #ffffffff
+   */
+  light?: string;
+};
+
+

LogoType

{
+  // logo图片
+  src: string;
+  // logo大小
+  logoSize: number;
+  // 背景颜色
+  bgColor: string;
+  // logo圆角
+  logoRadius: number;
+}
+

Methods

名称回调参数说明
downloadFunction(fileName:string)下载

事件

名称回调参数说明
done(data: QrcodeDoneEventParams)=>void绘制完成
error(error)=>void生成二维码时发生错误

QrcodeDoneEventParams

{
+  url: string;  // 二维码DataURL数据
+  ctx?: CanvasRenderingContext2D;  // 该对象为画布的2D渲染上下文,仅在tag为canvas时有效,可用于自定义绘制
+}
+

done 事件回调中可以对二维码进行自定义的绘制,示例代码如下:

<QrCode
+  :value="qrCodeUrl"
+  :width="200"
+  @done="onQrcodeDone"
+/>
+
function onQrcodeDone({ ctx }) {
+  if (ctx instanceof CanvasRenderingContext2D) {
+    // 额外绘制
+    ctx.fillStyle = 'black';
+    ctx.font = '16px "微软雅黑"';
+    ctx.textBaseline = 'bottom';
+    ctx.textAlign = 'center';
+    ctx.fillText('你帅你先扫', 100, 195, 200);
+  }
+}
+

有关 CanvasRenderingContext2D 的更多资料以及绘制方法,请参考MDN

+ + + + \ No newline at end of file diff --git a/components/scroll-container.html b/components/scroll-container.html new file mode 100644 index 00000000..dfcf52c5 --- /dev/null +++ b/components/scroll-container.html @@ -0,0 +1,89 @@ + + + + + + ScrollContainer | Vben Admin + + + + + + + + + + + + + + + + +

ScrollContainer

参考 element-ui 的 el-scrollbar 组件实现

滚动容器组件

Usage

<template>
+  <div class="p-4">
+    <div class="my-4">
+      <a-button @click="scrollTo(100)">滚动到100px位置</a-button>
+      <a-button @click="scrollTo(800)">滚动到800px位置</a-button>
+      <a-button @click="scrollTo(0)">滚动到顶部</a-button>
+      <a-button @click="scrollBottom()">滚动到底部</a-button>
+    </div>
+    <div class="scroll-wrap">
+      <ScrollContainer ref="scrollRef">
+        <ul>
+          <template v-for="index in 100" :key="index">
+            <li>{{ index }}</li>
+          </template>
+        </ul>
+      </ScrollContainer>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  import { CollapseContainer } from '/@/components/Container/index';
+  import { ScrollContainer, ScrollActionType } from '/@/components/Container/index';
+  export default defineComponent({
+    components: { CollapseContainer, ScrollContainer },
+    setup() {
+      const scrollRef = ref<Nullable<ScrollActionType>>(null);
+      const getScroll = () => {
+        const scroll = unref(scrollRef);
+        if (!scroll) {
+          throw new Error('scroll is Null');
+        }
+        return scroll;
+      };
+
+      function scrollTo(top: number) {
+        getScroll()?.scrollTo(top);
+      }
+
+      function scrollBottom() {
+        getScroll()?.scrollBottom();
+      }
+
+      return {
+        scrollTo,
+        scrollRef,
+        scrollBottom,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .scroll-wrap {
+    width: 50%;
+    height: 300px;
+    background: #fff;
+  }
+</style>
+

Methods

名称回调参数说明
getScrollWrap()=>HtmlElement获取滚动容器 el
scrollBottomFunction滚动到底部
scrollToFunction(to:number,duration = 500)滚动到指定位置

Slots

名称说明
default默认区域
+ + + + \ No newline at end of file diff --git a/components/strength-meter.html b/components/strength-meter.html new file mode 100644 index 00000000..2f938e0e --- /dev/null +++ b/components/strength-meter.html @@ -0,0 +1,58 @@ + + + + + + StrengthMeter | Vben Admin + + + + + + + + + + + + + + + + +

StrengthMeter

用于校验密码强度

Usage

<template>
+  <div class="p-4 flex justify-center">
+    <div class="demo-wrap p-10">
+      <StrengthMeter placeholder="默认" />
+      <StrengthMeter placeholder="禁用" disabled />
+      <br />
+      <StrengthMeter placeholder="隐藏input" :show-input="false" value="!@#qwe12345" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import StrengthMeter from '/@/components/StrengthMeter/index';
+  export default defineComponent({
+    components: {
+      StrengthMeter,
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .demo-wrap {
+    width: 50%;
+    background: #fff;
+    border-radius: 10px;
+  }
+</style>
+

Props

属性类型默认值可选值说明
valuestring--校验的值
showInputbooleantrue-是否显示 input
disabledbooleanfalse-是否禁用

Events

事件回调参数说明
score-changenumber强度值改变触发
changestringinput 值改变触发
+ + + + \ No newline at end of file diff --git a/components/table.html b/components/table.html new file mode 100644 index 00000000..74eb7ca5 --- /dev/null +++ b/components/table.html @@ -0,0 +1,342 @@ + + + + + + Table 表格 | Vben Admin + + + + + + + + + + + + + + + + +

Table 表格

antv 的 table 组件进行封装

如果文档内没有,可以尝试在在线示例内寻找

Usage

示例

<template>
+  <div class="p-4">
+    <BasicTable
+      title="基础示例"
+      titleHelpMessage="温馨提醒"
+      :columns="columns"
+      :dataSource="data"
+      :canResize="canResize"
+      :loading="loading"
+      :striped="striped"
+      :bordered="border"
+      :pagination="{ pageSize: 20 }"
+    >
+      <template #toolbar>
+        <a-button type="primary"> 操作按钮 </a-button>
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { BasicTable } from '/@/components/Table';
+  import { getBasicColumns, getBasicData } from './tableData';
+
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      return {
+        columns: getBasicColumns(),
+        data: getBasicData(),
+      };
+    },
+  });
+</script>
+

template 示例

所有可调用函数见下方 Methods 说明

<template>
+  <div class="p-4">
+    <BasicTable
+      :canResize="false"
+      title="RefTable示例"
+      titleHelpMessage="使用Ref调用表格内方法"
+      ref="tableRef"
+      :api="api"
+      :columns="columns"
+      rowKey="id"
+      :rowSelection="{ type: 'checkbox' }"
+    />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  import { BasicTable, TableActionType } from '/@/components/Table';
+  import { getBasicColumns, getBasicShortColumns } from './tableData';
+  import { demoListApi } from '/@/api/demo/table';
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const tableRef = ref<Nullable<TableActionType>>(null);
+
+      function getTableAction() {
+        const tableAction = unref(tableRef);
+        if (!tableAction) {
+          throw new Error('tableAction is null');
+        }
+        return tableAction;
+      }
+      function changeLoading() {
+        getTableAction().setLoading(true);
+        setTimeout(() => {
+          getTableAction().setLoading(false);
+        }, 1000);
+      }
+      return {
+        tableRef,
+        api: demoListApi,
+        columns: getBasicColumns(),
+        changeLoading,
+      };
+    },
+  });
+</script>
+

BasicColumn 和 tableAction 通过权限和业务控制显示隐藏的示例

<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '编辑',
+              onClick: handleEdit.bind(null, record),
+              auth: 'other', // 根据权限控制是否显示: 无权限,不显示
+            },
+            {
+              label: '删除',
+              icon: 'ic:outline-delete-outline',
+              onClick: handleDelete.bind(null, record),
+              auth: 'super', // 根据权限控制是否显示: 有权限,会显示
+            },
+          ]"
+          :dropDownActions="[
+            {
+              label: '启用',
+              popConfirm: {
+                title: '是否启用?',
+                confirm: handleOpen.bind(null, record),
+              },
+              ifShow: (_action) => {
+                return record.status !== 'enable'; // 根据业务控制是否显示: 非enable状态的不显示启用按钮
+              },
+            },
+            {
+              label: '禁用',
+              popConfirm: {
+                title: '是否禁用?',
+                confirm: handleOpen.bind(null, record),
+              },
+              ifShow: () => {
+                return record.status === 'enable'; // 根据业务控制是否显示: enable状态的显示禁用按钮
+              },
+            },
+            {
+              label: '同时控制',
+              popConfirm: {
+                title: '是否动态显示?',
+                confirm: handleOpen.bind(null, record),
+              },
+              auth: 'super', // 同时根据权限和业务控制是否显示
+              ifShow: () => {
+                return true; // 根据业务控制是否显示
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable, BasicColumn, TableAction } from '/@/components/Table';
+
+  import { demoListApi } from '/@/api/demo/table';
+  const columns: BasicColumn[] = [
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      auth: 'test', // 根据权限控制是否显示: 无权限,不显示
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+      auth: 'super', // 同时根据权限控制是否显示
+      ifShow: (_column) => {
+        return true; // 根据业务控制是否显示
+      },
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable, TableAction },
+    setup() {
+      const [registerTable] = useTable({
+        title: 'TableAction组件及固定列示例',
+        api: demoListApi,
+        columns: columns,
+        bordered: true,
+        actionColumn: {
+          width: 250,
+          title: 'Action',
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+        },
+      });
+      function handleEdit(record: Recordable) {
+        console.log('点击了编辑', record);
+      }
+      function handleDelete(record: Recordable) {
+        console.log('点击了删除', record);
+      }
+      function handleOpen(record: Recordable) {
+        console.log('点击了启用', record);
+      }
+      return {
+        registerTable,
+        handleEdit,
+        handleDelete,
+        handleOpen,
+      };
+    },
+  });
+</script>
+

useTable

使用组件自带的 useTable 可以方便使用表单

下面是一个使用简单表格的示例,

<template>
+  <BasicTable @register="registerTable" />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { getBasicColumns, getBasicShortColumns } from './tableData';
+  import { demoListApi } from '/@/api/demo/table';
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const [
+        registerTable,
+        {
+          setLoading,
+        },
+      ] = useTable({
+        api: demoListApi,
+        columns: getBasicColumns(),
+      });
+
+      function changeLoading() {
+        setLoading(true);
+        setTimeout(() => {
+          setLoading(false);
+        }, 1000);
+      }
+      }
+      return {
+        registerTable,
+        changeLoading,
+      };
+    },
+  });
+</script>
+

Usage

用于调用 Table 内部方法及 table 参数配置

// 表格的props也可以直接注册到useTable内部
+const [register, methods] = useTable(props);
+

register

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

<template>
+  <BasicTable @register="register" />
+</template>
+<script>
+  export default defineComponent({
+    components: { BasicForm },
+    setup() {
+      const [register] = useTable();
+      return { register };
+    },
+  });
+</script>
+

Methods

setProps

类型:(props: Partial<BasicTableProps>) => void

说明: 用于设置表格参数

reload

类型:(opt?: FetchParams) => Promise<void>

说明: 刷新表格

redoHeight

类型:() => void

说明: 重新计算表格高度

setLoading

类型:(loading: boolean) => void

说明: 设置表格 loading 状态

getDataSource

获取表格数据

类型:<T = Recordable>() => T[]

说明: 获取表格数据

getRawDataSource

获取后端接口原始数据

类型:<T = Recordable>() => T

说明: 获取后端接口原始数据

getColumns

类型:(opt?: GetColumnsParams) => BasicColumn[]

说明: 获取表格数据

setColumns

类型:(columns: BasicColumn[] | string[]) => void

说明: 设置表头数据

setTableData

类型:<T = Recordable>(values: T[]) => void

说明: 设置表格数据

setPagination

类型:(info: Partial<PaginationProps>) => void

说明: 设置分页信息

deleteSelectRowByKey

类型:(key: string) => void

说明: 根据 key 删除取消选中行

getSelectRowKeys

类型:() => string[]

说明: 获取选中行的 keys

getSelectRows

类型:<T = Recordable>() => T[]

说明: 获取选中行的 rows

clearSelectedRowKeys

类型:() => void

说明: 清空选中行

setSelectedRowKeys

类型:(rowKeys: string[] | number[]) => void

说明: 设置选中行

getPaginationRef

类型:() => PaginationProps | boolean

说明: 获取当前分页信息

getShowPagination

类型:() => boolean

说明: 获取当前是否显示分页

setShowPagination

类型:(show: boolean) => Promise<void>

说明: 设置当前是否显示分页

getRowSelection

类型:() => TableRowSelection<Recordable>

说明: 获取勾选框信息

updateTableData

类型:(index: number, key: string, value: any)=>void

说明: 更新表格数据

updateTableDataRecord

类型: (rowKey: string | number, record: Recordable) => Recordable | void

说明: 根据唯一的 rowKey 更新指定行的数据.可用于不刷新整个表格而局部更新数据

deleteTableDataRecord

类型: (rowKey: string | number | string[] | number[]) => void

说明: 根据唯一的rowKey 动态删除指定行的数据.可用于不刷新整个表格而局部更新数据

insertTableDataRecord

类型: (record: Recordable, index?: number) => Recordable | void

说明: 可根据传入的 index 值决定插入数据行的位置,不传则是顺序插入,可用于不刷新整个表格而局部更新数据

getForm

类型:() => FormActionType

说明: 如果开启了搜索区域。可以通过该函数获取表单对象函数进行操作

expandAll

类型:() => void

说明: 展开树形表格

collapseAll

类型:() => void

说明: 折叠树形表格

Props

温馨提醒

  • 除以下参数外,官方文档内的 props 也都支持,具体可以参考 antv table
  • 注意:defaultExpandAllRowsdefaultExpandedRowKeys 属性在basicTable中不受支持,并且在antv table v2.2.0之后也被移除。
属性类型默认值可选值说明版本
clickToRowSelectbooleantrue-点击行是否选中 checkbox 或者 radio。需要开启
sortFn(sortInfo: SorterResult<any>) => any--自定义排序方法。见下方全局配置说明
filterFn(sortInfo: Partial<Recordable<string[]>>) => any--自定义过滤方法。见下方全局配置说明
showTableSettingbooleanfalse-显示表格设置工具
tableSettingTableSetting--表格设置工具配置,见下方 TableSetting
stripedbooleantrue-斑马纹
insetbooleanfalse-取消表格的默认 padding
autoCreateKeybooleantrue-是否自动生成 key
showSummarybooleanfalse-是否显示合计行
summaryDataany[]--自定义合计数据。如果有则显示该数据
emptyDataIsShowTablebooleantrue-在启用搜索表单的前提下,是否在表格没有数据的时候显示表格
summaryFunc(...arg) => any[]--计算合计行的方法
canRowDragbooleanfalse-是否可拖拽行排序
canColDragbooleanfalse-是否可拖拽列
isTreeTablebooleanfalse-是否树表
api(...arg: any) => Promise<any>--请求接口,可以直接将src/api内的函数直接传入
beforeFetch(T)=>T--请求之前对参数进行处理
afterFetch(T)=>T--请求之后对返回值进行处理
handleSearchInfoFn(T)=>T--开启表单后,在请求之前处理搜索条件参数
fetchSettingFetchSetting--接口请求配置,可以配置请求的字段和响应的字段名,见下方全局配置说明
immediatebooleantrue-组件加载后是否立即请求接口,在 api 有传的情况下,如果为 false,需要自行使用 reload 加载表格数据
searchInfoany--额外的请求参数
useSearchFormbooleanfalse-使用搜索表单
formConfigany--表单配置,参考表单组件的 Props
columnsany--表单列信息 BasicColumn[]
showIndexColumnbooleanture-是否显示序号列
indexColumnPropsany--序号列配置 BasicColumn
actionColumnany--表格右侧操作列配置 BasicColumn
ellipsisbooleantrue-文本超过宽度是否显示...
canResizebooleantrue-是否可以自适应高度(如果置于PageWrapper组件内,请勿启用PageWrapper的fixedHeight属性,二者不可同时使用)
clearSelectOnPageChangebooleanfalse-切换页码是否重置勾选状态
resizeHeightOffsetnumber0-表格自适应高度计算结果会减去这个值
rowSelectionany--选择列配置
titlestring--表格标题
titleHelpMessagestring | string[]--表格标题右侧温馨提醒
maxHeightnumber--表格最大高度,超出会显示滚动条
dataSourceany[]--表格数据,非 api 加载情况
borderedbooleanfalse-是否显示表格边框
paginationany--分页信息配置,为 false 不显示分页
loadingbooleanfalse-表格 loading 状态
scrollany--参考官方文档 scroll
beforeEditSubmit({record: Recordable,index: number,key: string | number,value: any}) => Promise<any>--单元格编辑状态提交回调,返回false将阻止单元格提交数据到table。该回调在行编辑模式下无效。2.7.2

TableSetting

{
+  // 是否显示刷新按钮
+  redo?: boolean;
+  // 是否显示尺寸调整按钮
+  size?: boolean;
+  // 是否显示字段调整按钮
+  setting?: boolean;
+  // 是否显示全屏按钮
+  fullScreen?: boolean;
+}
+

BasicColumn

除 参考官方 Column 配置外,扩展以下参数

属性类型默认值可选值说明
defaultHiddenbooleanfalse-默认隐藏,可在列配置显示
helpMessagestring|string[]--列头右侧帮助文本
editboolean--是否开启单元格编辑
editRowboolean--是否开启行编辑
editablebooleanfalse-是否处于编辑状态
editComponentComponentTypeInput-编辑组件
editComponentPropsany--对应编辑组件的 props
editRule((text: string, record: Recordable) => Promise<string>)--对应编辑组件的表单校验
editValueMap(value: any) => string--对应单元格值枚举
onEditRow()=>void--触发行编辑
formatCellFormat--单元格格式化
authRoleEnumRoleEnum[]stringstring[]--根据权限编码来控制当前列是否显示
ifShowboolean | ((action: ActionItem) => boolean)--根据业务状态来控制当前列是否显示

EditComponentType

export type ComponentType =
+  | 'Input'
+  | 'InputNumber'
+  | 'Select'
+  | 'ApiSelect'
+  | 'Checkbox'
+  | 'Switch'
+  | 'DatePicker'  // v2.5.0 以上
+  | 'TimePicker'; // v2.5.0 以上
+

CellFormat

export type CellFormat =
+  | string
+  | ((text: string, record: Recordable, index: number) => string | number)
+  | Map<string | number, any>;
+

事件

温馨提醒

除以下事件外,官方文档内的 event 也都支持,具体可以参考 antv table

事件回调参数说明
fetch-successFunction({items,total})接口请求成功后触发
fetch-errorFunction(error)错误信息
selection-changeFunction({keys,rows})勾选事件触发
row-clickFunction(record, index, event)行点击触发
row-dbClickFunction(record, index, event)行双击触发
row-contextmenuFunction(record, index, event)行右键触发
row-mouseenterFunction(record, index, event)行移入触发
row-mouseleaveFunction(record, index, event)行移出触发
edit-endFunction({record, index, key, value})单元格编辑完成触发
edit-cancelFunction({record, index, key, value})单元格取消编辑触发
edit-row-endFunction()行编辑结束触发
edit-changeFunction({column,value,record})单元格编辑组件的 value 发生变化时触发

edit-change 说明

从版本 2.4.2 起,对于 edit-change 事件,record 中的 editValueRefs 装载了当前行的所有编辑组件(如果有的话)的值的 ref 对象,可用于处理同一行中的编辑组件的联动。请看下面的例子

      function onEditChange({ column, record }) {
+        // 当同一行的单价或者数量发生变化时,更新合计金额(三个数据均为当前行编辑组件的值)
+        if (column.dataIndex === 'qty' || column.dataIndex === 'price') {
+          const { editValueRefs: { total, qty, price } } = record;
+          total.value = unref(qty) * unref(price);
+        }
+      }
+

Slots

温馨提醒

除以下参数外,官方文档内的 slot 也都支持,具体可以参考 antv table

名称说明版本
tableTitle表格顶部左侧区域
toolbar表格顶部右侧区域
expandedRowRender展开行区域
headerTop表格顶部区域(标题上方)2.6.1

Form-Slots

当开启 form 表单后。以form-xxxx为前缀的 slot 会被视为 form 的 slot

xxxx 为 form 组件的 slot。具体参考form 组件文档

e.g

form-submitBefore
+

ColumnSetting组件

字段调整组件

提供了可视化操作表格每一列的是否展示、位置、固定;包括序号列、勾选列。会响应tableMethodssetColumnssetProps方法的更改内容。

值得注意的是

序号列勾选列是在table的props中定义的,对应的字段分别是showIndexColumnrowSelection。因此在动态改变表格列配置的时候,建议使用setProps方法,并显式地设置这两个字段的值来保证达到预期效果

// ...
+const [registerTable, { setProps }] = useTable({...})
+
+setProps({
+  columns: [], // 表格的列配置 BasicColumn[]
+  showIndexColumn: false, // 是否展示序号列
+  rowSelection: false // 勾选列配置
+})
+

内置组件(只能用于表格内部)

TableAction

用于表格右侧操作列渲染

Props

属性类型默认值可选值说明版本
actionsActionItem[]--右侧操作列按钮列表
dropDownActionsActionItem[]--右侧操作列更多下拉按钮列表
stopButtonPropagationbooleanfalsetrue/false是否阻止操作按钮的click事件冒泡2.5.0

ActionItem

export interface ActionItem {
+  // 按钮文本
+  label: string;
+  // 是否禁用
+  disabled?: boolean;
+  // 按钮颜色
+  color?: 'success' | 'error' | 'warning';
+  // 按钮类型
+  type?: string;
+  // button组件props
+  props?: any;
+  // 按钮图标
+  icon?: string;
+  // 气泡确认框
+  popConfirm?: PopConfirm;
+  // 是否显示分隔线,v2.0.0+
+  divider?: boolean;
+  // 根据权限编码来控制当前列是否显示,v2.4.0+
+  auth?: RoleEnum | RoleEnum[] | string | string[];
+  // 根据业务状态来控制当前列是否显示,v2.4.0+
+  ifShow?: boolean | ((action: ActionItem) => boolean);
+  // 点击回调
+  onClick?: Fn;
+  // Tooltip配置,2.5.3以上版本支持,可以配置为string,或者完整的tooltip属性
+  tooltip?: string | TooltipProps
+}
+

有关TooltipProps的说明,请参考tooltip

PopConfirm

export interface PopConfirm {
+  title: string;
+  okText?: string;
+  cancelText?: string;
+  confirm: Fn;
+  cancel?: Fn;
+  icon?: string;
+}
+

TableImg

用于渲染单元格图片,支持图片预览

Props

属性类型默认值可选值说明版本
imgListstring[]--图片地址列表
sizenumber--图片大小
simpleShowbooleanfalsetrue/false简单显示模式(只显示第一张图片)2.5.0
showBadgebooleantruetrue/false简单模式下是否显示计数Badge2.5.0
marginnumber4-常规模式下的图片间距2.5.0
srcPrefixstring--在每一个图片src前插入的内容2.5.0

全局配置

componentsSettings 可以配置全局参数。用于统一整个项目的风格。可以通过 props 传值覆盖

+ + + + \ No newline at end of file diff --git a/components/time.html b/components/time.html new file mode 100644 index 00000000..47f74856 --- /dev/null +++ b/components/time.html @@ -0,0 +1,52 @@ + + + + + + Time | Vben Admin + + + + + + + + + + + + + + + + +

Time

相对时间组件

Usage

<template>
+  <Time :value="time" />
+</template>
+<script lang="ts">
+  import { defineComponent, reactive, toRefs } from 'vue';
+  import { Time } from '/@/components/Time';
+
+  export default defineComponent({
+    components: { Time },
+    setup() {
+      const now = new Date().getTime();
+      const state = reactive({
+        time: now - 60 * 3 * 1000,
+      });
+      return {
+        ...toRefs(state),
+        now,
+      };
+    },
+  });
+</script>
+

Props

属性类型默认值可选值说明
valuestring,Date,number--时间值
stepnumber60-刷新时间
modestringrelative-模式,date:日期,datetime:时间戳,relative:相对时间
+ + + + \ No newline at end of file diff --git a/components/tinymce.html b/components/tinymce.html new file mode 100644 index 00000000..2f102b12 --- /dev/null +++ b/components/tinymce.html @@ -0,0 +1,50 @@ + + + + + + Tinymce | Vben Admin + + + + + + + + + + + + + + + + +

Tinymce

富文本组件位于 src/components/TinyMce

富文本组件使用的是 CDN 方式引入

可在 /@/components/TinyMce/src/Editor.vue 更改下面 CDN 地址

const CDN_URL = 'https://cdn.bootcdn.net/ajax/libs/tinymce/5.5.1';
+

Usage

<template>
+  <Tinymce v-model="value" @change="handleChange" width="100%" />
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { Tinymce } from '/@/components/Tinymce/index';
+
+  export default defineComponent({
+    components: { Tinymce },
+    setup() {
+      const value = ref('hello world!');
+      function handleChange(value: string) {
+        console.log(value);
+      }
+      return { handleChange, value };
+    },
+  });
+</script>
+

Props

属性类型默认值说明
optionsany{}tinymce 的配置项
value(v-model)string-双向绑定值
heightnumber , string400高度
widthnumber , stringauto宽度
toolbarstring[]-工具栏
pluginsstring[]-插件
showImageUploadbooleantrue是否显示上传按钮

Events

事件回调参数返回值说明
change(str:string)=>{}富文本内容改变触发事件
+ + + + \ No newline at end of file diff --git a/components/transition.html b/components/transition.html new file mode 100644 index 00000000..ae796009 --- /dev/null +++ b/components/transition.html @@ -0,0 +1,125 @@ + + + + + + Transition | Vben Admin + + + + + + + + + + + + + + + + +

Transition

用于页面/组件切换动画

Usage

<template>
+  <div class="p-4">
+    <div class="flex">
+      <Select
+        :options="options"
+        v-model:value="value"
+        placeholder="选择动画"
+        :style="{ width: '150px' }"
+      />
+      <a-button type="primary" class="ml-4" @click="start"> start </a-button>
+    </div>
+    <component :is="`${value}Transition`">
+      <div class="box" v-show="show"></div>
+    </component>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { Select } from 'ant-design-vue';
+  import {
+    FadeTransition,
+    ScaleTransition,
+    SlideYTransition,
+    ScrollYTransition,
+    SlideYReverseTransition,
+    ScrollYReverseTransition,
+    SlideXTransition,
+    ScrollXTransition,
+    SlideXReverseTransition,
+    ScrollXReverseTransition,
+    ScaleRotateTransition,
+    ExpandXTransition,
+    ExpandTransition,
+  } from '/@/components/Transition/index';
+
+  const transitionList = [
+    'Fade',
+    'Scale',
+    'SlideY',
+    'ScrollY',
+    'SlideYReverse',
+    'ScrollYReverse',
+    'SlideX',
+    'ScrollX',
+    'SlideXReverse',
+    'ScrollXReverse',
+    'ScaleRotate',
+    'ExpandX',
+    'Expand',
+  ];
+  const options = transitionList.map((item) => ({
+    label: item,
+    value: item,
+    key: item,
+  }));
+
+  export default defineComponent({
+    components: {
+      Select,
+      FadeTransition,
+      ScaleTransition,
+      SlideYTransition,
+      ScrollYTransition,
+      SlideYReverseTransition,
+      ScrollYReverseTransition,
+      SlideXTransition,
+      ScrollXTransition,
+      SlideXReverseTransition,
+      ScrollXReverseTransition,
+      ScaleRotateTransition,
+      ExpandXTransition,
+      ExpandTransition,
+    },
+    setup() {
+      const value = ref('Fade');
+      const show = ref(true);
+      function start() {
+        show.value = false;
+        setTimeout(() => {
+          show.value = true;
+        }, 300);
+      }
+      return { options, value, start, show };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .box {
+    width: 150px;
+    height: 150px;
+    margin-top: 20px;
+    background: pink;
+  }
+</style>
+
+ + + + \ No newline at end of file diff --git a/components/tree.html b/components/tree.html new file mode 100644 index 00000000..f1c5ba76 --- /dev/null +++ b/components/tree.html @@ -0,0 +1,104 @@ + + + + + + Tree | Vben Admin + + + + + + + + + + + + + + + + +

Tree

antv 的 tree 组件进行封装

Usage

<template>
+  <BasicTree :treeData="treeData" />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTree } from '/@/components/Tree/index';
+  import { treeData } from './data';
+  import { CollapseContainer } from '/@/components/Container/index';
+  import { TreeItem } from '/@/components/Tree/index';
+
+  export const treeData: TreeItem[] = [
+    {
+      title: 'parent 1',
+      key: '0-0',
+      icon: 'home|svg',
+      children: [
+        { title: 'leaf', key: '0-0-0' },
+        {
+          title: 'leaf',
+          key: '0-0-1',
+          children: [
+            { title: 'leaf', key: '0-0-0-0' },
+            { title: 'leaf', key: '0-0-0-1' },
+          ],
+        },
+      ],
+    },
+    {
+      title: 'parent 2',
+      key: '1-1',
+      icon: 'home|svg',
+      children: [
+        { title: 'leaf', key: '1-1-0' },
+        { title: 'leaf', key: '1-1-1' },
+      ],
+    },
+    {
+      title: 'parent 3',
+      key: '2-2',
+      icon: 'home|svg',
+      children: [
+        { title: 'leaf', key: '2-2-0' },
+        { title: 'leaf', key: '2-2-1' },
+      ],
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTree, CollapseContainer },
+    setup() {
+      return { treeData };
+    },
+  });
+</script>
+

Props

温馨提醒

除以下参数外,官方文档内的 props 也都支持,具体可以参考 antv tree

属性类型默认值可选值说明版本
treeDataTreeItem[]--树组件数据
rightMenuListContextMenuItem[]--右键菜单列表
checkedKeysstring[]--勾选的节点
selectedKeysstring[]--选中的节点
expandedKeysstring[]--展开的节点
actionListActionItem[]--鼠标移动上去右边操作按钮列表
titlestring--定制标题字符串
toolbarboolean--是否显示工具栏
searchboolean--显示搜索框
clickRowToExpandboolean--是否在点击行时自动展开
beforeRightClick(node, event)=>ContextMenuItem[]--右键点击回调,可返回右键菜单列表数据来生成右键菜单
rightMenuListContextMenuItem[]--右键菜单列表数据
defaultExpandLevelstring | number--初次渲染后默认展开的层级2.4.1
defaultExpandAllbooleanfalsetrue/false初次渲染后默认全部2.4.1
searchValue(v-model)string--当前搜索词2.7.1

注意

defaultExpandLeveldefaultExpandAll 仅在初次渲染时生效。如果basicTree是在创建完毕之后才设置的treeData(如异步数据),需要在更新后自己调用basicTree提供的expandAllfilterByLevel来执行展开

ActionItem

{
+  // 渲染的图标
+  render: (record: any) => any;
+  // 是否显示
+  show?: boolean | ((record: Recordable) => boolean);
+}
+

ContextMenuItem

{
+  // 文本
+  label: string;
+  // 图标
+  icon?: string;
+  // 是否禁用
+  disabled?: boolean;
+  // 事件
+  handler?: (...arg) => any;
+  // 是否显示分隔线
+  divider?: boolean;
+  // 子级菜单数据
+  children?: ContextMenuItem[];
+}
+

Slots

温馨提醒

官方文档内的 slot 都支持,具体可以参考 antv tree

Methods

名称回调参数说明
checkAll(checkAll: boolean) => void选择所有
expandAll(expandAll: boolean) => void展开所有
setExpandedKeys(keys: Keys) => void设置展开节点
getExpandedKeys() => Keys获取展开节点
setSelectedKeys(keys: Keys) => void设置选中节点
getSelectedKeys() => Keys获取选中节点
setCheckedKeys(keys: CheckKeys) => void设置勾选节点
getCheckedKeys() => CheckKeys获取勾选节点
filterByLevel(level: number) => void显示指定等级
insertNodeByKey(opt: InsertNodeParams) => void插入子节点到指定节点内
deleteNodeByKey(key: string) => void根据 key 删除节点
updateNodeByKey(key: string, node: Omit<TreeItem, 'key'>) => void根据 key 更新节点
setSearchValue(value: string) => void设置当前搜索词(v2.7.1)
getSearchValue() => string获取当前搜索词(v2.7.1)
+ + + + \ No newline at end of file diff --git a/components/upload.html b/components/upload.html new file mode 100644 index 00000000..aa3493cb --- /dev/null +++ b/components/upload.html @@ -0,0 +1,65 @@ + + + + + + Upload | Vben Admin + + + + + + + + + + + + + + + + +

Upload

文件上传组件

Usage

<template>
+  <BasicUpload :maxSize="20" :maxNumber="10" @change="handleChange" :api="uploadApi" />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicUpload } from '/@/components/Upload';
+  import { uploadApi } from '/@/api/sys/upload';
+
+  export default defineComponent({
+    components: { BasicUpload },
+    setup() {
+      return {
+        uploadApi,
+        handleChange: (list: string[]) => {
+          createMessage.info(`已上传文件${JSON.stringify(list)}`);
+        },
+      };
+    },
+  });
+</script>
+

Config

.env.development.env.production 配置开发和生产的文件上传地址

# .env.development
+
+VITE_PROXY=[["/upload","http://localhost:3001/upload"]]
+
+::: tip
+v3.0.0开始,作者重构了vite.config.ts,新版本不再支持VITE_PROXY环境变量。
+:::
+
+# 如果没有跨域问题,则直接使用真实上传地址
+VITE_GLOB_UPLOAD_URL=/upload
+
+# .env.production
+VITE_GLOB_UPLOAD_URL=/upload
+
+

Props

属性类型默认值可选值说明
valuestring[]--已上传的文件列表,支持v-model
showPreviewNumberbooleantrue-是否显示预览数量
emptyHidePreviewbooleanfalse-没有上传文件时是否隐藏预览
helpTextstring--帮助文本
maxSizenumber2-单个文件最大体积,单位 M
maxNumbernumberInfinity-最大上传数量,Infinity 则不限制
acceptstring[]--限制上传格式,可使用文件后缀名(点号可选)或MIME字符串。例如 ['.doc,','docx','application/msword','image/*']
multipleboolean--开启多文件上传
uploadParamsany--上传携带的参数
apiFn--上传接口,为上面配置的接口

Events

事件回调参数返回值说明版本
change(fileList)=>void文件列表内容改变触发事件
delete(record)=>void在上传列表中删除文件的事件
preview-delete(url:string)=>void在预览列表中删除文件的事件2.5.3
+ + + + \ No newline at end of file diff --git a/components/verify.html b/components/verify.html new file mode 100644 index 00000000..c3e70b2a --- /dev/null +++ b/components/verify.html @@ -0,0 +1,76 @@ + + + + + + BasicDragVerify | Vben Admin + + + + + + + + + + + + + + + + +

BasicDragVerify

拖动校验组件

BasicDragVerify

Usage

<template>
+  <div class="p-10">
+    <BasicDragVerify @success="handleSuccess" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { BasicDragVerify, DragVerifyActionType, PassingData } from '/@/components/Verify/index';
+  export default defineComponent({
+    components: { BasicDragVerify },
+    setup() {
+      function handleSuccess(data: PassingData) {
+        const { time } = data;
+        createMessage.success(`校验成功,耗时${time}`);
+      }
+      return {
+        handleSuccess,
+        handleBtnClick,
+      };
+    },
+  });
+</script>
+

Props

属性类型默认值说明
valueboolean-是否通过
textstring请按住滑块拖动未拖动时候显示文字
successTextstring验证通过验证成功后显示文本
heightstring|string40高度
widthstring|string260宽度
circlebooleanfalse是否圆角
wrapStyleany-外层容器样式
contentStyleany-主体内容样式
barStyleany-bar 样式
actionStyleany-拖拽按钮样式

Methods

名称回调参数说明
resume()=>{}还原初始值

RotateDragVerify

图片还原正方向校验组件

Usage

<template>
+  <div class="p-10">
+    <RotateDragVerify :src="img" ref="el" @success="handleSuccess" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { RotateDragVerify } from '/@/components/Verify/index';
+
+  import img from '/@/assets/images/header.jpg';
+  export default defineComponent({
+    components: { RotateDragVerify },
+    setup() {
+      const handleSuccess = () => {
+        console.log('success!');
+      };
+      return {
+        handleSuccess,
+        img,
+      };
+    },
+  });
+</script>
+

props

属性类型默认值说明
srcstring-图片地址
imgWidthnumber-图片宽度
imgWrapStyleany-图片外层容器样式
minDegreenumber-最小旋转角度
maxDegreenumber-最大旋转角度
diffDegreenumber-误差角度
valueboolean-是否通过
textstring请按住滑块拖动未拖动时候显示文字
successTextstring验证通过验证成功后显示文本
heightstring|string40高度
widthstring|string260宽度
circlebooleanfalse是否圆角
wrapStyleany-外层容器样式
contentStyleany-主体内容样式
barStyleany-bar 样式
actionStyleany-拖拽按钮样式

Methods

名称回调参数说明
resumeFunction还原初始值
+ + + + \ No newline at end of file diff --git a/components/virtual-scroll.html b/components/virtual-scroll.html new file mode 100644 index 00000000..d00e965b --- /dev/null +++ b/components/virtual-scroll.html @@ -0,0 +1,90 @@ + + + + + + VirtualScroll | Vben Admin + + + + + + + + + + + + + + + + +

VirtualScroll

虚拟滚动组件(用于大量数据纯展示时使用)

Usage

<template>
+  <div class="p-4 virtual-scroll-demo">
+    <Divider>基础滚动示例</Divider>
+    <div class="virtual-scroll-demo-wrap">
+      <VirtualScroll :itemHeight="41" :items="data" :height="300" :width="300">
+        <template v-slot="{ item }">
+          <div class="virtual-scroll-demo__item">{{ item.title }}</div>
+        </template>
+      </VirtualScroll>
+    </div>
+
+    <Divider>即使不可见,也预先加载50条数据,防止空白</Divider>
+    <div class="virtual-scroll-demo-wrap">
+      <VirtualScroll :itemHeight="41" :items="data" :height="300" :width="300" :bench="50">
+        <template v-slot="{ item }">
+          <div class="virtual-scroll-demo__item">{{ item.title }}</div>
+        </template>
+      </VirtualScroll>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { VirtualScroll } from '/@/components/VirtualScroll/index';
+
+  import { Divider } from 'ant-design-vue';
+  const data: any[] = (() => {
+    const arr: any[] = [];
+    for (let index = 1; index < 20000; index++) {
+      arr.push({
+        title: '列表项' + index,
+      });
+    }
+    return arr;
+  })();
+  export default defineComponent({
+    components: { VirtualScroll, Divider },
+    setup() {
+      return { data: data };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .virtual-scroll-demo {
+    &-wrap {
+      display: flex;
+      margin: 0 30%;
+      background: #fff;
+      justify-content: center;
+    }
+
+    /deep/ &__item {
+      height: 40px;
+      padding: 0 20px;
+      line-height: 40px;
+      border-bottom: 1px solid #ddd;
+    }
+  }
+</style>
+

Props

属性类型默认值可选值说明
heightstring|number--高度
widthstring|number--宽度
maxHeightstring|number--最大高度
maxWidthstring|number--最大宽度
minHeightstring|number--最小高度
minWidthstring|number--最小宽度
itemHeightstring|number--每个选项高度,必传
itemsany[]--选项列表

Slots

名称说明
default默认
+ + + + \ No newline at end of file diff --git a/dep/cors.html b/dep/cors.html new file mode 100644 index 00000000..15528a1d --- /dev/null +++ b/dep/cors.html @@ -0,0 +1,31 @@ + + + + + + 跨域处理 | Vben Admin + + + + + + + + + + + + + + + + +

跨域处理

产生原因

跨域产生的原因是由于前端地址与后台接口不是同源,从而导致 ajax 不能发送

非同源产生的问题

  1. Cookie、LocalStorage 和 IndexDB 无法获取
  2. DOM 无法获得
  3. AJAX 请求不能发送

同源条件

协议端口主机 三者相同即为同源

反之,其中只要 某一个 不一样则为不同源

解决方式

本地开发跨域

本地开发一般使用下面 3 种方式进行处理

  1. vite 的 proxy 进行代理
  2. 后台开启 cors
  3. 使用 nginx 转发请求

项目内部自带第一种方式,具体可以参考服务端交互-本地开发环境接口地址修改

生产环境跨域

生产环境一般使用下面 2 种方式进行处理

  1. 后台开启 cors
  2. 使用 nginx 转发请求

后台开启 cors 不需要前端做任何改动

nginx 配置文件可以查看nginx 配置

+ + + + \ No newline at end of file diff --git a/dep/dark.html b/dep/dark.html new file mode 100644 index 00000000..68378215 --- /dev/null +++ b/dep/dark.html @@ -0,0 +1,56 @@ + + + + + + 黑暗主题 | Vben Admin + + + + + + + + + + + + + + + + +

黑暗主题

介绍

项目已经内置了黑暗主题切换,只需配置自己需要的颜色变量,即可在项目中使用

原理

通过 vite-plugin-theme 插件,将所有的颜色变量抽取到独立的 css 文件,并且全部在 html 上面加上 css 选择器。通过改变 html 标签的 data-theme 属性来进行黑暗主题切换

配置

黑暗主题颜色配置通过 vite-plugin-theme 实现,具体代码在 build/vite/plugin/theme

antdDarkThemePlugin({
+  darkModifyVars: {
+    ...generateModifyVars(true),
+    'text-color': '#c9d1d9',
+    'text-color-base': '#c9d1d9',
+    'component-background': '#151515',
+    'text-color-secondary': '#8b949e',
+    'border-color-base': '#303030',
+    'item-active-bg': '#111b26',
+    'app-content-background': 'rgb(255 255 255 / 4%)',
+  },
+});
+

切换

只需要使用 vite-plugin-theme 提供的函数来进行切换即可

import { darkCssIsReady, loadDarkThemeCss } from 'vite-plugin-theme/es/client';
+
+export async function updateDarkTheme(mode: string | null = 'light') {
+  const htmlRoot = document.getElementById('htmlRoot');
+  if (mode === 'dark') {
+    if (import.meta.env.PROD && !darkCssIsReady) {
+      await loadDarkThemeCss();
+    }
+    htmlRoot?.setAttribute('data-theme', 'dark');
+  } else {
+    htmlRoot?.setAttribute('data-theme', 'light');
+  }
+}
+
+ + + + \ No newline at end of file diff --git a/dep/i18n.html b/dep/i18n.html new file mode 100644 index 00000000..b9ca02c5 --- /dev/null +++ b/dep/i18n.html @@ -0,0 +1,150 @@ + + + + + + 国际化 | Vben Admin + + + + + + + + + + + + + + + + +

国际化

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

I18n-ally 插件

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

配置默认语言

src/settings/localeSetting.ts 内可以配置默认语言

export const LOCALE: { [key: string]: LocaleType } = {
+  ZH_CN: 'zh_CN',
+  EN_US: 'en',
+};
+
+export const localeSetting: LocaleSetting = {
+  // 是否显示语言选择器
+  showPicker: true,
+  // 当前语言
+  locale: LOCALE.ZH_CN,
+  // 默认语言
+  fallback: LOCALE.ZH_CN,
+  // 允许的语言
+  availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US],
+};
+
+// 配置语言列表
+export const localeList: DropMenu[] = [
+  {
+    text: '简体中文',
+    event: 'zh_CN',
+  },
+  {
+    text: 'English',
+    event: 'en',
+  },
+];
+

配置

src/locales/setupI18n.ts 内引入的 i18n 这个无需修改

语言文件

src/locales/lang/ 可以配置具体的语言

# locales/lang/
+
+# 中文语言
+zh_CN:
+  component: 组件相关
+  layout: 布局相关
+  routes: 路由菜单相关
+  sys: 系统页面相关
+
+en: 同上
+
+

语言导入逻辑说明

  1. 初始化

src/locales/setupI18n 内的根语言文件可以看到

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

这会导入 src/locales/lang/{lang}.ts 文件语言包,此文件会导入对应语言下的所有文件。

import { genMessage } from '../helper';
+import antdLocale from 'ant-design-vue/es/locale/zh_CN';
+import momentLocale from 'moment/dist/locale/zh-cn';
+
+const modules = import.meta.globEager('./zh_CN/**/*.ts');
+export default {
+  message: {
+    ...genMessage(modules, 'zh_CN'),
+    antdLocale,
+  },
+  momentLocale,
+  momentLocaleName: 'zh-cn',
+};
+

并将其按相应的目录结构转化为多层级的

例:

lang/zh_CN/components/modal.ts 的文件内容为

{
+  title: '标题';
+}
+

则在使用的使用直接使用 t('components.modal.title') 进行获取。

这样做的好处在于更容易管理大型项目的多语言。如果不需要分模块划分,可以直接自己手动导入即可。

使用

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

import { useI18n } from '/@/hooks/web/useI18n';
+
+const { t } = useI18n();
+
+const title = t('components.modal.title');
+

切换语言

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

import { useLocale } from '/@/locales/useLocale';
+
+const { changeLocale } = useLocale();
+
+changeLocale('en');
+

新增

语言文件

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

新增语言

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

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

  1. src/locales/lang/ 下新增相应的语言目录及语言文件并引入 引入 ant-design-vue 和 moment 对应的语言包
  2. types/config.d.ts 内加上预览类型定义
  3. src/settings/localeSetting.ts 修改语言配置

远程读取语言数据

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

// src/main.ts
+await setupI18n(app);
+
+app.mount('#app', true);
+

setupI18n 函数

代码: src/locales/setupI18n/

如下所示,这里会先设置一个默认语言,默认语言可以设置在本地,也可以在这里等待接口返回默认语言

// setup i18n instance with glob
+export async function setupI18n(app: App) {
+  const options = await createI18nOptions();
+  i18n = createI18n(options) as I18n;
+  app.use(i18n);
+}
+
+async function createI18nOptions(): Promise<I18nOptions> {
+  const locale = localeStore.getLocale;
+
+  // 这里改成接口获取
+  const defaultLocal = await import(`./lang/${locale}.ts`);
+  const message = defaultLocal.default?.message ?? {};
+
+  return {
+    legacy: false,
+    locale,
+    fallbackLocale: fallback,
+    messages: {
+      [locale]: message,
+    },
+    availableLocales: availableLocales,
+    sync: true,
+    silentTranslationWarn: true,
+    missingWarn: false,
+    silentFallbackWarn: true,
+  };
+}
+

changeLocale 函数

代码: src/locales/useLocale/

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

async function changeLocale(locale: LocaleType) {
+  const globalI18n = i18n.global;
+  const currentLocale = unref(globalI18n.locale);
+  if (currentLocale === locale) return locale;
+
+  if (loadLocalePool.includes(locale)) {
+    setI18nLanguage(locale);
+    return locale;
+  }
+  // 这里改成接口获取
+  const langModule = ((await import(`./lang/${locale}.ts`)) as any).default as LangModule;
+  if (!langModule) return;
+
+  const { message, momentLocale, momentLocaleName } = langModule;
+
+  globalI18n.setLocaleMessage(locale, message);
+  moment.updateLocale(momentLocaleName, momentLocale);
+  loadLocalePool.push(locale);
+
+  setI18nLanguage(locale);
+  return locale;
+}
+
+ + + + \ No newline at end of file diff --git a/dep/icon.html b/dep/icon.html new file mode 100644 index 00000000..a05e080e --- /dev/null +++ b/dep/icon.html @@ -0,0 +1,193 @@ + + + + + + 图标 | Vben Admin + + + + + + + + + + + + + + + + +

图标

项目中有以下多种图标使用方式。

组件库图标

使用 ant-design-vue 提供的图标

<template>
+  <StarOutlined />
+  <StarFilled />
+  <StarTwoTone twoToneColor="#eb2f96" />
+</template>
+
+<script>
+  import { defineComponent } from 'vue';
+  import { StarOutlined, StarFilled, StarTwoTone } from '@ant-design/icons-vue';
+  export default defineComponent({
+    components: { StarOutlined, StarFilled, StarTwoTone },
+  });
+</script>
+

Svg Sprite 图标

使用

将需要的 svg 图标放到src/assets/icons

例: test.svg

  1. 使用SvgIcon组件进行展示
<template>
+  <SvgIcon name="test" />
+</template>
+
+<script>
+  import { defineComponent } from 'vue';
+  import { SvgIcon } from '/@/components/Icon';
+  export default defineComponent({
+    components: { SvgIcon },
+  });
+</script>
+
  1. 使用Icon组件进行展示

|svg 结尾会自动使用SvgIcon组件

<template>
+  <Icon name="test|svg" />
+</template>
+
+<script>
+  import { defineComponent } from 'vue';
+  import { Icon } from '/@/components/Icon';
+  export default defineComponent({
+    components: { Icon },
+  });
+</script>
+

Iconify 图标

使用方式请参考 Icon 组件

项目中使用到的是 vite-plugin-purge-icons 这个插件来进行图标实现。

  1. 安装依赖

+yarn add @iconify/iconify
+
+yarn add @iconify/json @purge-icons/generated -D
+
+
  1. vite.config.ts内引入插件
import PurgeIcons from 'vite-plugin-purge-icons';
+
+export default {
+  plugins: [PurgeIcons()],
+};
+
  1. 编写 Icon 组件

完整代码 src/components/Icon/src/Icon.vue

<template>
+  <SvgIcon :size="size" :name="getSvgIcon" v-if="isSvgIcon" :class="[$attrs.class]" :spin="spin" />
+  <span
+    v-else
+    ref="elRef"
+    :class="[$attrs.class, 'app-iconify anticon', spin && 'app-iconify-spin']"
+    :style="getWrapStyle"
+  ></span>
+</template>
+<script lang="ts">
+  import type { PropType } from 'vue';
+  import {
+    defineComponent,
+    ref,
+    watch,
+    onMounted,
+    nextTick,
+    unref,
+    computed,
+    CSSProperties,
+  } from 'vue';
+
+  import SvgIcon from './SvgIcon.vue';
+  import Iconify from '@purge-icons/generated';
+  import { isString } from '/@/utils/is';
+  import { propTypes } from '/@/utils/propTypes';
+
+  const SVG_END_WITH_FLAG = '|svg';
+  export default defineComponent({
+    name: 'GIcon',
+    components: { SvgIcon },
+    props: {
+      // icon name
+      icon: propTypes.string,
+      // icon color
+      color: propTypes.string,
+      // icon size
+      size: {
+        type: [String, Number] as PropType<string | number>,
+        default: 16,
+      },
+      spin: propTypes.bool.def(false),
+      prefix: propTypes.string.def(''),
+    },
+    setup(props) {
+      const elRef = ref<ElRef>(null);
+
+      const isSvgIcon = computed(() => props.icon?.endsWith(SVG_END_WITH_FLAG));
+      const getSvgIcon = computed(() => props.icon.replace(SVG_END_WITH_FLAG, ''));
+      const getIconRef = computed(() => `${props.prefix ? props.prefix + ':' : ''}${props.icon}`);
+
+      const update = async () => {
+        if (unref(isSvgIcon)) return;
+
+        const el = unref(elRef);
+        if (!el) return;
+
+        await nextTick();
+        const icon = unref(getIconRef);
+        if (!icon) return;
+
+        const svg = Iconify.renderSVG(icon, {});
+        if (svg) {
+          el.textContent = '';
+          el.appendChild(svg);
+        } else {
+          const span = document.createElement('span');
+          span.className = 'iconify';
+          span.dataset.icon = icon;
+          el.textContent = '';
+          el.appendChild(span);
+        }
+      };
+
+      const getWrapStyle = computed((): CSSProperties => {
+        const { size, color } = props;
+        let fs = size;
+        if (isString(size)) {
+          fs = parseInt(size, 10);
+        }
+
+        return {
+          fontSize: `${fs}px`,
+          color: color,
+          display: 'inline-flex',
+        };
+      });
+
+      watch(() => props.icon, update, { flush: 'post' });
+
+      onMounted(update);
+
+      return { elRef, getWrapStyle, isSvgIcon, getSvgIcon };
+    },
+  });
+</script>
+<style lang="less">
+  .app-iconify {
+    display: inline-block;
+    // vertical-align: middle;
+
+    &-spin {
+      svg {
+        animation: loadingCircle 1s infinite linear;
+      }
+    }
+  }
+
+  span.iconify {
+    display: block;
+    min-width: 1em;
+    min-height: 1em;
+    background-color: @iconify-bg-color;
+    border-radius: 100%;
+  }
+</style>
+

图标选择器

图标集预生成

由于图标选择器这个比较特殊的存在,项目会打包一些比较多的图标,图标选择器的图标需要事先指定并生成相应的文件。

生成

  • 执行图标生成命令
yarn gen:icon
+
  • 这里会让你选择本地还是在线生成,两种方式各有利弊。如下图所示

local 表示本地,online 表示在线,回车确认

  • 选择你要生成的图标集,回车确认

  • 选择图标输出的目录(项目默认 src/components/Icon/data),可以直接回车选择默认

到这里图标集已经生成完成了,此时你的图标选择器已经是你所选的的图标集的图标了。

注意不要频繁更新

如果前面选择的是本地生成的话,频繁更换图标集,可能会导致图标丢失或者显示不出来

优缺点

  • 在线图标(项目默认,推荐)

该方式会在图标选择器使用到图标的时候进行在线请求,然后缓存对应的图标到浏览器。可以有效减少代码打包体积。

如果你的项目可以访问外网,建议可以使用这种方式

缺点: 在局域网或者无法访问到外网的环境中图标显示不出来

  • 本地图标

该方式会在打包的时候将图标选择器的图标全部打包到 js 内。在使用的时候不会额外的请求在线图标

缺点: 打包体积会偏大,具体的体积增加得看前面选择图标集的时候选择的图标数量的多少决定

+ + + + \ No newline at end of file diff --git a/dep/lint.html b/dep/lint.html new file mode 100644 index 00000000..56c31364 --- /dev/null +++ b/dep/lint.html @@ -0,0 +1,51 @@ + + + + + + Lint | Vben Admin + + + + + + + + + + + + + + + + +

Lint

介绍

使用 lint 的好处

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

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

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

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

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

WARNING

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

ESLint

ESLint 是一个代码规范和错误检查工具,有以下几个特性

  • 所有东西都是可以插拔的。你可以调用任意的 rule api 或者 formatter api 去打包或者定义 rule or formatter。
  • 任意的 rule 都是独立的
  • 没有特定的 coding style,你可以自己配置

手动校验代码

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

配置项

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

编辑器配合

推荐使用 vscode 进行开发,vscode 自带 eslint 插件,可以自动修改一些错误。

同时项目内也自带了 vscode eslint 配置,具体在 .vscode/setting.json 文件夹内部。只要使用 vscode 开发不用任何设置即可使用

CommitLint

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

配置

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

Git 提交规范

  • 参考 vue 规范 (Angular)

    • feat 增加新功能
    • fix 修复问题/BUG
    • style 代码风格相关无影响运行结果的
    • perf 优化/性能提升
    • refactor 重构
    • revert 撤销修改
    • test 测试相关
    • docs 文档/注释
    • chore 依赖更新/脚手架配置修改等
    • workflow 工作流改进
    • ci 持续集成
    • mod 不确定分类的修改
    • wip 开发中
    • types 类型修改

如何关闭

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

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

示例


+git commit -m 'feat(home): add home page'
+
+

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

如何关闭

# 删除husky依赖即可
+yarn remove huksy
+
+

如何跳过某一个检查

# 加上 --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/guide/auth.html b/guide/auth.html new file mode 100644 index 00000000..6ef68483 --- /dev/null +++ b/guide/auth.html @@ -0,0 +1,260 @@ + + + + + + 权限 | Vben Admin + + + + + + + + + + + + + + + + +

权限

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

  1. 通过用户角色来过滤菜单(前端方式控制),菜单和路由分开配置
  2. 通过用户角色来过滤菜单(前端方式控制),菜单由路由配置自动生成
  3. 通过后台来动态生成路由表(后台方式控制)

前端角色权限

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

缺点: 权限相对不自由,如果后台改动角色,前台也需要跟着改动。适合角色较固定的系统

实现

  1. 项目配置将系统内权限模式改为 ROLE 模式
// ! 改动后需要清空浏览器缓存
+const setting: ProjectConfig = {
+  // 权限模式
+  permissionMode: PermissionModeEnum.ROLE,
+};
+
  1. 在路由表配置路由所需的权限,如果不配置,默认可见(见注释)
import type { AppRouteModule } from '/@/router/types';
+
+import { getParentLayout, LAYOUT } from '/@/router/constant';
+import { RoleEnum } from '/@/enums/roleEnum';
+import { t } from '/@/hooks/web/useI18n';
+
+const permission: AppRouteModule = {
+  path: '/permission',
+  name: 'Permission',
+  component: LAYOUT,
+  redirect: '/permission/front/page',
+  meta: {
+    icon: 'ion:key-outline',
+    title: t('routes.demo.permission.permission'),
+  },
+
+  children: [
+    {
+      path: 'front',
+      name: 'PermissionFrontDemo',
+      component: getParentLayout('PermissionFrontDemo'),
+      meta: {
+        title: t('routes.demo.permission.front'),
+      },
+      children: [
+        {
+          path: 'auth-pageA',
+          name: 'FrontAuthPageA',
+          component: () => import('/@/views/demo/permission/front/AuthPageA.vue'),
+          meta: {
+            title: t('routes.demo.permission.frontTestA'),
+            roles: [RoleEnum.SUPER],
+          },
+        },
+        {
+          path: 'auth-pageB',
+          name: 'FrontAuthPageB',
+          component: () => import('/@/views/demo/permission/front/AuthPageB.vue'),
+          meta: {
+            title: t('routes.demo.permission.frontTestB'),
+            roles: [RoleEnum.TEST],
+          },
+        },
+      ],
+    },
+  ],
+};
+
+export default permission;
+
  1. 在路由钩子内动态判断

详细代码见 src/router/guard/permissionGuard.ts

// 这里只列举了主要代码
+const routes = await permissionStore.buildRoutesAction();
+
+routes.forEach((route) => {
+  router.addRoute(route as unknown as RouteRecordRaw);
+});
+
+const redirectPath = (from.query.redirect || to.path) as string;
+const redirect = decodeURIComponent(redirectPath);
+const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
+permissionStore.setDynamicAddedRoute(true);
+next(nextData);
+

permissionStore.buildRoutesAction 用于过滤动态路由,详细代码见 src/store/modules/permission.ts

// 主要代码
+if (permissionMode === PermissionModeEnum.ROLE) {
+  const routeFilter = (route: AppRouteRecordRaw) => {
+    const { meta } = route;
+    const { roles } = meta || {};
+    if (!roles) return true;
+    return roleList.some((role) => roles.includes(role));
+  };
+  routes = filter(asyncRoutes, routeFilter);
+  routes = routes.filter(routeFilter);
+  // Convert multi-level routing to level 2 routing
+  routes = flatMultiLevelRoutes(routes);
+}
+

动态更换角色

系统提供 usePermission 方便角色相关操作

import { usePermission } from '/@/hooks/web/usePermission';
+import { RoleEnum } from '/@/enums/roleEnum';
+
+export default defineComponent({
+  setup() {
+    const { changeRole } = usePermission();
+    // 更换为test角色
+    // 动态更改角色,传入角色名称,可以是数组
+    changeRole(RoleEnum.TEST);
+    return {};
+  },
+});
+

细粒度权限

函数方式

usePermission 还提供了按钮级别的权限控制。

<template>
+  <a-button v-if="hasPermission([RoleEnum.TEST, RoleEnum.SUPER])" color="error" class="mx-4">
+    拥有[test,super]角色权限可见
+  </a-button>
+</template>
+<script lang="ts">
+  import { usePermission } from '/@/hooks/web/usePermission';
+  import { RoleEnum } from '/@/enums/roleEnum';
+
+  export default defineComponent({
+    setup() {
+      const { hasPermission } = usePermission();
+
+      return { hasPermission };
+    },
+  });
+</script>
+

组件方式

具体查看权限组件使用

指令方式

TIP

指令方式不能动态更改权限

<a-button v-auth="RoleEnum.SUPER" type="primary" class="mx-4"> 拥有super角色权限可见</a-button>
+

后台动态获取

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

实现

  1. 项目配置将系统内权限模式改为 BACK 模式
// ! 改动后需要清空浏览器缓存
+const setting: ProjectConfig = {
+  // 权限模式
+  permissionMode: PermissionModeEnum.BACK,
+};
+
  1. 路由拦截,与角色权限模式一致

permissionStore.buildRoutesAction 用于过滤动态路由,详细代码见 /@/store/modules/permission.ts

// 主要代码
+if (permissionMode === PermissionModeEnum.BACK) {
+  const { createMessage } = useMessage();
+
+  createMessage.loading({
+    content: t('sys.app.menuLoading'),
+    duration: 1,
+  });
+
+  // !Simulate to obtain permission codes from the background,
+  // this function may only need to be executed once, and the actual project can be put at the right time by itself
+  let routeList: AppRouteRecordRaw[] = [];
+  try {
+    this.changePermissionCode();
+    routeList = (await getMenuList()) as AppRouteRecordRaw[];
+  } catch (error) {
+    console.error(error);
+  }
+
+  // Dynamically introduce components
+  routeList = transformObjToRoute(routeList);
+
+  //  Background routing to menu structure
+  const backMenuList = transformRouteToMenu(routeList);
+  this.setBackMenuList(backMenuList);
+
+  routeList = flatMultiLevelRoutes(routeList);
+  routes = [PAGE_NOT_FOUND_ROUTE, ...routeList];
+}
+

getMenuList 返回值格式

返回值由多个路由模块组成

注意

后端接口返回的数据中必须包含PageEnum.BASE_HOME指定的路由(path定义于src/enums/pageEnum.ts

[
+  {
+    path: '/dashboard',
+    name: 'Dashboard',
+    component: '/dashboard/welcome/index',
+    meta: {
+      title: 'routes.dashboard.welcome',
+      affix: true,
+      icon: 'ant-design:home-outlined',
+    },
+  },
+  {
+    path: '/permission',
+    name: 'Permission',
+    component: 'LAYOUT',
+    redirect: '/permission/front/page',
+    meta: {
+      icon: 'carbon:user-role',
+      title: 'routes.demo.permission.permission',
+    },
+    children: [
+      {
+        path: 'back',
+        name: 'PermissionBackDemo',
+        meta: {
+          title: 'routes.demo.permission.back',
+        },
+
+        children: [
+          {
+            path: 'page',
+            name: 'BackAuthPage',
+            component: '/demo/permission/back/index',
+            meta: {
+              title: 'routes.demo.permission.backPage',
+            },
+          },
+          {
+            path: 'btn',
+            name: 'BackAuthBtn',
+            component: '/demo/permission/back/Btn',
+            meta: {
+              title: 'routes.demo.permission.backBtn',
+            },
+          },
+        ],
+      },
+    ],
+  },
+];
+

动态更换菜单

系统提供 usePermission 方便角色相关操作

import { usePermission } from '/@/hooks/web/usePermission';
+import { RoleEnum } from '/@/enums/roleEnum';
+
+export default defineComponent({
+  setup() {
+    const { changeMenu } = usePermission();
+
+    // 更改菜单的实现需要自行去修改
+    changeMenu();
+    return {};
+  },
+});
+

细粒度权限

函数方式

usePermission 还提供了按钮级别的权限控制。

<template>
+  <a-button v-if="hasPermission(['20000', '2000010'])" color="error" class="mx-4">
+    拥有[20000,2000010]code可见
+  </a-button>
+</template>
+<script lang="ts">
+  import { usePermission } from '/@/hooks/web/usePermission';
+  import { RoleEnum } from '/@/enums/roleEnum';
+
+  export default defineComponent({
+    setup() {
+      const { hasPermission } = usePermission();
+      return { hasPermission };
+    },
+  });
+</script>
+

组件方式

具体查看权限组件使用

指令方式

TIP

指令方式不能动态更改权限

<a-button v-auth="'1000'" type="primary" class="mx-4"> 拥有code ['1000']权限可见 </a-button>
+

如何初始化 code

通常,如需做按钮级别权限,后台会提供相应的 code,或者类型的判断标识。这些编码只需要在登录后获取一次即可。

import { getPermCodeByUserId } from '/@/api/sys/user';
+import { permissionStore } from '/@/store/modules/permission';
+async function changePermissionCode(userId: string) {
+  // 从后台获取当前用户拥有的编码
+  const codeList = await getPermCodeByUserId({ userId });
+  permissionStore.commitPermCodeListState(codeList);
+}
+
+ + + + \ No newline at end of file diff --git a/guide/component.html b/guide/component.html new file mode 100644 index 00000000..4cc0e749 --- /dev/null +++ b/guide/component.html @@ -0,0 +1,145 @@ + + + + + + 组件注册 | Vben Admin + + + + + + + + + + + + + + + + +

组件注册

按需引入

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

<template>
+  <Menu>
+    <SubMenu></SubMenu>
+  <Menu>
+
+  <menu>
+    <sub-menu></sub-menu>
+  <menu>
+</template>
+<script>
+import { Menu } from 'ant-design-vue';
+export default defineComponent({
+  components: {
+    Menu: Menu,
+    SubMenu: Menu.SubMenu
+  },
+})
+</script>
+

tsx 文件注册

tsx 文件内不能使用全局注册组件

import { Menu } from 'ant-design-vue';
+
+export default defineComponent({
+  setup() {
+    return () => (
+      <Menu>
+        <Menu.SubMenu></Menu.SubMenu>
+      </Menu>
+    );
+  },
+});
+

全局注册

如果不习惯按需引入方式,可以进行全局注册。全局注册也分两种方式

全局按需注册

只注册需要的组件

代码地址:src/components/registerGlobComp.ts

import {
+  // Need
+  Button as AntButton,
+  Optional,
+  Select,
+  Alert,
+  Checkbox,
+  DatePicker,
+  Radio,
+  Switch,
+  Card,
+  List,
+  Tabs,
+  Descriptions,
+  Tree,
+  Table,
+  Divider,
+  Modal,
+  Drawer,
+  Dropdown,
+  Tag,
+  Tooltip,
+  Badge,
+  Popover,
+  Upload,
+  Transfer,
+  Steps,
+  PageHeader,
+  Result,
+  Empty,
+  Avatar,
+  Menu,
+  Breadcrumb,
+  Form,
+  Input,
+  Row,
+  Col,
+  Spin,
+} from 'ant-design-vue';
+
+export function registerGlobComp(app: App) {
+  app
+    .use(Select)
+    .use(Alert)
+    .use(Breadcrumb)
+    .use(Checkbox)
+    .use(DatePicker)
+    .use(Radio)
+    .use(Switch)
+    .use(Card)
+    .use(List)
+    .use(Descriptions)
+    .use(Tree)
+    .use(Table)
+    .use(Divider)
+    .use(Modal)
+    .use(Drawer)
+    .use(Dropdown)
+    .use(Tag)
+    .use(Tooltip)
+    .use(Badge)
+    .use(Popover)
+    .use(Upload)
+    .use(Transfer)
+    .use(Steps)
+    .use(PageHeader)
+    .use(Result)
+    .use(Empty)
+    .use(Avatar)
+    .use(Menu)
+    .use(Tabs)
+    .use(Form)
+    .use(Input)
+    .use(Row)
+    .use(Col)
+    .use(Spin);
+}
+

全量注册

  • main.ts
import { createApp } from 'vue';
+import Antd from 'ant-design-vue';
+import 'ant-design-vue/dist/antd.less';
+const app = createApp(App);
+app.use(Antd);
+
  • 删除以下代码
if (import.meta.env.DEV) {
+  import('ant-design-vue/dist/antd.less');
+}
+
+ + + + \ No newline at end of file diff --git a/guide/deploy.html b/guide/deploy.html new file mode 100644 index 00000000..97fa6950 --- /dev/null +++ b/guide/deploy.html @@ -0,0 +1,139 @@ + + + + + + 构建&部署 | Vben Admin + + + + + + + + + + + + + + + + +

构建&部署

前言

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

当然,你也可以使用精简版 vue-vben-admin-thin 进行开发。

构建

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

yarn build
+

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

旧版浏览器兼容

.env.production

设置 VITE_LEGACY=true 即可打包出兼容旧版浏览器的代码

VITE_LEGACY = true
+

预览

发布之前可以在本地进行预览,有多种方式,这里介绍两种

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

  • 使用项目自定的命令进行预览(推荐)
# 先打包再进行预览
+yarn preview
+# 直接预览本地 dist 文件目录
+yarn preview:dist
+
  • 本地服务器预览(通过 live-server)
# 1.全局安装live-server
+yarn global add live-server
+# 2. 进入打包的后目录
+cd ./dist
+# 本地预览,默认端口8080
+live-server
+# 指定端口
+live-server --port 9000
+

分析构建文件体积

如果你的构建文件很大,可以通过项目内置 rollup-plugin-analyzer 插件进行代码体积分析,从而优化你的代码。

yarn report
+
+

运行之后,在自动打开的页面可以看到具体的体积分布,以分析哪些依赖有问题。

TIP

左上角可以切换 显示 gzip 或者 brotli

压缩

开启 gzip 压缩

开启 gzip,并配合 nginx 的 gzip_static 功能可以大大加快页面访问速度

TIP

只需开启 VITE_BUILD_COMPRESS='gzip' 即可在打包的同时生成 .gz 文件

# 根据自己路径来配置更改
+# 例如部署在nginx /next/路径下  则VITE_PUBLIC_PATH=/next/
+VITE_PUBLIC_PATH=/
+

开启 brotli 压缩

brotli 是比 gzip 压缩率更高的算法,可以与 gzip 共存不会冲突,需要 nginx 安装指定模块并开启即可。

TIP

只需开启 VITE_BUILD_COMPRESS='brotli' 即可在打包的同时生成 .br 文件

# 根据自己路径来配置更改
+# 例如部署在nginx /next/路径下  则VITE_PUBLIC_PATH=/next/
+VITE_PUBLIC_PATH=/
+

同时开启 gzip 与 brotli

只需开启 VITE_BUILD_COMPRESS='brotli,gzip' 即可在打包的同时生成 .gz.br 文件。

gzip 与 brotli 在 nginx 内的配置

http {
+  # 开启gzip
+  gzip on;
+  # 开启gzip_static
+  # gzip_static 开启后可能会报错,需要安装相应的模块, 具体安装方式可以自行查询
+  # 只有这个开启,vue文件打包的.gz文件才会有效果,否则不需要开启gzip进行打包
+  gzip_static on;
+  gzip_proxied any;
+  gzip_min_length 1k;
+  gzip_buffers 4 16k;
+  #如果nginx中使用了多层代理 必须设置这个才可以开启gzip。
+  gzip_http_version 1.0;
+  gzip_comp_level 2;
+  gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
+  gzip_vary off;
+  gzip_disable "MSIE [1-6]\.";
+
+  # 开启 brotli压缩
+  # 需要安装对应的nginx模块,具体安装方式可以自行查询
+  # 可以与gzip共存不会冲突
+  brotli on;
+  brotli_comp_level 6;
+  brotli_buffers 16 8k;
+  brotli_min_length 20;
+  brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
+}
+

部署

注意

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

发布

简单的部署只需要将最终生成的静态文件,dist 文件夹的静态文件发布到你的 cdn 或者静态服务器即可,需要注意的是其中的 index.html 通常会是你后台服务的入口页面,在确定了 js 和 css 的静态之后可能需要改变页面的引入路径。

例如上传到 nginx

/srv/www/project/index.html

# nginx配置
+location / {
+  # 不缓存html,防止程序更新后缓存继续生效
+  if ($request_filename ~* .*\.(?:htm|html)$) {
+    add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
+    access_log on;
+  }
+  # 这里是vue打包文件dist内的文件的存放路径
+  root   /srv/www/project/;
+  index  index.html index.htm;
+}
+
+

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

# 根据自己路径来配置更改
+# 注意需要以 / 开头和结尾
+VITE_PUBLIC_PATH=/
+VITE_PUBLIC_PATH=/xxx/
+

前端路由与服务端的结合

项目前端路由使用的是 vue-router,所以你可以选择两种方式:history 和 hash。

  • hash 默认会在 url 后面拼接#
  • history 则不会,不过 history 需要服务器配合

可在 src/router/index.ts 内进行 mode 修改

import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
+
+createRouter({
+  history: createWebHashHistory(),
+  // or
+  history: createWebHistory(),
+});
+

history 路由模式下服务端配置

开启 history 模式需要服务器配置,更多的服务器配置详情可以看 history-mode

这里以 nginx 配置为例

部署到根目录

server {
+  listen 80;
+  location / {
+    # 用于配合 History 使用
+    try_files $uri $uri/ /index.html;
+  }
+}
+

部署到非根目录

  1. 首先需要在打包的时候更改配置
# 在.env.production内,配置子目录路径
+VITE_PUBLIC_PATH = /sub/
+
server {
+    listen       80;
+    server_name  localhost;
+    location /sub/ {
+      # 这里是vue打包文件dist内的文件的存放路径
+      alias   /srv/www/project/;
+      index index.html index.htm;
+      try_files $uri $uri/ /sub/index.html;
+    }
+}
+

使用 nginx 处理跨域

使用 nginx 处理项目部署后的跨域问题

  1. 配置前端项目接口地址
# 在.env.production内,配置接口地址
+VITE_GLOB_API_URL=/api
+
  1. 在 nginx 配置请求转发到后台
server {
+  listen       8080;
+  server_name  localhost;
+  # 接口代理,用于解决跨域问题
+  location /api {
+    proxy_set_header Host $host;
+    proxy_set_header X-Real-IP $remote_addr;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    # 后台接口地址
+    proxy_pass http://110.110.1.1:8080/api;
+    proxy_redirect default;
+    add_header Access-Control-Allow-Origin *;
+    add_header Access-Control-Allow-Headers X-Requested-With;
+    add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
+  }
+}
+
+ + + + \ No newline at end of file diff --git a/guide/design.html b/guide/design.html new file mode 100644 index 00000000..691473a3 --- /dev/null +++ b/guide/design.html @@ -0,0 +1,107 @@ + + + + + + 样式 | Vben Admin + + + + + + + + + + + + + + + + +

样式

介绍

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

默认使用 less 作为预处理语言,建议在使用前或者遇到疑问时学习一下 Less 的相关特性(如果想获取基础的 CSS 知识或查阅属性,请参考 MDN 文档)。

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

.
+├── ant # ant design 一些样式覆盖
+├── color.less # 颜色
+├── index.less # 入口
+├── public.less # 公共类
+├── theme.less # 主题相关
+├── config.less  # 每个组件都会自动引入样式
+├── transition # 动画相关
+└── var # 变量
+
+

全局注入

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

<style lang="less" scoped>
+  // 这里已经隐式注入了 config.less
+</style>
+

tailwindcss(2.5.0+)

项目中引用到了 tailwindcss,具体可以见文件使用说明。

语法如下:

<div class="relative w-full h-full px-4"></div>
+

windicss(2.5.0 已弃用)

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

语法如下:

<div class="relative w-full h-full px-4"></div>
+

注意事项

windcss 目前会造成本地开发内存溢出,所以后续可能会考虑切换到 TailwindCss,两者基本相同。

所以尽量少用 Windicss 新增的特性,防止后续切换成本高。

为什么使用 Less

主要是因为 Ant Design 默认使用 less 作为样式语言,使用 Less 可以跟其保持一致。

开启 scoped

没有加 scoped 属性,默认会编译成全局样式,可能会造成全局污染

<style></style>
+
+<style scoped></style>
+

温馨提醒

使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。

深度选择器

有时我们可能想明确地制定一个针对子组件的规则。

如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作符。有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下你可以使用 /deep/::v-deep 操作符取而代之——两者都是 >>> 的别名,同样可以正常工作。

详情可以查看 RFC0023-scoped-styles-changes

使用 scoped 后,父组件的样式将不会渗透到子组件中,所以可以使用以下方式解决:

<style scoped>
+  /* deep selectors */
+  ::v-deep(.foo) {
+  }
+  /* shorthand */
+  :deep(.foo) {
+  }
+
+  /* targeting slot content */
+  ::v-slotted(.foo) {
+  }
+  /* shorthand */
+  :slotted(.foo) {
+  }
+
+  /* one-off global rule */
+  ::v-global(.foo) {
+  }
+  /* shorthand */
+  :global(.foo) {
+  }
+</style>
+

CSS Modules

针对样式覆盖问题,还有一种方案是使用 CSS Modules 模块化方案。使用方式如下。

<template>
+  <span :class="$style.span1">hello</span>
+</template>
+
+<script>
+  import { useCSSModule } from 'vue';
+
+  export default {
+    setup(props, context) {
+      const $style = useCSSModule();
+      const moduleAStyle = useCSSModule('moduleA');
+      return {
+        $style,
+        moduleAStyle,
+      };
+    },
+  };
+</script>
+
+<style lang="less" module>
+  .span1 {
+    color: green;
+    font-size: 30px;
+  }
+</style>
+
+<style lang="less" module="moduleA">
+  .span1 {
+    color: green;
+    font-size: 30px;
+  }
+</style>
+

重复引用问题

加上 reference 可以解决页面内重复引用导致实际生成的 style 样式表重复的问题。

这步已经全局引入了。所以可以不写,直接使用变量

<style lang="less" scoped>
+  /* 该行代码已全局引用。可以不用单独引入 */
+  @import (reference) '../../design/config.less';
+<style>
+
+ + + + \ No newline at end of file diff --git a/guide/electron.html b/guide/electron.html new file mode 100644 index 00000000..81ec6918 --- /dev/null +++ b/guide/electron.html @@ -0,0 +1,37 @@ + + + + + + Electron | Vben Admin + + + + + + + + + + + + + + + + +

Electron

URL 模式

这种模式会先启动 vite 服务,Electron 使用 Url 地址来进行渲染

使用

从 GitHub 获取代码

Electron 代码在 electron-main 分支

# clone electron-main分支代码
+git clone -b electron-main https://github.com/vbenjs/vue-vben-admin vben-admin-electron
+

安装依赖

yarn
+

提示

首次下载 Electron 依赖会比较慢,可以在项目根目录下新建.npmrc文件,填入下方内容即可

ELETRON_MIRROR=https://npm.taobao.org/mirrors/electron/
+

运行

yarn dev:app
+

打包

yarn build:app
+

标准模式

TODO: 待适配

+ + + + \ No newline at end of file diff --git a/guide/index.html b/guide/index.html new file mode 100644 index 00000000..94cc42e9 --- /dev/null +++ b/guide/index.html @@ -0,0 +1,130 @@ + + + + + + 开始 | Vben Admin + + + + + + + + + + + + + + + + +

开始

本文会帮助你从头启动项目

前言

关于组件

项目虽然二次封装了一些组件,但是可能不能满足大部分的要求。所以,如果组件不满足你的要求,完全可以不用甚至删除代码自己写,不必坚持使用项目自带的组件。

环境准备

本地环境需要安装 pnpmNode.jsGit

注意

  • 推荐使用pnpm,否则依赖可能安装不上。
  • Node.js 版本要求14.x以上,这里推荐 20.x 及以上。
  • 推荐安装 nvm 来管理 Node.js 版本。

工具配置

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

代码获取

注意

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

从 GitHub 获取代码

# clone 代码
+git clone https://github.com/vbenjs/vue-vben-admin.git
+
+

从 Gitee 获取代码

如果从 github clone 代码较慢的话,可以尝试用 Gitee 同步代码到自己的仓库,再 clone 下来即可。

也可以通过下方地址进行 clone

git clone https://gitee.com/annsion/vue-vben-admin.git
+

注意

Gitee的代码可能不是最新的

安装

安装 Node.js

如果您电脑未安装Node.js,请安装它。

验证

# 出现相应npm版本即可
+npm -v
+# 出现相应node版本即可
+node -v
+
+

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

安装依赖

pnpm 安装

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

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

# 全局安装pnpm
+npm install -g pnpm
+# 验证
+pnpm -v # 出现对应版本号即代表安装成功
+

依赖安装命令

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

# 安装依赖
+pnpm i
+

安装依赖时 husky 安装失败

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

npm script

"scripts": {
+  # 安装依赖
+  "bootstrap": "pnpm install",
+  # 构建项目
+  "build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build",
+  # 生成打包分析,在电脑上执行完成后会自动打开界面
+  "build:analyze": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build --mode analyze",
+  # 构建成docker镜像
+  "build:docker": "vite build --mode docker",
+  # 清空缓存后构建项目
+  "build:no-cache": "pnpm clean:cache && npm run build",
+  "build:test": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpm vite build --mode test",
+  # 用于生成标准化的git commit message
+  "commit": "czg",
+  # 运行项目
+  "dev": "pnpm vite",
+  "preinstall": "npx only-allow pnpm",
+  "postinstall": "turbo run stub",
+  "lint": "turbo run lint",
+  # 执行 eslint 校验,并修复部分问题
+  "lint:eslint": "eslint --cache --max-warnings 0  \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
+  # 执行 prettier 格式化(该命令会对项目所有代码进行 prettier 格式化,请谨慎执行)
+  "lint:prettier": "prettier --write .",
+  # 执行 stylelint 格式化
+  "lint:stylelint": "stylelint \"**/*.{vue,css,less,scss}\" --fix --cache --cache-location node_modules/.cache/stylelint/",
+  # 安装git hooks
+  "prepare": "husky install",
+  # 预览打包后的内容(先打包在进行预览)
+  "preview": "npm run build && vite preview",
+  # 重新安装依赖
+  "reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
+  # 运行项目
+  "serve": "npm run dev",
+  # 对打包结果进行 gzip 测试
+  "test:gzip": "npx http-server dist --cors --gzip -c-1",
+  # 类型检查
+  "type:check": "vue-tsc --noEmit --skipLibCheck"
+},
+

生成图标集

该命令会生成所选择的图标集,提供给图标选择器使用。具体使用方式请查看 图标集生成

重新安装依赖

该命令会先删除 node_modulesyarn.lockpackage.lock.json 后再进行依赖重新安装(安装速度会明显变慢)。

接下来你可以修改代码进行业务开发了。我们内建了模拟数据、HMR 实时预览、状态管理、国际化、全局路由等各种实用的功能辅助开发,请阅读其他章节了解更多。

目录说明


+.
+├── build # 打包脚本相关
+│   ├── config # 配置文件
+│   ├── generate # 生成器
+│   ├── script # 脚本
+│   └── vite # vite配置
+├── mock # mock文件夹
+├── public # 公共静态资源目录
+├── src # 主目录
+│   ├── api # 接口文件
+│   ├── assets # 资源文件
+│   │   ├── icons # icon sprite 图标文件夹
+│   │   ├── images # 项目存放图片的文件夹
+│   │   └── svg # 项目存放svg图片的文件夹
+│   ├── components # 公共组件
+│   ├── design # 样式文件
+│   ├── directives # 指令
+│   ├── enums # 枚举/常量
+│   ├── hooks # hook
+│   │   ├── component # 组件相关hook
+│   │   ├── core # 基础hook
+│   │   ├── event # 事件相关hook
+│   │   ├── setting # 配置相关hook
+│   │   └── web # web相关hook
+│   ├── layouts # 布局文件
+│   │   ├── default # 默认布局
+│   │   ├── iframe # iframe布局
+│   │   └── page # 页面布局
+│   ├── locales # 多语言
+│   ├── logics # 逻辑
+│   ├── main.ts # 主入口
+│   ├── router # 路由配置
+│   ├── settings # 项目配置
+│   │   ├── componentSetting.ts # 组件配置
+│   │   ├── designSetting.ts # 样式配置
+│   │   ├── encryptionSetting.ts # 加密配置
+│   │   ├── localeSetting.ts # 多语言配置
+│   │   ├── projectSetting.ts # 项目配置
+│   │   └── siteSetting.ts # 站点配置
+│   ├── store # 数据仓库
+│   ├── utils # 工具类
+│   └── views # 页面
+├── types # 类型文件
+└── vite.config.ts # vite配置文件
+
+
+ + + + \ No newline at end of file diff --git a/guide/introduction.html b/guide/introduction.html new file mode 100644 index 00000000..c660be6e --- /dev/null +++ b/guide/introduction.html @@ -0,0 +1,39 @@ + + + + + + 介绍 | Vben Admin + + + + + + + + + + + + + + + + +

介绍

简介

Vue-Vben-Admin 是一个基于 Vue3.0ViteAnt-Design-VueTypeScript 的后台解决方案,目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、按钮级别权限控制等功能。项目会使用前端较新的技术栈,可以作为项目的启动模版,以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例,用于学习 vue3vitets 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。

文档

  • 中文文档地址为 vben-admin-doc,采用 Vitepress 开发。如发现文档有误,欢迎提 pr 帮助我们改进。
  • 英文文档暂时没有时间来写,欢迎有时间的同学来帮忙写英文文档。

本地运行文档

如需本地运行文档,请拉取代码到本地。

# 拉取代码
+git clone https://github.com/vbenjs/vue-vben-admin-doc
+
+# 安装依赖
+yarn
+
+# 运行项目
+yarn dev
+

需要掌握的基础知识

本项目需要一定前端基础知识,请确保掌握 Vue 的基础知识,以便能处理一些常见的问题。建议在开发前先学一下以下内容,提前了解和学习这些知识,会对项目理解非常有帮助:

模版

该版本主要是提供一些 Demo 示例及插件的使用集成方式,主要用于参考。如果对项目不是很熟悉,不建议在此基础上进行开发,请使用下方提供的精简版本。

vue-vben-admin 精简版本。删除了相关示例、无用文件及功能、依赖。可以根据自身需求安装对应的依赖。因为使用的是 vite,依赖删除不会导致相关组件或者 hook 发出警告。只在需要的时候安装对应的库即可。

vite 插件推荐

如果这些插件对你有帮助,可以给一个 star 支持下

浏览器支持

本地开发推荐使用Chrome 最新版浏览器,不支持Chrome 80以下版本。

生产环境支持现代浏览器,不支持 IE。

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

如何加入我们

  • Vue-Vben-Admin 还在持续更新中,本项目欢迎您的参与,共同维护,逐步完善,将项目做得更强。同时整个项目本着一切免费的原则,原则上不会收取任何费用及版权,可以放心使用。
  • 如果你想加入我们,可以多提供一些好的建议或者提交 pr,我们会根据你的活跃度邀请你加入。
+ + + + \ No newline at end of file diff --git a/guide/lib.html b/guide/lib.html new file mode 100644 index 00000000..20acefad --- /dev/null +++ b/guide/lib.html @@ -0,0 +1,52 @@ + + + + + + 引入外部模块 | Vben Admin + + + + + + + + + + + + + + + + +

引入外部模块

除了自带组件以外,有时我们还需要引入其他外部模块。我们以 ant-design-vue 为例:

安装

安装 ant-design-vue

# 在终端输入下面的命令完成安装
+yarn add ant-design-vue
+

使用

全局使用

import { createApp } from 'vue';
+import App from './App.vue';
+import Antd from 'ant-design-vue';
+const app = createApp(App);
+app.use(Antd);
+app.mount('#app');
+

局部使用

<template>
+  <Button>text</Button>
+</template>
+
+<script>
+  import { defineComponent } from 'vue';
+  import { Button } from 'ant-design-vue';
+  export default defineComponent({
+    components: {
+      Button,
+    },
+  });
+</script>
+

注意

  • 如果组件有依赖样式,则需要再引入样式文件
+ + + + \ No newline at end of file diff --git a/guide/menu.html b/guide/menu.html new file mode 100644 index 00000000..85e42a21 --- /dev/null +++ b/guide/menu.html @@ -0,0 +1,91 @@ + + + + + + 菜单 | Vben Admin + + + + + + + + + + + + + + + + +

菜单

项目菜单配置存放于 src/router/menus 下面

提示

菜单必须和路由匹配才能显示

菜单项类型

export interface Menu {
+  //  菜单名
+  name: string;
+  // 菜单图标,如果没有,则会尝试使用route.meta.icon
+  icon?: string;
+  // 菜单图片,如果同时传递了icon和img,则只会显示img
+  img?: string;
+  // 菜单路径
+  path: string;
+  // 是否禁用
+  disabled?: boolean;
+  // 子菜单
+  children?: Menu[];
+  // 菜单标签设置
+  tag: {
+    // 为true则显示小圆点
+    dot: boolean;
+    // 内容
+    content: string';
+    // 类型
+    type: 'error' | 'primary' | 'warn' | 'success';
+  };
+  // 是否隐藏菜单
+  hideMenu?: boolean;
+}
+

菜单模块

一个菜单文件会被当作一个模块

提示

children 的 path 字段不需要以/开头

import type { MenuModule } from '/@/router/types';
+import { t } from '/@/hooks/web/useI18n';
+const menu: MenuModule = {
+  orderNo: 10,
+  menu: {
+    name: t('routes.dashboard.dashboard'),
+    path: '/dashboard',
+
+    children: [
+      {
+        path: 'analysis',
+        name: t('routes.dashboard.analysis'),
+      },
+      {
+        path: 'workbench',
+        name: t('routes.dashboard.workbench'),
+      },
+    ],
+  },
+};
+export default menu;
+

以上模块会转化成以下结构

[
+  path: '/dashboard',
+  name: t('routes.dashboard.dashboard'),
+  children: [
+    {
+      path: 'dashboard/analysis',
+      name: t('routes.dashboard.analysis'),
+    },
+    {
+      path: 'dashboard/workbench',
+      name: t('routes.dashboard.workbench'),
+    },
+  ],
+]
+

新增菜单

直接在 src/router/routes/modules 内新增一个模块文件即可。

不需要手动引入,放在src/router/routes/modules 内的文件会自动被加载。

菜单排序

在菜单模块内,设置 orderNo 变量,数值越大,排序越靠后

+ + + + \ No newline at end of file diff --git a/guide/mock.html b/guide/mock.html new file mode 100644 index 00000000..cfffed66 --- /dev/null +++ b/guide/mock.html @@ -0,0 +1,443 @@ + + + + + + 数据 mock&联调 | Vben Admin + + + + + + + + + + + + + + + + +

数据 mock&联调

开发环境

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

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

配置

开发环境时候,接口地址在项目根目录下

.env.development 文件配置

# vite 本地跨域代理
+VITE_PROXY=[["/basic-api","http://localhost:3000"]]
+# 接口地址
+VITE_GLOB_API_URL=/api
+

TIP

  • .env 文件中的字段如果是字符串,则无需加引号,默认全部为字符串
  • VITE_PROXY 不能换行

TIP

v3.0.0开始,作者重构了vite.config.ts,新版本不再支持VITE_PROXY环境变量。

跨域处理

如果你在 src/api/ 下面的接口为下方代码,且 .env.development 文件配置如下注释,则在控制台看到的地址为 http://localhost:3100/basic-api/login

由于 /basic-api 匹配到了设置的 VITE_PROXY,所以上方实际是请求 http://localhost:3000/login,这样同时也解决了跨域问题。(3100为项目端口号,http://localhost:3000为PROXY代理的目标地址)

// .env.development
+// VITE_PROXY=[["/basic-api","http://localhost:3000"]]
+// VITE_GLOB_API_URL=/basic-api
+
+enum Api {
+  Login = '/login',
+}
+
+/**
+ * @description: 用户登陆
+ */
+export function loginApi(params: LoginParams) {
+  return http.request<LoginResultModel>({
+    url: Api.Login,
+    method: 'POST',
+    params,
+  });
+}
+

没有跨域时的配置

如果没有跨域问题,可以直接忽略 VITE_PROXY 配置,直接将接口地址设置在 VITE_GLOB_API_URL

# 例如接口地址为 http://localhost:3000 则
+VITE_GLOB_API_URL=http://localhost:3000
+

如果有跨域问题,将 VITE_GLOB_API_URL 设置为跟 VITE_PROXY 内其中一个数组的第一个项一致的值即可。

下方的接口地址设置为 /basic-api,当请求发出的时候会经过 Vite 的 proxy 代理,匹配到了我们设置的 VITE_PROXY 规则,将 /basic-api 转化为 http://localhost:3000 进行请求

# 例如接口地址为 http://localhost:3000 则
+VITE_PROXY=[["/basic-api","http://localhost:3000"]]
+# 接口地址
+VITE_GLOB_API_URL=/basic-api
+

跨域原理解析

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

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

注意

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

生产环境

生产环境接口地址在项目根目录下 .env.production 文件配置。

生产环境接口地址值需要修改 VITE_GLOB_API_URL,如果出现跨域问题,可以使用 nginx 或者后台开启 cors 进行处理

打包后如何进行地址修改?

VITE_GLOB_* 开头的变量会在打包的时候注入 _app.config.js 文件内。

dist/_app.config.js 修改相应的接口地址后刷新页面即可,不需要在根据不同环境打包多次,一次打包可以用于多个不同接口环境的部署。

接口请求

在 vue-vben-admin 中:

  1. 页面交互操作;
  2. 调用统一管理的 api 请求函数;
  3. 使用封装的 axios.ts 发送请求;
  4. 获取服务端返回数据
  5. 更新 data;

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

以登陆接口为例:

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

TIP

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

import { defHttp } from '/@/utils/http/axios';
+import { LoginParams, LoginResultModel } from './model/userModel';
+
+enum Api {
+  Login = '/login',
+}
+
+export function loginApi(params: LoginParams) {
+  return defHttp.request<LoginResultModel>({
+    url: Api.Login,
+    method: 'POST',
+    params,
+  });
+}
+

axios 配置

axios 请求封装存放于 src/utils/http/axios 文件夹内部

index.ts 文件内容需要根据项目自行修改外,其余文件无需修改


+├── Axios.ts // axios实例
+├── axiosCancel.ts // axiosCancel实例,取消重复请求
+├── axiosTransform.ts // 数据转换类
+├── checkStatus.ts // 返回状态值校验
+├── index.ts // 接口返回统一处理
+
+

index.ts 配置说明

const axios = new VAxios({
+  // 认证方案,例如: Bearer
+  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
+  authenticationScheme: '',
+  // 接口超时时间 单位毫秒
+  timeout: 10 * 1000,
+  // 接口可能会有通用的地址部分,可以统一抽取出来
+  prefixUrl: prefix,
+  headers: { 'Content-Type': ContentTypeEnum.JSON },
+  // 数据处理方式,见下方说明
+  transform,
+  // 配置项,下面的选项都可以在独立的接口请求中覆盖
+  requestOptions: {
+    // 默认将prefix 添加到url
+    joinPrefix: true,
+    // 是否返回原生响应头 比如:需要获取响应头时使用该属性
+    isReturnNativeResponse: false,
+    // 需要对返回数据进行处理
+    isTransformRequestResult: true,
+    // post请求的时候添加参数到url
+    joinParamsToUrl: false,
+    // 格式化提交参数时间
+    formatDate: true,
+    // 消息提示类型
+    errorMessageMode: 'message',
+    // 接口地址
+    apiUrl: globSetting.apiUrl,
+    //  是否加入时间戳
+    joinTime: true,
+    // 忽略重复请求
+    ignoreCancelToken: true,
+  },
+});
+

transform 数据处理说明

类型定义,见 axiosTransform.ts 文件

export abstract class AxiosTransform {
+  /**
+   * @description: 请求之前处理配置
+   */
+  beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig;
+
+  /**
+   * @description: 请求成功处理
+   */
+  transformRequestData?: (res: AxiosResponse<Result>, options: RequestOptions) => any;
+
+  /**
+   * @description: 请求失败处理
+   */
+  requestCatch?: (e: Error) => Promise<any>;
+
+  /**
+   * @description: 请求之前的拦截器
+   */
+  requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig;
+
+  /**
+   * @description: 请求之后的拦截器
+   */
+  responseInterceptors?: (res: AxiosResponse<any>) => AxiosResponse<any>;
+
+  /**
+   * @description: 请求之前的拦截器错误处理
+   */
+  requestInterceptorsCatch?: (error: Error) => void;
+
+  /**
+   * @description: 请求之后的拦截器错误处理
+   */
+  responseInterceptorsCatch?: (error: Error) => void;
+}
+
+
+

项目默认 transform 处理逻辑,可以根据各自项目进行处理。一般需要更改的部分为下方代码,见代码注释说明

/**
+ * @description: 数据处理,方便区分多种处理方式
+ */
+const transform: AxiosTransform = {
+  /**
+   * @description: 处理请求数据。如果数据不是预期格式,可直接抛出错误
+   */
+  transformRequestHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
+    const { t } = useI18n();
+    const { isTransformResponse, isReturnNativeResponse } = options;
+    // 是否返回原生响应头 比如:需要获取响应头时使用该属性
+    if (isReturnNativeResponse) {
+      return res;
+    }
+    // 不进行任何处理,直接返回
+    // 用于页面代码可能需要直接获取code,data,message这些信息时开启
+    if (!isTransformResponse) {
+      return res.data;
+    }
+    // 错误的时候返回
+
+    const { data } = res;
+    if (!data) {
+      // return '[HTTP] Request has no return value';
+      throw new Error(t('sys.api.apiRequestFailed'));
+    }
+    //  这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
+    const { code, result, message } = data;
+
+    // 这里逻辑可以根据项目进行修改
+    const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS;
+    if (hasSuccess) {
+      return result;
+    }
+
+    // 在此处根据自己项目的实际情况对不同的code执行不同的操作
+    // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
+    let timeoutMsg = '';
+    switch (code) {
+      case ResultEnum.TIMEOUT:
+        timeoutMsg = t('sys.api.timeoutMessage');
+      default:
+        if (message) {
+          timeoutMsg = message;
+        }
+    }
+
+    // errorMessageMode=‘modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
+    // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
+    if (options.errorMessageMode === 'modal') {
+      createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg });
+    } else if (options.errorMessageMode === 'message') {
+      createMessage.error(timeoutMsg);
+    }
+
+    throw new Error(timeoutMsg || t('sys.api.apiRequestFailed'));
+  },
+
+  // 请求之前处理config
+  beforeRequestHook: (config, options) => {
+    const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true } = options;
+
+    if (joinPrefix) {
+      config.url = `${urlPrefix}${config.url}`;
+    }
+
+    if (apiUrl && isString(apiUrl)) {
+      config.url = `${apiUrl}${config.url}`;
+    }
+    const params = config.params || {};
+    if (config.method?.toUpperCase() === RequestEnum.GET) {
+      if (!isString(params)) {
+        // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
+        config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
+      } else {
+        // 兼容restful风格
+        config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
+        config.params = undefined;
+      }
+    } else {
+      if (!isString(params)) {
+        formatDate && formatRequestDate(params);
+        config.data = params;
+        config.params = undefined;
+        if (joinParamsToUrl) {
+          config.url = setObjToUrlParams(config.url as string, config.data);
+        }
+      } else {
+        // 兼容restful风格
+        config.url = config.url + params;
+        config.params = undefined;
+      }
+    }
+    return config;
+  },
+
+  /**
+   * @description: 请求拦截器处理
+   */
+  requestInterceptors: (config, options) => {
+    // 请求之前处理config
+    const token = getToken();
+    if (token) {
+      // jwt token
+      config.headers.Authorization = options.authenticationScheme
+        ? `${options.authenticationScheme} ${token}`
+        : token;
+    }
+    return config;
+  },
+
+  /**
+   * @description: 响应拦截器处理
+   */
+  responseInterceptors: (res: AxiosResponse<any>) => {
+    return res;
+  },
+
+  /**
+   * @description: 响应错误处理
+   */
+  responseInterceptorsCatch: (error: any) => {
+    const { t } = useI18n();
+    const errorLogStore = useErrorLogStoreWithOut();
+    errorLogStore.addAjaxErrorInfo(error);
+    const { response, code, message, config } = error || {};
+    const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none';
+    const msg: string = response?.data?.error?.message ?? '';
+    const err: string = error?.toString?.() ?? '';
+    let errMessage = '';
+
+    try {
+      if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
+        errMessage = t('sys.api.apiTimeoutMessage');
+      }
+      if (err?.includes('Network Error')) {
+        errMessage = t('sys.api.networkExceptionMsg');
+      }
+
+      if (errMessage) {
+        if (errorMessageMode === 'modal') {
+          createErrorModal({ title: t('sys.api.errorTip'), content: errMessage });
+        } else if (errorMessageMode === 'message') {
+          createMessage.error(errMessage);
+        }
+        return Promise.reject(error);
+      }
+    } catch (error) {
+      throw new Error(error);
+    }
+
+    checkStatus(error?.response?.status, msg, errorMessageMode);
+    return Promise.reject(error);
+  },
+};
+

更改参数格式

项目接口默认为 Json 参数格式,即 headers: { 'Content-Type': ContentTypeEnum.JSON },

如果需要更改为 form-data 格式,更改 headers 的 'Content-TypeContentTypeEnum.FORM_URLENCODED 即可

多个接口地址

当项目中需要用到多个接口地址时, 可以在 src/utils/http/axios/index.ts 导出多个 axios 实例

// 目前只导出一个默认实例,接口地址对应的是环境变量中的 VITE_GLOB_API_URL 接口地址
+export const defHttp = createAxios();
+
+// 需要有其他接口地址的可以在后面添加
+
+// other api url
+export const otherHttp = createAxios({
+  requestOptions: {
+    apiUrl: 'xxx',
+  },
+});
+

删除请求 URL 携带的时间戳参数

如果不需要 url 上面默认携带的时间戳参数 ?_t=xxx

const axios = new VAxios({
+  requestOptions: {
+    // 是否加入时间戳
+    joinTime: false,
+  },
+});
+

Mock 服务

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

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

本地 Mock

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

如何新增 mock 接口

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

在 mock 文件夹内新建文件

TIP

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

例:

import { MockMethod } from 'vite-plugin-mock';
+import { resultPageSuccess } from '../_util';
+
+const demoList = (() => {
+  const result: any[] = [];
+  for (let index = 0; index < 60; index++) {
+    result.push({
+      id: `${index}`,
+      beginTime: '@datetime',
+      endTime: '@datetime',
+      address: '@city()',
+      name: '@cname()',
+      'no|100000-10000000': 100000,
+      'status|1': ['正常', '启用', '停用'],
+    });
+  }
+  return result;
+})();
+
+export default [
+  {
+    url: '/api/table/getDemoList',
+    timeout: 1000,
+    method: 'get',
+    response: ({ query }) => {
+      const { page = 1, pageSize = 20 } = query;
+      return resultPageSuccess(page, pageSize, demoList);
+    },
+  },
+] as MockMethod[];
+

TIP

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

接口格式

{
+  url: string; // mock 接口地址
+  method?: MethodType; // 请求方式
+  timeout?: number; // 延时时间
+  statusCode: number; // 响应状态码
+  response: ((opt: { // 响应结果
+      body: any;
+      query: any;
+  }) => any) | object;
+}
+

参数获取

GET 接口: ({ query }) => { }

POST 接口: ({ body }) => { }

util 说明

可在 代码 中查看

TIP

util 只作为服务处理结果数据使用。可以不用,如需使用可自行封装,需要将对应的字段改为接口的返回结构

匹配

src/api 下面,如果接口匹配到 mock,则会优先使用 mock 进行响应

import { defHttp } from '/@/utils/http/axios';
+import { LoginParams, LoginResultModel } from './model/userModel';
+
+enum Api {
+  Login = '/login',
+}
+
+/**
+ * @description: user login api
+ */
+export function loginApi(params: LoginParams) {
+  return defHttp.request<LoginResultModel>(
+    {
+      url: Api.Login,
+      method: 'POST',
+      params,
+    },
+    {
+      errorMessageMode: 'modal',
+    }
+  );
+}
+// 会匹配到上方的
+export default [
+  {
+    url: '/api/login',
+    timeout: 1000,
+    method: 'POST',
+    response: ({ body }) => {
+      return resultPageSuccess({});
+    },
+  },
+] as MockMethod[];
+

接口有了,如何去掉 mock

当后台接口已经开发完成,只需要将相应的 mock 函数去掉即可。

以上方接口为例,假如后台接口 login 已经开发完成,则只需要删除/注释掉下方代码即可

export default [
+  {
+    url: '/api/login',
+    timeout: 1000,
+    method: 'POST',
+    response: ({ body }) => {
+      return resultPageSuccess({});
+    },
+  },
+] as MockMethod[];
+

线上 mock

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

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

线上如何开启 mock

注意

线上开启 mock 只适用于一些简单的示例网站及预览网站。一定不要在正式的生产环境开启!!!

  1. 修改 .env.production 文件内的 VITE_USE_MOCK 的值为 true
VITE_USE_MOCK = true;
+
  1. mock/_createProductionServer.ts 文件中引入需要的 mock 文件
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
+
+const modules = import.meta.globEager('./**/*.ts');
+
+const mockModules: any[] = [];
+Object.keys(modules).forEach((key) => {
+  if (key.includes('/_')) {
+    return;
+  }
+  mockModules.push(...modules[key].default);
+});
+
+export function setupProdMockServer() {
+  createProdMockServer(mockModules);
+}
+
  1. build/vite/plugin/mock.ts 里面引入
import { viteMockServe } from 'vite-plugin-mock';
+
+export function configMockPlugin(isBuild: boolean) {
+  return viteMockServe({
+    injectCode: `
+      import { setupProdMockServer } from '../mock/_createProductionServer';
+
+      setupProdMockServer();
+      `,
+  });
+}
+

为什么通过插件注入代码而不是直接在 main.ts 内插入

在插件内通过 injectCode 插入代码,方便控制 mockjs 是否被打包到最终代码内。如果在 main.ts 内判断,如果关闭了 mock 功能,mockjs 也会打包到构建文件内,这样会增加打包体积。

到这里线上 mock 就配置完成了。线上与本地差异不大,比较大的区别是线上在控制台内看不到接口请求日志。

+ + + + \ No newline at end of file diff --git a/guide/router.html b/guide/router.html new file mode 100644 index 00000000..9bbf0ad1 --- /dev/null +++ b/guide/router.html @@ -0,0 +1,291 @@ + + + + + + 路由 | Vben Admin + + + + + + + + + + + + + + + + +

路由

项目路由配置存放于 src/router/routes 下面。 src/router/routes/modules用于存放路由模块,在该目录下的文件会自动注册。

配置

模块说明

src/router/routes/modules 内的 .ts 文件会被视为一个路由模块。

一个路由模块包含以下结构

import type { AppRouteModule } from '/@/router/types';
+
+import { LAYOUT } from '/@/router/constant';
+import { t } from '/@/hooks/web/useI18n';
+
+const dashboard: AppRouteModule = {
+  path: '/dashboard',
+  name: 'Dashboard',
+  component: LAYOUT,
+  redirect: '/dashboard/analysis',
+  meta: {
+    icon: 'ion:grid-outline',
+    title: t('routes.dashboard.dashboard'),
+  },
+  children: [
+    {
+      path: 'analysis',
+      name: 'Analysis',
+      component: () => import('/@/views/dashboard/analysis/index.vue'),
+      meta: {
+        affix: true,
+        title: t('routes.dashboard.analysis'),
+      },
+    },
+    {
+      path: 'workbench',
+      name: 'Workbench',
+      component: () => import('/@/views/dashboard/workbench/index.vue'),
+      meta: {
+        title: t('routes.dashboard.workbench'),
+      },
+    },
+  ],
+};
+export default dashboard;
+

多级路由

注意事项

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

示例

import type { AppRouteModule } from '/@/router/types';
+import { getParentLayout, LAYOUT } from '/@/router/constant';
+import { t } from '/@/hooks/web/useI18n';
+const permission: AppRouteModule = {
+  path: '/level',
+  name: 'Level',
+  component: LAYOUT,
+  redirect: '/level/menu1/menu1-1/menu1-1-1',
+  meta: {
+    icon: 'ion:menu-outline',
+    title: t('routes.demo.level.level'),
+  },
+
+  children: [
+    {
+      path: 'tabs/:id', 
+      name: 'TabsParams',
+      component: getParentLayout('TabsParams'),
+      meta: {
+        carryParam: true,
+        hidePathForChildren: true, // 本级path将会在子级菜单中合成完整path时会忽略这一层级
+      },
+      children: [
+        path: 'tabs/id1', // 其上级有标记hidePathForChildren,所以本级在生成菜单时最终的path为  /level/tabs/id1
+        name: 'TabsParams',
+        component: getParentLayout('TabsParams'),
+        meta: {
+          carryParam: true,
+          ignoreRoute: true,  // 本路由仅用于菜单生成,不会在实际的路由表中出现
+        },
+      ]
+    },
+    {
+      path: 'menu1',
+      name: 'Menu1Demo',
+      component: getParentLayout('Menu1Demo'),
+      meta: {
+        title: 'Menu1',
+      },
+      redirect: '/level/menu1/menu1-1/menu1-1-1',
+      children: [
+        {
+          path: 'menu1-1',
+          name: 'Menu11Demo',
+          component: getParentLayout('Menu11Demo'),
+          meta: {
+            title: 'Menu1-1',
+          },
+          redirect: '/level/menu1/menu1-1/menu1-1-1',
+          children: [
+            {
+              path: 'menu1-1-1',
+              name: 'Menu111Demo',
+              component: () => import('/@/views/demo/level/Menu111.vue'),
+              meta: {
+                title: 'Menu111',
+              },
+            },
+          ],
+        },
+      ],
+    },
+  ],
+};
+
+export default permission;
+

Meta 配置说明

export interface RouteMeta {
+  // 路由title  一般必填
+  title: string;
+  // 动态路由可打开Tab页数
+  dynamicLevel?: number;
+  // 动态路由的实际Path, 即去除路由的动态部分;
+  realPath?: string;
+  // 是否忽略权限,只在权限模式为Role的时候有效
+  ignoreAuth?: boolean;
+  // 可以访问的角色,只在权限模式为Role的时候有效
+  roles?: RoleEnum[];
+  // 是否忽略KeepAlive缓存
+  ignoreKeepAlive?: boolean;
+  // 是否固定标签
+  affix?: boolean;
+  // 图标,也是菜单图标
+  icon?: string;
+  // 内嵌iframe的地址
+  frameSrc?: string;
+  // 指定该路由切换的动画名
+  transitionName?: string;
+  // 隐藏该路由在面包屑上面的显示
+  hideBreadcrumb?: boolean;
+  // 如果该路由会携带参数,且需要在tab页上面显示。则需要设置为true
+  carryParam?: boolean;
+  // 隐藏所有子菜单
+  hideChildrenInMenu?: boolean;
+  // 当前激活的菜单。用于配置详情页时左侧激活的菜单路径
+  currentActiveMenu?: string;
+  // 当前路由不再标签页显示
+  hideTab?: boolean;
+  // 当前路由不再菜单显示
+  hideMenu?: boolean;
+  // 菜单排序,只对第一级有效
+  orderNo?: number;
+  // 忽略路由。用于在ROUTE_MAPPING以及BACK权限模式下,生成对应的菜单而忽略路由。2.5.3以上版本有效
+  ignoreRoute?: boolean;
+  // 是否在子级菜单的完整path中忽略本级path。2.5.3以上版本有效
+  hidePathForChildren?: boolean;
+}
+

外部页面嵌套

只需要将 frameSrc 设置为需要跳转的地址即可

const IFrame = () => import('/@/views/sys/iframe/FrameBlank.vue');
+{
+  path: 'doc',
+  name: 'Doc',
+  component: IFrame,
+  meta: {
+    frameSrc: 'https://vvbin.cn/doc-next/',
+    title: t('routes.demo.iframe.doc'),
+  },
+},
+

外链

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

{
+  path: 'https://vvbin.cn/doc-next/',
+  name: 'DocExternal',
+  component: IFrame,
+  meta: {
+    title: t('routes.demo.iframe.docExternal'),
+  },
+}
+

动态路由Tab自动关闭功能

若需要开启该功能,需要在动态路由的meta中设置如下两个参数:

  • dynamicLevel 最大能打开的Tab标签页数
  • realPath 动态路由实际路径(考虑到动态路由有时候可能存在N层的情况, 例:/:id/:subId/:...), 为了减少计算开销, 使用配置方式事先规定好路由的实际路径(注意: 该参数若不设置,将无法使用该功能)
{
+  path: 'detail/:id',
+  name: 'TabDetail',
+  component: () => import('/@/views/demo/feat/tabs/TabDetail.vue'),
+  meta: {
+    currentActiveMenu: '/feat/tabs',
+    title: t('routes.demo.feat.tabDetail'),
+    hideMenu: true,
+    dynamicLevel: 3,
+    realPath: '/feat/tabs/detail',
+  },
+}
+

图标

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

新增路由

如何新增一个路由模块

  1. src/router/routes/modules 内新增一个模块文件。

示例,新增 test.ts 文件

import type { AppRouteModule } from '/@/router/types';
+import { LAYOUT } from '/@/router/constant';
+import { t } from '/@/hooks/web/useI18n';
+
+const dashboard: AppRouteModule = {
+  path: '/about',
+  name: 'About',
+  component: LAYOUT,
+  redirect: '/about/index',
+  meta: {
+    icon: 'simple-icons:about-dot-me',
+    title: t('routes.dashboard.about'),
+  },
+  children: [
+    {
+      path: 'index',
+      name: 'AboutPage',
+      component: () => import('/@/views/sys/about/index.vue'),
+      meta: {
+        title: t('routes.dashboard.about'),
+        icon: 'simple-icons:about-dot-me',
+      },
+    },
+  ],
+};
+
+export default dashboard;
+

此时路由已添加完成,不需要手动引入,放在src/router/routes/modules 内的文件会自动被加载。

验证

访问 ip:端口/about/index 出现对应组件内容即代表成功

路由刷新

项目中采用的是重定向方式

实现

import { useRedo } from '/@/hooks/web/usePage';
+import { defineComponent } from 'vue';
+export default defineComponent({
+  setup() {
+    const redo = useRedo();
+    // 执行刷新
+    redo();
+    return {};
+  },
+});
+

Redirect

src/views/sys/redirect/index.vue

import { defineComponent, unref } from 'vue';
+import { useRouter } from 'vue-router';
+export default defineComponent({
+  name: 'Redirect',
+  setup() {
+    const { currentRoute, replace } = useRouter();
+    const { params, query } = unref(currentRoute);
+    const { path } = params;
+    const _path = Array.isArray(path) ? path.join('/') : path;
+    replace({
+      path: '/' + _path,
+      query,
+    });
+    return {};
+  },
+});
+

页面跳转

页面跳转建议采用项目提供的 useGo

方式

import { useGo } from '/@/hooks/web/usePage';
+import { defineComponent } from 'vue';
+export default defineComponent({
+  setup() {
+    const go = useGo();
+
+    // 执行刷新
+    go();
+    go(PageEnum.Home);
+    return {};
+  },
+});
+

多标签页

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

如何开启页面缓存

开启缓存有 3 个条件

  1. src/settings/projectSetting.ts 内将openKeepAlive 设置为 true
  2. 路由设置 name,且不能重复
  3. 路由对应的组件加上 name,与路由设置的 name 保持一致
 {
+   ...,
+    // name
+    name: 'Login',
+    // 对应组件组件的name
+    component: () => import('/@/views/sys/login/index.vue'),
+    ...
+  },
+
+  // /@/views/sys/login/index.vue
+  export default defineComponent({
+    // 需要和路由的name一致
+    name:"Login"
+  });
+

注意

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

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

如何让某个页面不缓存

可在 router.meta 下配置

可以将 ignoreKeepAlive 配置成 true 即可关闭缓存。

export interface RouteMeta {
+  // 是否忽略KeepAlive缓存
+  ignoreKeepAlive?: boolean;
+}
+

如何更改首页路由

首页路由指的是应用程序中的默认路由,当不输入其他任何路由时,会自动重定向到该路由下,并且该路由在Tab上是固定的,即使设置affix: false也不允许关闭

例:首页路由配置的是/dashboard/analysis,那么当直接访问 http://localhost:3100/ 会自动跳转到http://localhost:3100/#/dashboard/analysis 上(用户已登录的情况下)

可以将pageEnum.ts中的BASE_HOME更改为需要你想设置的首页即可

export enum PageEnum {
+    // basic home path
+    // 更改此处即可
+    BASE_HOME = '/dashboard',
+}
+
+
+ + + + \ No newline at end of file diff --git a/guide/settings.html b/guide/settings.html new file mode 100644 index 00000000..ddf6aab9 --- /dev/null +++ b/guide/settings.html @@ -0,0 +1,405 @@ + + + + + + 项目配置项 | Vben Admin + + + + + + + + + + + + + + + + +

项目配置项

用于修改项目的配色、布局、缓存、多语言、组件默认配置

环境变量配置

项目的环境变量配置位于项目根目录下的 .env.env.development.env.production

具体可以参考 Vite 文档

.env                # 在所有的环境中被载入
+.env.local          # 在所有的环境中被载入,但会被 git 忽略
+.env.[mode]         # 只在指定的模式中被载入
+.env.[mode].local   # 只在指定的模式中被载入,但会被 git 忽略
+
+

温馨提醒

  • 只有以 VITE_ 开头的变量会被嵌入到客户端侧的包中,你可以在项目代码中这样访问它们:
console.log(import.meta.env.VITE_PROT);
+
  • VITE_GLOB_* 开头的的变量,在打包的时候,会被加入_app.config.js配置文件当中.

配置项说明

.env

所有环境适用

# 端口号
+VITE_PORT=3100
+# 网站标题
+VITE_GLOB_APP_TITLE=vben admin
+# 简称,用于配置文件名字 不要出现空格、数字开头等特殊字符
+VITE_GLOB_APP_SHORT_NAME=vben_admin
+

.env.development

开发环境适用

# 是否开启mock数据,关闭时需要自行对接后台接口
+VITE_USE_MOCK=true
+# 资源公共路径,需要以 /开头和结尾
+VITE_PUBLIC_PATH=/
+# 是否删除Console.log
+VITE_DROP_CONSOLE=false
+# 本地开发代理,可以解决跨域及多地址代理
+# 如果接口地址匹配到,则会转发到http://localhost:3000,防止本地出现跨域问题
+# 可以有多个,注意多个不能换行,否则代理将会失效
+VITE_PROXY=[["/api","http://localhost:3000"],["api1","http://localhost:3001"],["/upload","http://localhost:3001/upload"]]
+
+::: tip
+v3.0.0开始,作者重构了vite.config.ts,新版本不再支持VITE_PROXY环境变量。
+:::
+
+# 接口地址
+# 如果没有跨域问题,直接在这里配置即可
+VITE_GLOB_API_URL=/api
+# 文件上传接口  可选
+VITE_GLOB_UPLOAD_URL=/upload
+# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换
+VITE_GLOB_API_URL_PREFIX=
+

注意

这里配置的 VITE_PROXY 以及 VITE_GLOB_API_URL, /api 需要是唯一的,不要和接口有的名字冲突

如果你的接口是 http://localhost:3000/api 之类的,请考虑将 VITE_GLOB_API_URL=/xxxx 换成别的名字

.env.production

生产环境适用

# 是否开启mock
+VITE_USE_MOCK=true
+# 接口地址 可以由nginx做转发或者直接写实际地址
+VITE_GLOB_API_URL=/api
+# 文件上传地址 可以由nginx做转发或者直接写实际地址
+VITE_GLOB_UPLOAD_URL=/upload
+# 接口地址前缀,有些系统所有接口地址都有前缀,可以在这里统一加,方便切换
+VITE_GLOB_API_URL_PREFIX=
+# 是否删除Console.log
+VITE_DROP_CONSOLE=true
+# 资源公共路径,需要以 / 开头和结尾
+VITE_PUBLIC_PATH=/
+# 打包是否输出gz|br文件
+# 可选: gzip | brotli | none
+# 也可以有多个, 例如 ‘gzip’|'brotli',这样会同时生成 .gz和.br文件
+VITE_BUILD_COMPRESS = 'gzip'
+# 打包是否压缩图片
+VITE_USE_IMAGEMIN = false
+# 打包是否开启pwa功能
+VITE_USE_PWA = false
+# 是否兼容旧版浏览器。开启后打包时间会慢一倍左右。会多打出旧浏览器兼容包,且会根据浏览器兼容性自动使用相应的版本
+VITE_LEGACY = false
+

生产环境动态配置

说明

当执行yarn build构建项目之后,会自动生成 _app.config.js 文件并插入 index.html

注意: 开发环境不会生成

// _app.config.js
+// 变量名命名规则  __PRODUCTION__xxx_CONF__   xxx:为.env配置的VITE_GLOB_APP_SHORT_NAME
+window.__PRODUCTION__VUE_VBEN_ADMIN__CONF__ = {
+  VITE_GLOB_APP_TITLE: 'vben admin',
+  VITE_GLOB_APP_SHORT_NAME: 'vue_vben_admin',
+  VITE_GLOB_API_URL: '/app',
+  VITE_GLOB_API_URL_PREFIX: '/',
+  VITE_GLOB_UPLOAD_URL: '/upload',
+};
+

作用

_app.config.js 用于项目在打包后,需要动态修改配置的需求,如接口地址。不用重新进行打包,可在打包后修改 /dist/_app.config.js 内的变量,刷新即可更新代码内的局部变量。

如何获取全局变量

想要获取 _app.config.js 内的变量,可以使用 src/hooks/setting/index.ts 提供的函数来进行获取

如何新增(新增一个可动态修改的配置项)

  1. 首先在 .env 或者对应的开发环境配置文件内,新增需要可动态配置的变量,需要以 VITE_GLOB_开头

  2. VITE_GLOB_ 开头的变量会自动加入环境变量,通过在 types/config.d.ts 内修改 GlobEnvConfigGlobConfig 两个环境变量的值来定义新添加的类型

  3. useGlobSetting 函数中添加刚新增的返回值即可

const {
+  VITE_GLOB_APP_TITLE,
+  VITE_GLOB_API_URL,
+  VITE_GLOB_APP_SHORT_NAME,
+  VITE_GLOB_API_URL_PREFIX,
+  VITE_GLOB_UPLOAD_URL,
+} = ENV;
+
+export const useGlobSetting = (): SettingWrap => {
+  // Take global configuration
+  const glob: Readonly<GlobConfig> = {
+    title: VITE_GLOB_APP_TITLE,
+    apiUrl: VITE_GLOB_API_URL,
+    shortName: VITE_GLOB_APP_SHORT_NAME,
+    urlPrefix: VITE_GLOB_API_URL_PREFIX,
+    uploadUrl: VITE_GLOB_UPLOAD_URL
+  };
+  return glob as Readonly<GlobConfig>;
+};
+
+

项目配置

WARNING

项目配置文件用于配置项目内展示的内容、布局、文本等效果,存于localStorage中。如果更改了项目配置,需要手动清空 localStorage 缓存,刷新重新登录后方可生效。

配置文件路径

src/settings/projectSetting.ts

说明

// ! 改动后需要清空浏览器缓存
+const setting: ProjectConfig = {
+  // 是否显示SettingButton
+  showSettingButton: true,
+
+  // 是否显示主题切换按钮
+  showDarkModeToggle: true,
+
+  // 设置按钮位置 可选项
+  // SettingButtonPositionEnum.AUTO: 自动选择
+  // SettingButtonPositionEnum.HEADER: 位于头部
+  // SettingButtonPositionEnum.FIXED: 固定在右侧
+  settingButtonPosition: SettingButtonPositionEnum.AUTO,
+
+  // 权限模式,默认前端角色权限模式
+  // ROUTE_MAPPING: 前端模式(菜单由路由生成,默认)
+  // ROLE:前端模式(菜单路由分开)
+  permissionMode: PermissionModeEnum.ROUTE_MAPPING,
+  // 权限缓存存放位置。默认存放于localStorage
+  permissionCacheType: CacheTypeEnum.LOCAL,
+  // 会话超时处理方案
+  // SessionTimeoutProcessingEnum.ROUTE_JUMP: 路由跳转到登录页
+  // SessionTimeoutProcessingEnum.PAGE_COVERAGE: 生成登录弹窗,覆盖当前页面
+  sessionTimeoutProcessing: SessionTimeoutProcessingEnum.ROUTE_JUMP,
+  // 项目主题色
+  themeColor: primaryColor,
+  // 网站灰色模式,用于可能悼念的日期开启
+  grayMode: false,
+  // 色弱模式
+  colorWeak: false,
+  // 是否取消菜单,顶部,多标签页显示, 用于可能内嵌在别的系统内
+  fullContent: false,
+  // 主题内容宽度
+  contentMode: ContentEnum.FULL,
+  // 是否显示logo
+  showLogo: true,
+  // 是否显示底部信息 copyright
+  showFooter: true,
+  // 头部配置
+  headerSetting: {
+    // 背景色
+    bgColor: '#ffffff',
+    // 固定头部
+    fixed: true,
+    // 是否显示顶部
+    show: true,
+    // 主题
+    theme: MenuThemeEnum.LIGHT,
+    // 开启锁屏功能
+    useLockPage: true,
+    // 显示全屏按钮
+    showFullScreen: true,
+    // 显示文档按钮
+    showDoc: true,
+    // 显示消息中心按钮
+    showNotice: true,
+    // 显示菜单搜索按钮
+    showSearch: true,
+  },
+  // 菜单配置
+  menuSetting: {
+    // 背景色
+    bgColor: '#273352',
+    // 是否固定住菜单
+    fixed: true,
+    // 菜单折叠
+    collapsed: false,
+    // 折叠菜单时候是否显示菜单名
+    collapsedShowTitle: false,
+    // 是否可拖拽
+    canDrag: true,
+    // 是否显示
+    show: true,
+    // 菜单宽度
+    menuWidth: 180,
+    // 菜单模式
+    mode: MenuModeEnum.INLINE,
+    // 菜单类型
+    type: MenuTypeEnum.SIDEBAR,
+    // 菜单主题
+    theme: MenuThemeEnum.DARK,
+    // 分割菜单
+    split: false,
+    // 顶部菜单布局
+    topMenuAlign: 'start',
+    // 折叠触发器的位置
+    trigger: TriggerEnum.HEADER,
+    // 手风琴模式,只展示一个菜单
+    accordion: true,
+    // 在路由切换的时候关闭左侧混合菜单展开菜单
+    closeMixSidebarOnChange: false,
+    // 左侧混合菜单模块切换触发方式
+    mixSideTrigger: MixSidebarTriggerEnum.CLICK,
+    // 是否固定左侧混合菜单
+    mixSideFixed: false,
+  },
+  // 多标签
+  multiTabsSetting: {
+    // 刷新后是否保留已经打开的标签页
+    cache: false,
+    // 开启
+    show: true,
+    // 开启快速操作
+    showQuick: true,
+    // 是否可以拖拽
+    canDrag: true,
+    // 是否显示刷新按钮
+    showRedo: true,
+    // 是否显示折叠按钮
+    showFold: true,
+  },
+
+  // 动画配置
+  transitionSetting: {
+    //  是否开启切换动画
+    enable: true,
+    // 动画名
+    basicTransition: RouterTransitionEnum.FADE_SIDE,
+    // 是否打开页面切换loading
+    openPageLoading: true,
+    // 是否打开页面切换顶部进度条
+    openNProgress: false,
+  },
+
+  // 是否开启KeepAlive缓存  开发时候最好关闭,不然每次都需要清除缓存
+  openKeepAlive: true,
+  // 自动锁屏时间,为0不锁屏。 单位分钟 默认1个小时
+  lockTime: 0,
+  // 显示面包屑
+  showBreadCrumb: true,
+  // 显示面包屑图标
+  showBreadCrumbIcon: false,
+  // 是否使用全局错误捕获
+  useErrorHandle: false,
+  // 是否开启回到顶部
+  useOpenBackTop: true,
+  //  是否可以嵌入iframe页面
+  canEmbedIFramePage: true,
+  // 切换界面的时候是否删除未关闭的message及notify
+  closeMessageOnSwitch: true,
+  // 切换界面的时候是否取消已经发送但是未响应的http请求。
+  // 如果开启,想对单独接口覆盖。可以在单独接口设置
+  removeAllHttpPending: true,
+};
+

缓存配置

用于配置缓存内容加密信息,对缓存到浏览器的信息进行 AES 加密

/@/settings/encryptionSetting.ts 内可以配置 localStoragesessionStorage 缓存信息

前提: 使用项目自带的缓存工具类 /@/utils/cache 来进行缓存操作

import { isDevMode } from '/@/utils/env';
+
+// 缓存默认过期时间
+export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
+
+// 开启缓存加密后,加密密钥。采用aes加密
+export const cacheCipher = {
+  key: '_11111000001111@',
+  iv: '@11111000001111_',
+};
+
+// 是否加密缓存,默认生产环境加密
+export const enableStorageEncryption = !isDevMode();
+

多语言配置

用于配置多语言信息

src/settings/localeSetting.ts 内配置

export const LOCALE: { [key: string]: LocaleType } = {
+  ZH_CN: 'zh_CN',
+  EN_US: 'en',
+};
+
+export const localeSetting: LocaleSetting = {
+  // 是否显示语言选择器
+  showPicker: true,
+  // 当前语言
+  locale: LOCALE.ZH_CN,
+  // 默认语言
+  fallback: LOCALE.ZH_CN,
+  // 允许的语言
+  availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US],
+};
+
+// 语言列表
+export const localeList: DropMenu[] = [
+  {
+    text: '简体中文',
+    event: LOCALE.ZH_CN,
+  },
+  {
+    text: 'English',
+    event: LOCALE.EN_US,
+  },
+];
+

主题色配置

默认全局主题色配置位于 build/config/glob/themeConfig.ts

只需要修改 primaryColor 为您需要的配色,然后重新执行 yarn serve 即可

/**
+ * less global variable
+ */
+export const primaryColor = '#0960bd';
+

样式配置

css 前缀设置

用于修改项目内组件 class 的统一前缀

export const prefixCls = 'vben';
+
@namespace: vben;
+

前缀使用

在 css 内

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

在 vue/ts 内

import { useDesign } from '/@/hooks/web/useDesign';
+
+const { prefixCls } = useDesign('app-logo');
+
+// prefixCls => vben-app-logo
+

颜色配置

用于预设一些颜色数组

src/settings/designSetting.ts 内配置

//  app主题色预设
+export const APP_PRESET_COLOR_LIST: string[] = [
+  '#0960bd',
+  '#0084f4',
+  '#009688',
+  '#536dfe',
+  '#ff5c93',
+  '#ee4f12',
+  '#0096c7',
+  '#9c27b0',
+  '#ff9800',
+];
+
+// 顶部背景色预设
+export const HEADER_PRESET_BG_COLOR_LIST: string[] = [
+  '#ffffff',
+  '#009688',
+  '#5172DC',
+  '#1E9FFF',
+  '#018ffb',
+  '#409eff',
+  '#4e73df',
+  '#e74c3c',
+  '#24292e',
+  '#394664',
+  '#001529',
+  '#383f45',
+];
+
+// 左侧菜单背景色预设
+export const SIDE_BAR_BG_COLOR_LIST: string[] = [
+  '#001529',
+  '#273352',
+  '#ffffff',
+  '#191b24',
+  '#191a23',
+  '#304156',
+  '#001628',
+  '#28333E',
+  '#344058',
+  '#383f45',
+];
+

组件默认参数配置

src/settings/componentSetting.ts 内配置

// 用于配置某些组件的常规配置,而无需修改组件
+import type { SorterResult } from '../components/Table';
+
+export default {
+  // 表格配置
+  table: {
+    // 表格接口请求通用配置,可在组件prop覆盖
+    // 支持 xxx.xxx.xxx格式
+    fetchSetting: {
+      // 传给后台的当前页字段
+      pageField: 'page',
+      // 传给后台的每页显示多少条的字段
+      sizeField: 'pageSize',
+      // 接口返回表格数据的字段
+      listField: 'items',
+      // 接口返回表格总数的字段
+      totalField: 'total',
+    },
+    // 可选的分页选项
+    pageSizeOptions: ['10', '50', '80', '100'],
+    //默认每页显示多少条
+    defaultPageSize: 10,
+    // 默认排序方法
+    defaultSortFn: (sortInfo: SorterResult) => {
+      const { field, order } = sortInfo;
+      return {
+        // 排序字段
+        field,
+        // 排序方式 asc/desc
+        order,
+      };
+    },
+    // 自定义过滤方法
+    defaultFilterFn: (data: Partial<Recordable<string[]>>) => {
+      return data;
+    },
+  },
+  // 滚动组件配置
+  scrollbar: {
+    // 是否使用原生滚动样式
+    // 开启后,菜单,弹窗,抽屉会使用原生滚动条组件
+    native: false,
+  },
+};
+
+ + + + \ No newline at end of file diff --git a/images/genIcon.png b/images/genIcon.png new file mode 100644 index 00000000..020b0a3b Binary files /dev/null and b/images/genIcon.png differ diff --git a/images/i18n.png b/images/i18n.png new file mode 100644 index 00000000..5cf77fa3 Binary files /dev/null and b/images/i18n.png differ diff --git a/images/outDir.png b/images/outDir.png new file mode 100644 index 00000000..e45c0b2d Binary files /dev/null and b/images/outDir.png differ diff --git a/images/report.png b/images/report.png new file mode 100644 index 00000000..e86860e9 Binary files /dev/null and b/images/report.png differ diff --git a/images/selectIconSet.png b/images/selectIconSet.png new file mode 100644 index 00000000..d5835604 Binary files /dev/null and b/images/selectIconSet.png differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..f9f34f63 --- /dev/null +++ b/index.html @@ -0,0 +1,31 @@ + + + + + + Vben Admin + + + + + + + + + + + + + + + + +

Vben Admin

一个开箱即用的前端框架

💡 最新技术栈

基于Vue3、Vite、TypeScript等最新技术栈开发

⚡️ 轻量快速的热重载

无论应用程序大小如何,都始终极快的模块热重载(HMR)

🛠️ 丰富的示例

常见的Web端插件示例实现

📦 组件封装

对日常使用频率较高的组件二次封装,满足基础工作需求

🔩 主题配置

丰富的主题配置及黑暗主题适配

🔑 权限管理

完善的前后端权限管理方案

MIT Licensed | Copyright © 2021-present Vben

+ + + + \ No newline at end of file diff --git a/logo.png b/logo.png new file mode 100644 index 00000000..cd4c33d8 Binary files /dev/null and b/logo.png differ diff --git a/other/donate.html b/other/donate.html new file mode 100644 index 00000000..ff3026f2 --- /dev/null +++ b/other/donate.html @@ -0,0 +1,31 @@ + + + + + + 赞助 | Vben Admin + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/other/doubt.html b/other/doubt.html new file mode 100644 index 00000000..0f73ff05 --- /dev/null +++ b/other/doubt.html @@ -0,0 +1,53 @@ + + + + + + 常见疑点说明 | Vben Admin + + + + + + + + + + + + + + + + +

常见疑点说明

该分类主要说明一些地方为什么这样做,以及原因是什么

项目别名

/@/vite 内配置的别名

/@/settings 等同于 src/settings

为什么是/@/

因为项目是从 vite1.0 过渡过来的,vite1.0 只能以 / 开头,所以有一部分从 webpack 用户转过来的可能不习惯。

为什么在本地没有按需引入组件库样式,在生产才引入

在 main.ts 内可以看到,本地开发会全量引入 antd.less,vite-plugin-style-import 在本地是没有作用的。

这样做的原因主要是加快本地开发刷新速度。如果在本地开发中也按需按需引入,则在浏览器控制台内可以看到,平均一个页面大概增加了 100 次 http 请求。如果全量引入,只增加了一个请求,所以为了减少请求数量,才这样种。

// src/main.ts
+if (import.meta.env.DEV) {
+  import('ant-design-vue/dist/antd.less');
+}
+
+// build/vite/plugin/styleImport
+import styleImport from 'vite-plugin-style-import';
+export function configStyleImportPlugin(isBuild: boolean) {
+  if (!isBuild) return [];
+  const styleImportPlugin = styleImport({
+    libs: [
+      {
+        libraryName: 'ant-design-vue',
+        esModule: true,
+        resolveStyle: (name) => {
+          return `ant-design-vue/es/${name}/style/index`;
+        },
+      },
+    ],
+  });
+  return styleImportPlugin;
+}
+

为什么单独把 moment 放到 dataUtil 内

src/utils/dataUtil 内,使用的是 moment,其次在页面中对时间的操作也是使用 dateUtil,而不是直接 import moment from 'moment'

这样做主要是方便后续切换到 dayjs,因为 api 一样,所以在后续切换中,只需更改 dataUtil 内的 import 即可,而不用全部更改。

+ + + + \ No newline at end of file diff --git a/other/faq.html b/other/faq.html new file mode 100644 index 00000000..404fd02e --- /dev/null +++ b/other/faq.html @@ -0,0 +1,87 @@ + + + + + + 常见问题 | Vben Admin + + + + + + + + + + + + + + + + +

常见问题

TIP

列举了一些常见的问题。有问题可以先来这里寻找,如果没有可以在 issue 提。

前言

遇到问题,可以先从以下几个方面查找

  1. 对应模块的 GitHub 仓库 issue 搜索
  2. google搜索问题
  3. 百度搜索问题
  4. 在下面列表找不到问题可以到 issue 提问 issues
  5. 如果不是问题类型的,需要讨论的,请到 discussions 讨论

关于缓存更新问题

vben-admin 的项目配置默认是缓存在 localStorage 内,所以版本更新后可能有些配置没改变。

解决方式是每次更新代码的时候修改 package.json 内的 version 版本号. 因为 localStorage 的 key 是根据版本号来的。所以更新后版本不同前面的配置会失效。重新登录即可

VUE_VBEN_ADMIN__DEVELOPMENT__2.0.3__COMMON__LOCAL__KEY__ key 的组成是 [项目名]+[开发环境]+[版本号]+[key]

关于修改配置文件的问题

当修改 .env 等环境文件及 vite.config.ts 文件时,vite 会自动重启服务。

自动重启有几率出现问题,请重新运行项目即可解决.

esbuild 模式下开启 LEGACY 打包失败

如果将  build.minify 设置为 'esbuild',且不能启用 LEGACY,否则打包将会报错,两者选其一即可打包。

ant-design-vue 控制台警告

在控制台看到以下警告的原因是 ant-design-vue 会检测是否使用了 babel-plugin-import 来判断是否进行了组件库的按需引入。

但是项目使用的是 vite 的插件 vite-plugin-style-import 来进行按需引入。在 vite 内没必要使用 babel 在转换一次代码了。

所以想关闭这个警告,得等 ant-design-vue 提供可以关闭该警告的配置。

You are using a whole package of antd, please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundle size. Not support Vite !!!
+

添加菜单后没显示

菜单必须和路由匹配才会显示在界面上,所以得确保菜单和对应的路由存在即可显示.

本地运行报错

由于 vite 在本地没有转换代码,且代码中用到了可选链等比较新的语法。所以本地开发需要使用版本较高的浏览器(Chrome 85+)进行开发

tab 页切换后页面空白

这是由于开启了路由切换动画,且对应的页面组件存在多个根节点导致的,在页面最外层添加<div></div>即可

错误示例

<template>
+  <!-- 注释也算一个节点 -->
+  <h1>text h1</h1>
+  <h2>text h2</h2>
+</template>
+

正确示例

<template>
+  <div>
+    <h1>text h1</h1>
+    <h2>text h2</h2>
+  </div>
+</template>
+

提示

  • 如果想使用多个根标签,可以禁用路由切换动画
  • template 下面的根注释节点也算一个节点

组件命名问题

目前在 vite+vue3.0.5 版本中,如果组件命名携带关键字,则可能会导致内存溢出。例如 ImportExcel excel 导入组件。

我的代码本地开发可以,打包就不行了

目前发现这个原因可能有以下,可以从以下原因来排查,如果还有别的可能,可以提交 pr 来告诉我

  1. 使用了 ctx 这个变量,ctx 本身未暴露出在实例类型内,尤大也是说了不要用这个属性。这个属性只是用于内部使用。
import { getCurrentInstance } from 'vue';
+getCurrentInstance().ctx.xxxx;
+

safari 问题

目前在 safari 上面本地开发运行样式会有问题,还未找到原因,有知道的也可以告诉我。

模版区别

  • Vue-Vben-Admin - 是包含 Demo 示例的,个人建议不要在这上面进行开发。当然,你如果动手能力强的话可以直接改。
  • Vue-Vben-Admin-Thin-Next 精简了代码后的模版项目。适合在这基础上进行二次开发。

环境问题

如果出现依赖安装报错,启动报错等。先检查电脑环境有没有安装齐全。

  • Node 版本必须大于12.0.0不支持 13, 推荐 14 版本。
  • Git
  • Yarn 最新版

依赖安装问题

  • 如果依赖安装不了或者启动报错可以先尝试 删除 yarn.locknode_modules,然后重新运行 yarn install
  • 如果依赖安装不了或者报错,可以尝试切换手机热点来进行依赖安装。
  • 如果还是不行,可以自行配置国内镜像安装。
  • 也可以在项目根目录创建 .npmrc 文件,内容如下
# .npmrc
+registry = https://registry.npm.taobao.org
+

然后重新执行yarn run reinstall等待安装完成即可

如何保证我的代码能更新到最新代码

如果你使用了该项目进行项目开发。开发之中想同步最新的代码。你可以设置多个源的方式

  1. 克隆代码
git clone https://github.com/vbenjs/vben-admin-thin-next.git
+
  1. 添加自己的公司 git 源地址
# up 为源名称,可以随意设置
+# gitUrl为开源最新代码
+git remote add up gitUrl;
+
  1. 提交代码到自己公司 git
# 提交代码到自己公司
+# main为分支名 需要自行根据情况修改
+git push up main
+
+# 同步公司的代码
+# main为分支名 需要自行根据情况修改
+git pull up main
+
  1. 如何同步开源最新代码
git pull origin main
+

TIP

同步代码的时候会出现冲突。只需要把冲突解决即可

打包文件过大

  • 首先,完整版由于引用了比较多的库文件,所以打包会比较大。可以使用精简版来进行开发

  • 其次建议开启 gzip,使用之后体积会只有原先 1/3 左右。

gzip 可以由服务器直接开启。如果是这样,前端不需要构建 .gz 格式的文件

如果前端构建了 .gz 文件,以 nginx 为例,nginx 需要开启 gzip_static: on 这个选项。

  • 开启 gzip 的同时还可以同时开启 brotli,比 gzip 更好的压缩。两者可以共存

注意

  • gzip_static: 这个模块需要 nginx 另外安装,默认的 nginx 没有安装这个模块。

  • 开启 brotli 也需要 nginx 另外安装模块

运行错误

如果出现类似以下错误,请检查项目全路径(包含所有父级路径)不能出现中文、日文、韩文。否则将会出现路径访问 404 导致以下问题

[vite] Failed to resolve module import "ant-design-vue/dist/antd.css-vben-adminode_modulesant-design-vuedistantd.css". (imported by /@/setup/ant-design-vue/index.ts)
+

为什么是 moment.js

很多人问为什么不用dayjs。在项目依赖中可以看到,它是 Ant-Design-Vue 内部自带的。

目前还没有基于 Vite 的 dayjs 替换 momentjs 方案,webpack 已经有了。等以后出现了在进行替换。

控制台路由警告问题

如果看到控制台有如下警告,且页面能正常打开 可以忽略该警告。

后续 vue-router 可能会提供配置项来关闭警告

2.6.1 及以上版本已移除此警告

[Vue Router warn]: No match found for location with path "xxxx"
+

启动报错

当出现以下错误信息时,请检查你的 nodejs 版本号是否符合要求

TypeError: str.matchAll is not a function
+at Object.extractor (vue-vben-admin-main\node_modules@purge-icons\core\dist\index.js:146:27)
+at Extract (vue-vben-admin-main\node_modules@purge-icons\core\dist\index.js:173:54)
+
+

页面报错

当页面出现以下报错,是因为 /xxx 对应的路由组件内部出现了错误。

 Uncaught (in promise) Error: Couldn't resolve component "default" at "/xxx"
+
+

可以尝试从以下几点排查

  1. 检查对应组件内部 import 的所有文件是否正确
  2. 检查引入方式是否错误。
// 正确的
+import { cloneDeep } from 'lodash-es';
+
+// 报错
+import _ from 'lodash-es';
+
  1. 检查样式是否使用变量及有没有引入对应的变量文件
  2. 检查代码明显的语法错误

这样就不会是使用的取值忘记 xxx.value 来进行数据获取

跨域问题

参考跨域问题

接口请求问题

proxy 代理不成功,没有代理到实际地址?

代理只是服务请求代理,这个地址是不会变的。 原理可以简单的理解为,在本地启了一个服务,你先请求了本地的服务,本地的服务转发了你的请求到实际服务器。所以你在浏览器上看到的请求地址还是 http://localhost:8000/xxx。以服务端是否收到请求为准。

组件库问题

跟组件库相关的问题可以查看常见问题

动态调整菜单问题

菜单数据的值被存放在 store/modules/permission store 中, 你可以在这里进行修改

更灵活的菜单路由权限控制

你可以在 store/modules/permission下, 修改 routeFilter 方法来进行更灵活的菜单路由权限控制

 const routeFilter = (route: AppRouteRecordRaw) => {
+    const { meta } = route;
+    // 抽出角色
+    const { roles } = meta || {};
+
+    // 添加你的自定义逻辑来过滤路由和菜单
+    if (xxx) {
+      return false;
+    }
+
+    if (!roles) return true;
+    // 进行角色权限判断
+    return roleList.some((role) => roles.includes(role));
+  };
+
+
+ + + + \ No newline at end of file diff --git a/other/project.html b/other/project.html new file mode 100644 index 00000000..ba9b6334 --- /dev/null +++ b/other/project.html @@ -0,0 +1,31 @@ + + + + + + 相关项目 | Vben Admin + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/other/server.html b/other/server.html new file mode 100644 index 00000000..73bc65e1 --- /dev/null +++ b/other/server.html @@ -0,0 +1,40 @@ + + + + + + 测试服务器 | Vben Admin + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file