diff --git a/.eslintignore b/.eslintignore index cc6fa79..b4bca4a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,4 @@ public *.config.js *.config.ts start-debug.js +src/assets/iconfont diff --git a/.vscode/settings.json b/.vscode/settings.json index ea1e0d1..3cd6249 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,7 +12,7 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[less]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "vscode.css-language-features" }, "files.associations": { "*.vue": "vue" diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..d00dd12 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,17 @@ +module.exports = { + presets: [ + [ + '@babel/preset-env', + { + targets: { + chrome: '80', + firefox: '75', + safari: '14', + edge: '80', + }, + useBuiltIns: 'usage', + corejs: 3, + }, + ], + ], +}; diff --git a/commitlint.config.js b/commitlint.config.cjs similarity index 100% rename from commitlint.config.js rename to commitlint.config.cjs diff --git a/src/App.tsx b/src/App.tsx index ba5b391..4c2d3ea 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,17 @@ export default defineComponent({ setup() { const theme = { token: { - colorPrimary: '#1677ff', + colorPrimary: '#1890ff', + colorSuccess: '#52c41a', + colorWarning: '#faad14', + colorError: '#ff4d4f', + colorInfo: '#1890ff', + colorBgBase: '#ffffff', + colorBgContainer: '#ffffff', + colorBgElevated: '#ffffff', + colorBgLayout: '#f0f2f5', + colorTextBase: '#262626', + borderRadius: 6, }, }; diff --git a/src/assets/iconfont/demo.css b/src/assets/iconfont/demo.css new file mode 100644 index 0000000..8a05f6e --- /dev/null +++ b/src/assets/iconfont/demo.css @@ -0,0 +1,545 @@ +/* Logo 字体 */ +@font-face { + font-family: 'iconfont logo'; + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); + src: + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') + format('embedded-opentype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') + format('svg'); +} + +.logo { + font-family: 'iconfont logo'; + font-size: 160px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* tabs */ +.nav-tabs { + position: relative; +} + +.nav-tabs .nav-more { + position: absolute; + right: 0; + bottom: 0; + height: 42px; + line-height: 42px; + color: #666; +} + +#tabs { + border-bottom: 1px solid #eee; +} + +#tabs li { + cursor: pointer; + width: 100px; + height: 40px; + line-height: 40px; + text-align: center; + font-size: 16px; + border-bottom: 2px solid transparent; + position: relative; + z-index: 1; + margin-bottom: -1px; + color: #666; +} + +#tabs .active { + border-bottom-color: #f00; + color: #222; +} + +.tab-container .content { + display: none; +} + +/* 页面布局 */ +.main { + padding: 30px 100px; + width: 960px; + margin: 0 auto; +} + +.main .logo { + color: #333; + text-align: left; + margin-bottom: 30px; + line-height: 1; + height: 110px; + margin-top: -50px; + overflow: hidden; + *zoom: 1; +} + +.main .logo a { + font-size: 160px; + color: #333; +} + +.helps { + margin-top: 40px; +} + +.helps pre { + padding: 20px; + margin: 10px 0; + border: solid 1px #e7e1cd; + background-color: #fffdef; + overflow: auto; +} + +.icon_lists { + width: 100% !important; + overflow: hidden; + *zoom: 1; +} + +.icon_lists li { + width: 100px; + margin-bottom: 10px; + margin-right: 20px; + text-align: center; + list-style: none !important; + cursor: default; +} + +.icon_lists li .code-name { + line-height: 1.2; +} + +.icon_lists .icon { + display: block; + height: 100px; + line-height: 100px; + font-size: 42px; + margin: 10px auto; + color: #333; + -webkit-transition: + font-size 0.25s linear, + width 0.25s linear; + -moz-transition: + font-size 0.25s linear, + width 0.25s linear; + transition: + font-size 0.25s linear, + width 0.25s linear; +} + +.icon_lists .icon:hover { + font-size: 100px; +} + +.icon_lists .svg-icon { + /* 通过设置 font-size 来改变图标大小 */ + width: 1em; + /* 图标和文字相邻时,垂直对齐 */ + vertical-align: -0.15em; + /* 通过设置 color 来改变 SVG 的颜色/fill */ + fill: currentColor; + /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 + normalize.css 中也包含这行 */ + overflow: hidden; +} + +.icon_lists li .name, +.icon_lists li .code-name { + color: #666; +} + +/* markdown 样式 */ +.markdown { + color: #666; + font-size: 14px; + line-height: 1.8; +} + +.highlight { + line-height: 1.5; +} + +.markdown img { + vertical-align: middle; + max-width: 100%; +} + +.markdown h1 { + color: #404040; + font-weight: 500; + line-height: 40px; + margin-bottom: 24px; +} + +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + color: #404040; + margin: 1.6em 0 0.6em 0; + font-weight: 500; + clear: both; +} + +.markdown h1 { + font-size: 28px; +} + +.markdown h2 { + font-size: 22px; +} + +.markdown h3 { + font-size: 16px; +} + +.markdown h4 { + font-size: 14px; +} + +.markdown h5 { + font-size: 12px; +} + +.markdown h6 { + font-size: 12px; +} + +.markdown hr { + height: 1px; + border: 0; + background: #e9e9e9; + margin: 16px 0; + clear: both; +} + +.markdown p { + margin: 1em 0; +} + +.markdown > p, +.markdown > blockquote, +.markdown > .highlight, +.markdown > ol, +.markdown > ul { + width: 80%; +} + +.markdown ul > li { + list-style: circle; +} + +.markdown > ul li, +.markdown blockquote ul > li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown > ul li p, +.markdown > ol li p { + margin: 0.6em 0; +} + +.markdown ol > li { + list-style: decimal; +} + +.markdown > ol li, +.markdown blockquote ol > li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown code { + margin: 0 3px; + padding: 0 5px; + background: #eee; + border-radius: 3px; +} + +.markdown strong, +.markdown b { + font-weight: 600; +} + +.markdown > table { + border-collapse: collapse; + border-spacing: 0px; + empty-cells: show; + border: 1px solid #e9e9e9; + width: 95%; + margin-bottom: 24px; +} + +.markdown > table th { + white-space: nowrap; + color: #333; + font-weight: 600; +} + +.markdown > table th, +.markdown > table td { + border: 1px solid #e9e9e9; + padding: 8px 16px; + text-align: left; +} + +.markdown > table th { + background: #f7f7f7; +} + +.markdown blockquote { + font-size: 90%; + color: #999; + border-left: 4px solid #e9e9e9; + padding-left: 0.8em; + margin: 1em 0; +} + +.markdown blockquote p { + margin: 0; +} + +.markdown .anchor { + opacity: 0; + transition: opacity 0.3s ease; + margin-left: 8px; +} + +.markdown .waiting { + color: #ccc; +} + +.markdown h1:hover .anchor, +.markdown h2:hover .anchor, +.markdown h3:hover .anchor, +.markdown h4:hover .anchor, +.markdown h5:hover .anchor, +.markdown h6:hover .anchor { + opacity: 1; + display: inline-block; +} + +.markdown > br, +.markdown > p > br { + clear: both; +} + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} + +/* 代码高亮 */ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*='language-'], +pre[class*='language-'] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + 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; +} + +pre[class*='language-']::-moz-selection, +pre[class*='language-'] ::-moz-selection, +code[class*='language-']::-moz-selection, +code[class*='language-'] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*='language-']::selection, +pre[class*='language-'] ::selection, +code[class*='language-']::selection, +code[class*='language-'] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*='language-'], + pre[class*='language-'] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*='language-'] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; +} + +:not(pre) > code[class*='language-'], +pre[class*='language-'] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*='language-'] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: 0.7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, 0.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #dd4a68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/src/assets/iconfont/demo_index.html b/src/assets/iconfont/demo_index.html new file mode 100644 index 0000000..5184e09 --- /dev/null +++ b/src/assets/iconfont/demo_index.html @@ -0,0 +1,1410 @@ + + + + + iconfont Demo + + + + + + + + + + + + + +
+

+ + +

+ +
+
+
    + +
  • + +
    场馆
    +
    
    +
  • + +
  • + +
    灯-点亮
    +
    
    +
  • + +
  • + +
    灯-关闭
    +
    
    +
  • + +
  • + +
    刷新
    +
    
    +
  • + +
  • + +
    关闭
    +
    
    +
  • + +
  • + +
    添加
    +
    
    +
  • + +
  • + +
    签到码
    +
    
    +
  • + +
  • + +
    日历
    +
    
    +
  • + +
  • + +
    分组title
    +
    
    +
  • + +
  • + +
    成功
    +
    
    +
  • + +
  • + +
    存草稿
    +
    
    +
  • + +
  • + +
    图片
    +
    
    +
  • + +
  • + +
    微信支付
    +
    
    +
  • + +
  • + +
    删除
    +
    
    +
  • + +
  • + +
    钱包
    +
    
    +
  • + +
  • + +
    +
    
    +
  • + +
  • + +
    金币
    +
    
    +
  • + +
  • + +
    +
    
    +
  • + +
  • + +
    分享
    +
    
    +
  • + +
  • + +
    修改
    +
    
    +
  • + +
  • + +
    编辑
    +
    
    +
  • + +
  • + +
    更多1
    +
    
    +
  • + +
  • + +
    +
    
    +
  • + +
  • + +
    添加
    +
    
    +
  • + +
  • + +
    更多
    +
    
    +
  • + +
  • + +
    电话
    +
    
    +
  • + +
  • + +
    撤销
    +
    
    +
  • + +
  • + +
    微信
    +
    
    +
  • + +
  • + +
    分组
    +
    
    +
  • + +
  • + +
    点名
    +
    
    +
  • + +
  • + +
    套餐
    +
    
    +
  • + +
  • + +
    特价
    +
    
    +
  • + +
  • + +
    满减
    +
    
    +
  • + +
  • + +
    +
    
    +
  • + +
  • + +
    +
    
    +
  • + +
  • + +
    分享
    +
    
    +
  • + +
  • + +
    浏览量
    +
    
    +
  • + +
  • + +
    日历
    +
    
    +
  • + +
  • + +
    +
    
    +
  • + +
  • + +
    +
    
    +
  • + +
  • + +
    定位
    +
    
    +
  • + +
  • + +
    关闭
    +
    
    +
  • + +
  • + +
    收藏
    +
    
    +
  • + +
  • + +
    时间
    +
    
    +
  • + +
  • + +
    场馆
    +
    
    +
  • + +
  • + +
    未选中
    +
    
    +
  • + +
  • + +
    选择
    +
    
    +
  • + +
  • + +
    选择-fill
    +
    
    +
  • + +
  • + +
    选中
    +
    
    +
  • + +
  • + +
    微信支付
    +
    
    +
  • + +
  • + +
    +
    
    +
  • + +
  • + +
    联系我们
    +
    
    +
  • + +
  • + +
    余额
    +
    
    +
  • + +
+
+

Unicode 引用

+
+ +

Unicode 是字体在网页端最原始的应用方式,特点是:

+
    +
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • +
  • 默认情况下不支持多色,直接添加多色图标会自动去色。
  • +
+
+

注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)

+
+

Unicode 使用步骤如下:

+

第一步:拷贝项目下面生成的 @font-face

+
@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.eot?t=1784185072587'); /* IE9 */
+  src: url('iconfont.eot?t=1784185072587#iefix') format('embedded-opentype'), /* IE6-IE8 */
+       url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
+       url('iconfont.woff?t=1784185072587') format('woff'),
+       url('iconfont.ttf?t=1784185072587') format('truetype'),
+       url('iconfont.svg?t=1784185072587#iconfont') format('svg');
+}
+
+

第二步:定义使用 iconfont 的样式

+
.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+

第三步:挑选相应图标并获取字体编码,应用于页面

+
+<span class="iconfont">&#x33;</span>
+
+
+

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    + 场馆 +
    +
    .icon-changguan1 +
    +
  • + +
  • + +
    + 灯-点亮 +
    +
    .icon-deng-dianliang +
    +
  • + +
  • + +
    + 灯-关闭 +
    +
    .icon-deng-guanbi +
    +
  • + +
  • + +
    + 刷新 +
    +
    .icon-shuaxin +
    +
  • + +
  • + +
    + 关闭 +
    +
    .icon-guanbi1 +
    +
  • + +
  • + +
    + 添加 +
    +
    .icon-tianjia1 +
    +
  • + +
  • + +
    + 签到码 +
    +
    .icon-qiandaoma +
    +
  • + +
  • + +
    + 日历 +
    +
    .icon-rili1 +
    +
  • + +
  • + +
    + 分组title +
    +
    .icon-fenzutitle +
    +
  • + +
  • + +
    + 成功 +
    +
    .icon-chenggong +
    +
  • + +
  • + +
    + 存草稿 +
    +
    .icon-cuncaogao +
    +
  • + +
  • + +
    + 图片 +
    +
    .icon-tupian +
    +
  • + +
  • + +
    + 微信支付 +
    +
    .icon-weixinzhifu1 +
    +
  • + +
  • + +
    + 删除 +
    +
    .icon-shanchu +
    +
  • + +
  • + +
    + 钱包 +
    +
    .icon-qianbao +
    +
  • + +
  • + +
    + 男 +
    +
    .icon-nan +
    +
  • + +
  • + +
    + 金币 +
    +
    .icon-jinbi +
    +
  • + +
  • + +
    + 女 +
    +
    .icon-nv +
    +
  • + +
  • + +
    + 分享 +
    +
    .icon-fenxiang1 +
    +
  • + +
  • + +
    + 修改 +
    +
    .icon-xiugai +
    +
  • + +
  • + +
    + 编辑 +
    +
    .icon-bianji +
    +
  • + +
  • + +
    + 更多1 +
    +
    .icon-gengduo1 +
    +
  • + +
  • + +
    + 上 +
    +
    .icon-shang +
    +
  • + +
  • + +
    + 添加 +
    +
    .icon-tianjia +
    +
  • + +
  • + +
    + 更多 +
    +
    .icon-gengduo +
    +
  • + +
  • + +
    + 电话 +
    +
    .icon-dianhua +
    +
  • + +
  • + +
    + 撤销 +
    +
    .icon-chexiao +
    +
  • + +
  • + +
    + 微信 +
    +
    .icon-weixin +
    +
  • + +
  • + +
    + 分组 +
    +
    .icon-fenzu +
    +
  • + +
  • + +
    + 点名 +
    +
    .icon-dianming +
    +
  • + +
  • + +
    + 套餐 +
    +
    .icon-taocan +
    +
  • + +
  • + +
    + 特价 +
    +
    .icon-tejia +
    +
  • + +
  • + +
    + 满减 +
    +
    .icon-manjian +
    +
  • + +
  • + +
    + 票 +
    +
    .icon-piao +
    +
  • + +
  • + +
    + 勾 +
    +
    .icon-gou +
    +
  • + +
  • + +
    + 分享 +
    +
    .icon-fenxiang +
    +
  • + +
  • + +
    + 浏览量 +
    +
    .icon-liulanliang +
    +
  • + +
  • + +
    + 日历 +
    +
    .icon-rili +
    +
  • + +
  • + +
    + 左 +
    +
    .icon-zuo +
    +
  • + +
  • + +
    + 下 +
    +
    .icon-xia +
    +
  • + +
  • + +
    + 定位 +
    +
    .icon-dingwei +
    +
  • + +
  • + +
    + 关闭 +
    +
    .icon-guanbi +
    +
  • + +
  • + +
    + 收藏 +
    +
    .icon-shoucang +
    +
  • + +
  • + +
    + 时间 +
    +
    .icon-shijian +
    +
  • + +
  • + +
    + 场馆 +
    +
    .icon-changguan +
    +
  • + +
  • + +
    + 未选中 +
    +
    .icon-weixuanzhong +
    +
  • + +
  • + +
    + 选择 +
    +
    .icon-xuanze +
    +
  • + +
  • + +
    + 选择-fill +
    +
    .icon-xuanze-fill +
    +
  • + +
  • + +
    + 选中 +
    +
    .icon-xuanzhong +
    +
  • + +
  • + +
    + 微信支付 +
    +
    .icon-weixinzhifu +
    +
  • + +
  • + +
    + 右 +
    +
    .icon-you +
    +
  • + +
  • + +
    + 联系我们 +
    +
    .icon-lianxiwomen +
    +
  • + +
  • + +
    + 余额 +
    +
    .icon-yue +
    +
  • + +
+
+

font-class 引用

+
+ +

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

+

与 Unicode 使用方式相比,具有如下特点:

+
    +
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • +
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 fontclass 代码:

+
<link rel="stylesheet" href="./iconfont.css">
+
+

第二步:挑选相应图标并获取类名,应用于页面:

+
<span class="iconfont icon-xxx"></span>
+
+
+

" + iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    场馆
    +
    #icon-changguan1
    +
  • + +
  • + +
    灯-点亮
    +
    #icon-deng-dianliang
    +
  • + +
  • + +
    灯-关闭
    +
    #icon-deng-guanbi
    +
  • + +
  • + +
    刷新
    +
    #icon-shuaxin
    +
  • + +
  • + +
    关闭
    +
    #icon-guanbi1
    +
  • + +
  • + +
    添加
    +
    #icon-tianjia1
    +
  • + +
  • + +
    签到码
    +
    #icon-qiandaoma
    +
  • + +
  • + +
    日历
    +
    #icon-rili1
    +
  • + +
  • + +
    分组title
    +
    #icon-fenzutitle
    +
  • + +
  • + +
    成功
    +
    #icon-chenggong
    +
  • + +
  • + +
    存草稿
    +
    #icon-cuncaogao
    +
  • + +
  • + +
    图片
    +
    #icon-tupian
    +
  • + +
  • + +
    微信支付
    +
    #icon-weixinzhifu1
    +
  • + +
  • + +
    删除
    +
    #icon-shanchu
    +
  • + +
  • + +
    钱包
    +
    #icon-qianbao
    +
  • + +
  • + +
    +
    #icon-nan
    +
  • + +
  • + +
    金币
    +
    #icon-jinbi
    +
  • + +
  • + +
    +
    #icon-nv
    +
  • + +
  • + +
    分享
    +
    #icon-fenxiang1
    +
  • + +
  • + +
    修改
    +
    #icon-xiugai
    +
  • + +
  • + +
    编辑
    +
    #icon-bianji
    +
  • + +
  • + +
    更多1
    +
    #icon-gengduo1
    +
  • + +
  • + +
    +
    #icon-shang
    +
  • + +
  • + +
    添加
    +
    #icon-tianjia
    +
  • + +
  • + +
    更多
    +
    #icon-gengduo
    +
  • + +
  • + +
    电话
    +
    #icon-dianhua
    +
  • + +
  • + +
    撤销
    +
    #icon-chexiao
    +
  • + +
  • + +
    微信
    +
    #icon-weixin
    +
  • + +
  • + +
    分组
    +
    #icon-fenzu
    +
  • + +
  • + +
    点名
    +
    #icon-dianming
    +
  • + +
  • + +
    套餐
    +
    #icon-taocan
    +
  • + +
  • + +
    特价
    +
    #icon-tejia
    +
  • + +
  • + +
    满减
    +
    #icon-manjian
    +
  • + +
  • + +
    +
    #icon-piao
    +
  • + +
  • + +
    +
    #icon-gou
    +
  • + +
  • + +
    分享
    +
    #icon-fenxiang
    +
  • + +
  • + +
    浏览量
    +
    #icon-liulanliang
    +
  • + +
  • + +
    日历
    +
    #icon-rili
    +
  • + +
  • + +
    +
    #icon-zuo
    +
  • + +
  • + +
    +
    #icon-xia
    +
  • + +
  • + +
    定位
    +
    #icon-dingwei
    +
  • + +
  • + +
    关闭
    +
    #icon-guanbi
    +
  • + +
  • + +
    收藏
    +
    #icon-shoucang
    +
  • + +
  • + +
    时间
    +
    #icon-shijian
    +
  • + +
  • + +
    场馆
    +
    #icon-changguan
    +
  • + +
  • + +
    未选中
    +
    #icon-weixuanzhong
    +
  • + +
  • + +
    选择
    +
    #icon-xuanze
    +
  • + +
  • + +
    选择-fill
    +
    #icon-xuanze-fill
    +
  • + +
  • + +
    选中
    +
    #icon-xuanzhong
    +
  • + +
  • + +
    微信支付
    +
    #icon-weixinzhifu
    +
  • + +
  • + +
    +
    #icon-you
    +
  • + +
  • + +
    联系我们
    +
    #icon-lianxiwomen
    +
  • + +
  • + +
    余额
    +
    #icon-yue
    +
  • + +
+
+

Symbol 引用

+
+ +

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 + 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

+
    +
  • 支持多色图标了,不再受单色限制。
  • +
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • +
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • +
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 symbol 代码:

+
<script src="./iconfont.js"></script>
+
+

第二步:加入通用 CSS 代码(引入一次就行):

+
<style>
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>
+
+

第三步:挑选相应图标并获取类名,应用于页面:

+
<svg class="icon" aria-hidden="true">
+  <use xlink:href="#icon-xxx"></use>
+</svg>
+
+
+
+ +
+
+ + + diff --git a/src/assets/iconfont/iconfont.css b/src/assets/iconfont/iconfont.css new file mode 100644 index 0000000..1df74d1 --- /dev/null +++ b/src/assets/iconfont/iconfont.css @@ -0,0 +1,232 @@ +@font-face { + font-family: 'iconfont'; /* Project id 4533260 */ + src: url('iconfont.eot?t=1784185072587'); /* IE9 */ + src: + url('iconfont.eot?t=1784185072587#iefix') format('embedded-opentype'), + /* IE6-IE8 */ + url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAB+UAAsAAAAAPlgAAB9GAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACJAArjeNMIATYCJAOBWAtuAAQgBYRnB4RBG7U0VSGwcQDEj32nyP7/lqDG4RcuKjOh2HFy71PY3bZNXKGw95oQdfkYOF5+plRuoufhs1BDGmxYBkAw+AumdDktpgfDAB56LnjvhAfrdbr/ckMpIZ7WfvP2vqiiV5ESSYHUCSXuoKECES7bJLe52vOlFffgURYlsZLh+W3+H7iECSKRSkiWgVGkgShl1URnRMy51EV9dZW6at37vgoW/aoX/aIWEGC3+/3/M30WBtLWSn5RIm0JhBElFCdpTtxg8O1ek4ZuiG/R0KOlLgAa0ICAcwTsT7VP68PMsKRw4611/22XsRDKw5I/RmmDOTWp7ZG0sSGwcRw8AjAMqDyE6/KxvvIXlDYbJ42hxAMgg9+7ljd3/CfJ7rYsq/qqFJMqq7Lkic3YQ1tCX6EA1XVzqXpqAxwUFha0u7ekrA3Lx7LpP8QlTJKvLGNKZ9lKDwBdCDZcdEnZZ3ZXOlhwTmv7YU8O6HwPsp9kH+kJoAsASOebiXUwhgeA6r8LUhnuqSnSl983KVNW0VxDeujPJddyVFnCw+0YU+vhSLu1xOBCxRkUdMf9v4+EAC0333T97geYBtuKRhibBry60XgZ+V0iJKFu8Clu1lbTfh0y0h5e4IN8/fHxCH6QmIixV29zN3n71wT7Ck3x8MdvJmg6gSooQCnZsPiTzT/gOKQkLGS57R/ASH4pTL9feS5/de+VTHPtsfzf4f4X85cDZHbPzpChNPvX2v8l71c9wJnmmJmbcu0dHJ2cXVzd3D08vbztbK1trCz0rIGhkbGJSq3R6gRCkVgilckVSj6PMBSiALU7gHQo4smQ6RjgjHwRICHPCQrEvwTBAfhXIDgCfwU3socANSgDuIdygAeoAHiESoASqihFuBoowDVAAa4FCvBIoACPAgrwaKAAjwEKcB1QgMcCwB2MA7iF8QBXmABwAxMBLjAJ4ASTARYwBWAJUwFWUA+whgaADTQCbKEJYAfNAGNoAZjANIApTAeYwQyAOcwEaEIrQAtmAbShDaADswG6MAegB+0AfegAGEAnwBC6AEbQDdCAuQB12A1QgT0AVdhXssqwZ6uFd4AvwOAGTC8jGifx4ZVDKNrUZzLR2HYotY3FtMIk1NcFSvWZygJBkgtLs+nJTK2y2DIdYLIwDZg8WB1i4Ibu2GQeI5n0aTv5siOS3LjHOE6SWSNxQNJkQxt0qTN5tKPvqKJF6jDe9/b17J5BEI7jOI0boxL0WGubkcIqgBNk9ztrizKLojofd4dHEt1c1w6NiAm8NvpxVnIxmadEnnjbof+RUxyXdtemdT6XeYKDQZKMBpN127qlNDYuirEtveZTXZBffb94yedl4xIAchE5ehkSEVlkbuDdLYQAQwCMbaahapMK2PHSukHvQGY3oragLrCKco0lVTHu0D0dHsRNqFnRj3+7oLQDTkYCZHi/hNRtUC+lRginkLESHB2OBD/LFUedxdqSYhkNBol41AFgyOZ+dt+suFjFfYgKxTwCQVPlB3GyVCReHQzX87dtv+Hy3OyaX3c5zh4bg6DKZr9gC0BBBwDDjP/FVRtV4mz0SHjVJKJOSZOxcWEYUt9ctai0sAulGEEAerGKWGMSJbin4oIki37GCO2fi4xqMzI+snSnzF9D6hFRU23iG7yiMcPVOTnHnxoCYlIbEqI2YZghYYYFABuWbpZBLwIglzAbVyCRy5vo+1sQ6iIhxKJ7XAT4Sxp6MRWxDPyFULkCXUdRfDmrXpBvuenBYWUMRsiQowB9CqY/w4nxAubI8/9FDMQ1fEitTy1gEPccb17cgQ2feKhtF57nLU07q/mNGscxivIgCGH46j3+sAest8erKBqJpNnEj7UltnoGt9UEG6E/c/MyaDsjHTAlE8a0l69YWduYhfZmObAIgasO9W/Fmha7y/OAOByCg3X5k8/C2nS8kvki3o2mxc1oJPj+5vVc9OZWGbcn0ZYUPk16hVtEPaGZXGjzXNkdzc+V59GZEdDCmTzQB2dxMoKGMaGTknPezbx8PZiXiI+t4cOat27DaoiMQS+omgLEQfxGQGrhkpFY2om4Y9LaQ78A1QkxFa/Uc92R4TgioPpZFgAzuonEMoSpwRjmEI+sjJmP3uxA5q7ggCZOO2WYh32IZ1NGrdoI1OTkeRWSl68k+mhXKLRvLyumx720sOyjyDkaBDA9DGOPIo6zTQvMVATp9AvFcHUHgXrEx0eSJ+SmYWC2RSE9U6/xf3EnNtYaVCTx/v/DgKPnyxGJv7ry26fX8Itnj/XczY+AnnFJz5gIgI1ZfYaE5WKbSUTnymSMlOFq9Z5cedj6XNLzqIguwu4pyapNHZ1XZFPnrbrREDXlRlkflEoX46gr0+s856gTv1yJ1fe9m2+rQp9qd6VJPAJrgOS2524iOr0+KLVWgKPHNTI2nulP6XPy4vAV+/gBuyXwpz0L6NwQLJDhC9h2nId5eWEz8Q0XNP3gO9uRcmREMZ6WIl0Lgk3E7nXEp6PWE9PL5ApOamle3IvF+29oVTzGDElx9fzhGmeq3D3XsRHp1x4VT27E64/zh1dnFPnkbAldHQ5HR6nbNwqd91g7s9YX7G0J1JZPrOI5Brm0g+m7SOv8m2Vexj6ozF1TNRUyvESrEF58ltTqzOUimsxzzvs3N5rAsbWnS18xg1csnfucM81Z43LrDfGr6rQUnGKPozYkwqYxifkyzgOrVIAEdrYDW5t2OpfLdTFDRLPye3PRChLHXsOwhKbGgg+wWnv+rL1Po4iF1ALB1S4CGWGDQSI2gl2r9KrWKGD24vcvpICauoUrTCSau7hjc218jkTRaJ+WOXJFqbwE5EVTa+8H4Fjr/I5FkXusqZUEzfrmyr+Jo+Pr5qpOwnQluf1D8HFtIKnzlhQqhaex/DSMI5tnMjDIafXILQSPDwXi9Bk7sfjlXTVoRlH/Tjxo+IFO6EXQtNh4MA4z+1MMwLt8viinGiUEMaxlHnFB1GyWlkwuIeXSD139WZQFYedMUUQOp0UmbUER/PhhEMcUXw7eTzVyRVO5zgIxWcv8kFATcooxeMfA+AC79z5QuU+YqEAEZp8LlfuzItJei87HOJVLKqguSSynId1Zz3ySsD81xXh+sV8Q6xITl3e8pUmlhpDqJYm+yM8OSE32coS9Ab3LMwDB91f8BlGFy55fK1UkxHFlshasAFYSK6pvDa+UKq8DTT8xkC/6/mn4hqKduOBdTlUuBl0lP41PXiscyvGjqAB91V8I6ZzMqmmkAXCXVEVsa7yT6Uut6MhqUsrLlnhXio0BMYVEmuSKpjC2n+ZswRYC+GRpgaMAuAbMe8XSF/JhKArMZlk6fTotKZUKtVNCJV9kPVMOZpGnVAA5k8hWz0PCK/By3CfkrORgNXJ2eXWldOZ1i7c2HZmW1KzXHBn3RgF3ThNCXdU5chc5en/sOeLjkwZesSeQ8Qd4Y0ru7BNP9SY5VO3ntqklapV5W6MutrNqOaGSO8p3jKa7p3c8U/U2HRFTTRRTGxYOTSXHCOvhVWhdEz654I2Gl3cA42yYfS5uJVY+TUZ6J8YpohEr79K+yLYcrAU//yLUDcW71PkKBQEghHEy6WhP+QnJc3QQ6ljiMGlatHXukfnyhaRTEMKmuJ0twZ2G237PRJe3xB7fPx1lEmOV6vyRY/6dW39nz2T+d27erhynKbt7/XCGIOjTmYGTBdKnIENzGzT849EZGJnxp2LPDcnQidPDk/RuxKVTssFRxjCS3ReyETc81GmdPOFMTK3vfW5fPufu8s5fuVPo7ParnCZfDuTwuvF2Hdaj7HG+buTO9OwGL8jKGY2MdqmdEhALrd00QFpAi8arQJANVQ3iprLyB6xe0FJr4jSl4Dhw8PLC3cajuW2sz6R7krXsbGR+OJFR07pDLFJtNAZe9AI6gt321y4yWzVeqqlVR4JKSRwSVVKo+LnhpzeXQRP5w123pDMXWwxtYuhLlsr+S8lKTqHQIh7G9zpMKhzU23eHX1m8Vsp2XkaN6RNFdoK3Oky0exRLCBtsWGOg8bnxoWh7FrPrQOfkvYYYn4Ox8hnPo2/3c2ZrmDt1Wmx5fX9N63B6aOrD/jsgPrXi/FvDn4n+Zw1Dmi3wbmFQf93JcybPhy0/Hlui6JcuGJ+4BX72jV49MfAxJ2vmeOCSIQIrid5k+L+hugRgI+eXsiUYEbzigiUpZ5rCQUKjujptoe90momIkBgU38A+BrXKxI/kE5PFf2BZnIwaEhegiG5kzYx0rRMeA73D6HgSFTGRmMVuK2shyfNW423ArN67dJ40AQH+JEeVJzJcjId0N8PLcNPTAuIEDQWxgd5dFm8cuiOtB4GGdmjtC96xPlaDPpR2yIhwofZI4tBfp32d+3c9uvDb/QfXY5EqJ0KATicisSQP6uSmrtItqqm4S4QOg6QtDt2f1j9KhOV/2YGOVXVhug6WYDpvd6JtP5Z4uMCMXEd8BAX0WblKk9yAtqNdadtMSoPcjnKht1nCp8+BdnKzWrOs5IA+6NHWXaj8ZnR+ElQgrQj1X+tfizWEGbCqur//rlO5B1WL1WP97aB+03VUq50ZZdCmBjuC3Un7DVGpWvuUBO9PijJEpgY5MG6mnZJcXpZMUV83v/dfdl3omHawLmcMN6R9yeu4zGT6AmwivkIEbcRE3XCEByxpVTZM23UBJnAj4XHQgF9bkYwX9MNPinj+DVjnNQ1Wvfqff/rUWOM1J6YZawArhloiS7WFBZFFUY152aIsQax5mrpF4MPfoc6sol52yPnfvKqTzfmCIlFVTbEgn79qglfIL7kW0Cd21UQ+v1hw+WSa0eSVrJB4OevjMltKoooitYVRpYAFDdeUCAr5ExP8QkHJNUCIiYl8axZcY7g0lXNMF3eq0khMcV3MObvPHNPTqUKUVfLz7qsuFhTwxsd5BYLiK4J+6ePjBbxiwRXGW40kjI4GPCU9DYDXBfHQ8L80Fw2vjrossLjd6upCg0EcXwZtuRvpTq2T3phFN2qN9Cw96aa5zvM+mMqyit6jOV3MRmhlKovyNYCY99gHqPgVvKnQX4VL/CSXuA/wjLwbPAOPgiHcRs3zQtwBS+jKeIeZDkyhU1pLpoNv0VV7yKpd0CWGsmg8rFkLa721yb0YmQX4tBKjUGh0CU1GgVvQJ8glNBqFL1vSVgQWc2q7szlnhUuRyaxPwzWsI+GshvQLMcVRkcXNgKAtTsGNkUXF0SNQan/TlvZg+PPfbsH05AYQQsn8HXoUdzlfx0qFBO4pcLmkZYWFT0y4du9+PPyNUgm0bapVH2ARNy4/7sUmy3uabPT/3U81LEjAqSXMrJk4QtxsRmnOZ49vvTDr6bHkgYFB3yNsthwtKEf4BuwfP9b60MN+pf6ooGPE1q733wdbAGc05VLYyxMMAxKFzwu/+pIhDHu1EHYxOmZPsJauxyyqsW24Wd3f+c66RS86XlwAOA/uUrAeQ4v45MuqkIPXZjSJ6MDDlaahnp1dR9lEiD2zdam61hasK9yFbXFXdxhrpUgrfDDC3OD8935CPvL0QyT6kD9O/W5zwuQphNP5eW4HVFMSXhg+0cA3C9JyoOKSPTZp24BfADUHSYDQk/6siOaEivX+2HRCSU0JszBsoolvEaQ71IzS/f91IA1gOwyDw8RgQhmcw5bhTmHYPslhhII3rvEJJrlf9I//zwH3ah+CibLYGEdAIrmc/gADnBh2+C5L2pwoDcO4KR5/MA6S4ZskYotFLHlXUruKLRIhtVunVpY/rruZoJcHDL4aygxZxqkJtVouED86fI90RbA+5HH6dHb0u+GF9FKGxWS10mtp1YRxqa1W76YEe9Zk/McWC6OcXhrxfpL11Olq0ivxFcJjvI/kSJiyETLxVnwWYWpK2tgnbZqaH3PG65qkX0zhbXgmtU0edbHCU3nKjXY8hbfkG4zsyvVklibsJaVUWkpWRCrIpR4KRfrozSmintKrpdWkBOnvNKnN9nfykb6G9hQ+kYvohjF7kVbrYnoVjSemhp5uNRoZJfRC1oLOak7t6X0jfyL39fauWvlU/qwzGhftWbnqsqvNIebqlxZ6e8zmBvdy+8b4FdlWRv4C6NXTO1ZmETLw6fhMwuJJQhbenZSQuWVlg7RDFSuVUwfUyqsRj3vEJwYJrHP8C6vfMAUKq1RqLZZnWMX5VqncKid0yc9w9AwjsySgm1FEPOoVnxgiyKVWq7Q4pVqUJpEuFq9wmAaDtlS7Yo+4RaGDEQtXAPX3w8upF1dRDdRVF6lXCTPiGcJVw/I3x1qfKeIbvmm68AJx2oUzZUo0nfYrWTtwIpkwK/w041fC/Le8b0nEpzwv/xsSiQ7iy4bHKThlCAWnPUSJG/1gjoKjzAUcHA0MeqJKuHCRI+MgkXWtZ5b3eLxeT9VnXjA0NBxB223aTTM9rbWP43eDuGtFwWdu1a4xptFEeqI4b13iXNfr8m2LiTdqdF9Lua4kOrpEV/6joJ+8JLpc92PIbbq+ahVhhj9DgF2n+dMEyga+oZrSO3cdH1dKldIu4+NbLhVcY7hQo0fbK3Vlum9ZeV/nILJ63f1QUOYRPaMz2hPjie6k0/UMG5uaK6M7Y5jztZ4Hs/fG0fmusTw9w8EtKyrI52TvWFaV7/MpfAbD9aA2fnPWoWc1FR07wCHdi3fDDcY3UrBdy4UMkB7nxTV3m0zdEykdbWYjtP0o+hYEg6tcff9ePTlVbJNbpTKrwgbNy3KkWVZ5trIEJtiFmK8hil6auTaXJJOhjrGXKXTqfH6CoZf4Q45MYZVj/ehZzb8MGzer7CKl0SE35QjLIg+FScS1SRQABNw/6EkgFHh1ZD5JJ743roR2SwLppwIZP8j/ytPD/v7zofP+I/9R8E0IrwE4sIARG0XCDk1EG1uFiFKFzxtxgPUNC4N9J99gHbjGRqwmXC9XrtilYRZsY0NILqsT19CKjey2hFSH9Kdb+vos6ZPYDrYbNxmVr9XmR/VjtUADS9sS24dIRRh7tG3xKT+OsuUWuJ/KrlLay5Su5cf2s4O3XpnLYVSOw13Mk/zjRrYfzKgaLC63M7ujB1F6lPFgz0CMHmlEtTmjBjErDg7eM5JTkmKWWuQ6HKdsfnFyz7lbrfZe+wZ3htyzhdNgxb0tsSOLy77erbSrVPZyQVeUO5cxGNrG8rKeCJhZPzM/2pmEe5tsfLz0a3XIWXRPQZuZrHQUxEw/nHqWQ6msbT3TFDNCseJl0zNrxnAb8BtCNxDCU9oXJS36V/7+8ceurxf28L9rf8QVcENlNIysLlgwn/wshT+w5fOYdd1/FZNqou+eEC1OaG+LgBdgq5K5CAcZ9T8nYxJf9ezooyKA/+dRcWtQf1AfZvzh+/sUNXdLmD2mVHuKRm13eFJN9Xj3+X1pxAU7qZH6h+3LDx92312+l7iRdneBMvGwAWO/CeTQmXlQMdhmUii7NDyoM0hmMrsjpt2HV+hpzEp6DZMyav6RZhYzS7a3hOjwNmn7SdEIPwCrY+ooLD0jihXNZDN1LB3DVHaJuX7dOxf+5MP3PtiJ/Mw9yOrCmc6EBmbTRWyhrZe9l33rj4lvv+7/B/tnbe4V2g/vfg8OVBCEBJ+W5b8mJ11FQ1Krtce2kcnZGiaVKcDJdxVdNT1W+KVj2b1Q+L+Mhnt3XgcLPUvDPwuOpao6Qp8QKYVEVAsZhfsq1vpJ/xCi7An3aPzH29/Iw7Zo/HQML0WFY+LkOBZOgSaCg/y9WDmVRVVQmVQlI/Hje4Je52bDz3eqoq4TX28i/bHIObdcV56Sd2THBMLpKov4+OOvb7/DGmyhhlFZv68IfvzDV5SwpIjI+ZX7ru6UZvCNSDE9vH0mna6nA439T1aJfsGbMexg5nziPDOYjdnM+rOEVcto6cwMCObQ2cEgmE3nBM3cWPDnSQb/vpPn6NB+0bS9Wh8YmTt4Ki91LP1eRySIvzlcTfLKPiM9Mlt8QhiOdNJsfqS35SVV846Q8snz5N8DfAvgoF/Jn8m85F+566lcwKWuvyLngH4nfwY0rTM5wCQw8ER6sRHkBCoa3fYmyYm3w3DbPzIQ5DzK3noDzySwiQIkljRRrqqkFuBy83EDi1BqDmiWOlb+4RW17ULtRE24qu0rshJCg3aidmU7a2j8aO9vBSPQEuhFwcd7N0g4jReFYYwgXuBrKyA1FPYFB8fewsbNc+ZJe/ZjbA6SUyejg43jxKtB7cvat44QenGcLRzsITxuPoSz7mDtwfUcYqd/SFReXxAO4QirQr8ImQfK58nPLt+CqWHI/uzCAoD/Do9N0KtIiy2X3n+PAdW+Z09f/zOo0hA8vmedtyzEyljgw2ncarW7Wu1q5dUyVK51It0utYbLeHk85vh/uocLMQuPGBbLSvGh165RbAatK7yH5jEsis6xWSvXcvsJr8y2RuUYqmie8G5aFzbTteT1oZVixOhhgTlszmxQmeNzyXmMGlIVLtszsFFvh7fHNuirB7ZJSFWManL+n7xUZpNhzhIGCobXrvGub50SUL1r+q3XqpQuCL82AUFGpSrUR6l0egDVxmEz6DfU1Y2rogkUQBnKNLnIKrLBC+AmxdL/GRt2rLRbFqWNhcllzgqz07JrtanxoGIp3AQvENuEmWmKDCXwDWWukww6Sjt0qzFbwjaZxurqxkybwrZgVuvaS52DknWZHuv82rXzVlTl4aRu7mKjrVzRHjRA6YtrystriuujDAS1K8ptxsXc7iRXyraurq3J17tkluP1P7vwgFQ5HLBpLPLsbUw9YRa/rHUh7HHXjgj9xCJ3UfRnPvX4b6Iiv8meRnpFQok/flYwu7UKJ7h6tKsIc/w5wvWgM5KWvXeKPvgV006YfT93lWCWsCzNTwh86twdKb09KTuPpezo6d2Zcoxqwp3pmbeuXWud9wq+qBeyAHdEEexGM3bfdydPy+SrofpdjlQlA2Ojp0v1LlDuDQiR+Wez64Zq2mAtPDXvDVap1U/FT6emqvwbxzvaMRj7QwKGE7X5Gk2+NnEIA7zLl89iZsFh5lxT08v/ZllVrLlQ/GwICH07UJTPvU3DI9DzWP2D1R+duoWMLl+ErJ2AD7TwGrgzY3K3Iq8Samp+pyQ2gIgi0sr42DmsY8xyP9R/STnVk0FpLHSpzK3MrwiMaAVFZ1JGAWuIQPq565sJwyMC6Y5WX7rEieHUFa8BKjAMsjMPyMcXra6oWF05rjjwPhLWvyisa8H0rf5b08JUKsrutKNSPRHIKE3dzEydphWu5HRws3lOXhl3KtD5RP/EGdj1jvs8f+MJzO0WU/VmySfBAVOSqfNw+Pnbx3+XUHht256aLgelPrye4pj69tF/vIcvr3RtKQsPcm/sip2f13VbRI6aEzVdj6/u5O68+vVMVMtt0HXvSVaFKatlvHV4gDxAKfv9cIs1yrzxrxJWli0jI++zquND2S2WL++0pi9xXYDXON/atLZk93M3yx1ij8LP8mfxUXash+X53s7TsmDt8IKCSfqke5I2aXd0awV9hQfnk5+PsO8S7SHtFu/K4dvBkK/UB59XQF+DZqBo5dp/ZYKCr30uF0Szh3UPGklhDxtAwy+PH9NLqI8mU0miGhhD9RruCw0p5rB+QfY7CDhkZKlfq7I+S/U3Qqo3pOWhjphD0GUyYFgLGQ2D+n0+YACH8d7YFzuqLC1SqpBPq1g4ipZD0GuZ01/3XcgnSVHl8pxJ/8PwvzaBwa7y7J5N142YA/VHFFLltcsrE1rZvxO66hxAQNCQ71WTy4sR8f3XdHXMHELQuyw2hICRKHK5TGWyM1gXJIcQl6Glsi7EYWnKw15o4r/PGwV+cux1KTb+T79AxNcE/IOqZmL5/upOecwJAP63EhsmAfjPclmErkhQXszuoxrYiH/9nB1ejc9H5srDsBrfX+y5Lj5r0lK8Zg0FyMYO/J5VQWDQp8D3qtr3n2k8V0aO8V2Mtwqtkddv+Hsgxg4AsMzRAA2MR2/wbVdUPP/pKgn8KF2/MQAa0IlYHTnE0FF84wCBQE0SOibcuIAEGNs+AR40AwjAEP4A4AWjcCMMhICZG+EAAy4SCDp+RBDQ8e6NSEAC9wgKOr6+MQC4YcH5BQI+rFv4KAgVW9c8HQeVqIcqebG/kCxDiRr7D8vAGO386RKc8IOC5R4Bw43eao0uFu3ct7Z3mLO6vugVQz2lWvv38zmyAk5Bu9FHQajWrVuned6Xg0pcfKW/fb+QLEPJqP6TZVAwevGOd3LJoP4wyTRbSdzDjd5UJKPDpKDxnfN9gSBZT6rTs+0rhnfzJBWw+3dnc3ExKztVH+oeQoZxN0ej5R0mFM3BZHHy+PeBD9mVIkuOIopxoXgCkUSmUGl0Rlg4k8XmRHB5fIFQJJZIZXKFUqXWaCOjomVsQgIhMhB/aFHo2DJIxhHa+jGzqYaXQzJ4sCxB+lVNdFwZ/Pq/XXgL2sG8cGa/iSgvq1wzrkPK7UkqtA4mAZRkLar1qa3dHZmT45U4muebAhKSLT+VZuxUQOZAZVgTua150YckGfziwUbAi6ZSsSK+idbUzwsOo6UsoWUUXTYAiUuW1aO8UV2UxuaS0VWMu2OhRQUNJYUr5mNdYSEz4erolNRW5Y7fZrbcInTWQHv6Mp1GowqEEFe+6JCU1ZDUgiRySKyOkHW3arVFRL6Slqk5Udze6h0j57yOWXjbLtb0qVYyAREau2uHMn0ajkY=') + format('woff2'), + url('iconfont.woff?t=1784185072587') format('woff'), + url('iconfont.ttf?t=1784185072587') format('truetype'), + url('iconfont.svg?t=1784185072587#iconfont') format('svg'); +} + +.iconfont { + font-family: 'iconfont' !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-changguan1:before { + content: '\e87b'; +} + +.icon-deng-dianliang:before { + content: '\e7b9'; +} + +.icon-deng-guanbi:before { + content: '\e7ba'; +} + +.icon-shuaxin:before { + content: '\e878'; +} + +.icon-guanbi1:before { + content: '\e879'; +} + +.icon-tianjia1:before { + content: '\e81f'; +} + +.icon-qiandaoma:before { + content: '\e857'; +} + +.icon-rili1:before { + content: '\e856'; +} + +.icon-fenzutitle:before { + content: '\e84c'; +} + +.icon-chenggong:before { + content: '\e84d'; +} + +.icon-cuncaogao:before { + content: '\e84e'; +} + +.icon-tupian:before { + content: '\e84f'; +} + +.icon-weixinzhifu1:before { + content: '\e850'; +} + +.icon-shanchu:before { + content: '\e851'; +} + +.icon-qianbao:before { + content: '\e852'; +} + +.icon-nan:before { + content: '\e853'; +} + +.icon-jinbi:before { + content: '\e854'; +} + +.icon-nv:before { + content: '\e855'; +} + +.icon-fenxiang1:before { + content: '\e847'; +} + +.icon-xiugai:before { + content: '\e848'; +} + +.icon-bianji:before { + content: '\e849'; +} + +.icon-gengduo1:before { + content: '\e84a'; +} + +.icon-shang:before { + content: '\e84b'; +} + +.icon-tianjia:before { + content: '\e840'; +} + +.icon-gengduo:before { + content: '\e841'; +} + +.icon-dianhua:before { + content: '\e842'; +} + +.icon-chexiao:before { + content: '\e843'; +} + +.icon-weixin:before { + content: '\e844'; +} + +.icon-fenzu:before { + content: '\e845'; +} + +.icon-dianming:before { + content: '\e846'; +} + +.icon-taocan:before { + content: '\e7fc'; +} + +.icon-tejia:before { + content: '\e7da'; +} + +.icon-manjian:before { + content: '\e7db'; +} + +.icon-piao:before { + content: '\e83f'; +} + +.icon-gou:before { + content: '\e63d'; +} + +.icon-fenxiang:before { + content: '\e83e'; +} + +.icon-liulanliang:before { + content: '\e83c'; +} + +.icon-rili:before { + content: '\e83d'; +} + +.icon-zuo:before { + content: '\e83b'; +} + +.icon-xia:before { + content: '\e83a'; +} + +.icon-dingwei:before { + content: '\e82d'; +} + +.icon-guanbi:before { + content: '\e82e'; +} + +.icon-shoucang:before { + content: '\e82f'; +} + +.icon-shijian:before { + content: '\e830'; +} + +.icon-changguan:before { + content: '\e831'; +} + +.icon-weixuanzhong:before { + content: '\e832'; +} + +.icon-xuanze:before { + content: '\e833'; +} + +.icon-xuanze-fill:before { + content: '\e834'; +} + +.icon-xuanzhong:before { + content: '\e835'; +} + +.icon-weixinzhifu:before { + content: '\e836'; +} + +.icon-you:before { + content: '\e837'; +} + +.icon-lianxiwomen:before { + content: '\e838'; +} + +.icon-yue:before { + content: '\e839'; +} diff --git a/src/assets/iconfont/iconfont.eot b/src/assets/iconfont/iconfont.eot new file mode 100644 index 0000000..f4c8805 Binary files /dev/null and b/src/assets/iconfont/iconfont.eot differ diff --git a/src/assets/iconfont/iconfont.js b/src/assets/iconfont/iconfont.js new file mode 100644 index 0000000..901bc13 --- /dev/null +++ b/src/assets/iconfont/iconfont.js @@ -0,0 +1,67 @@ +((window._iconfont_svg_string_4533260 = + ''), + ((a) => { + var c = (h = (h = document.getElementsByTagName('script'))[h.length - 1]).getAttribute( + 'data-injectcss', + ), + h = h.getAttribute('data-disable-injectsvg'); + if (!h) { + var l, + v, + o, + t, + i, + s = function (c, h) { + h.parentNode.insertBefore(c, h); + }; + if (c && !a.__iconfont__svg__cssinject__) { + a.__iconfont__svg__cssinject__ = !0; + try { + document.write( + '', + ); + } catch (c) { + console && console.log(c); + } + } + ((l = function () { + var c, + h = document.createElement('div'); + ((h.innerHTML = a._iconfont_svg_string_4533260), + (h = h.getElementsByTagName('svg')[0]) && + (h.setAttribute('aria-hidden', 'true'), + (h.style.position = 'absolute'), + (h.style.width = 0), + (h.style.height = 0), + (h.style.overflow = 'hidden'), + (h = h), + (c = document.body).firstChild ? s(h, c.firstChild) : c.appendChild(h))); + }), + document.addEventListener + ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState) + ? setTimeout(l, 0) + : ((v = function () { + (document.removeEventListener('DOMContentLoaded', v, !1), l()); + }), + document.addEventListener('DOMContentLoaded', v, !1)) + : document.attachEvent && + ((o = l), + (t = a.document), + (i = !1), + z(), + (t.onreadystatechange = function () { + 'complete' == t.readyState && ((t.onreadystatechange = null), m()); + }))); + } + function m() { + i || ((i = !0), o()); + } + function z() { + try { + t.documentElement.doScroll('left'); + } catch (c) { + return void setTimeout(z, 50); + } + m(); + } + })(window)); diff --git a/src/assets/iconfont/iconfont.json b/src/assets/iconfont/iconfont.json new file mode 100644 index 0000000..197c344 --- /dev/null +++ b/src/assets/iconfont/iconfont.json @@ -0,0 +1,380 @@ +{ + "id": "4533260", + "name": "改版-六个羽友", + "font_family": "iconfont", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "43518948", + "name": "场馆", + "font_class": "changguan1", + "unicode": "e87b", + "unicode_decimal": 59515 + }, + { + "icon_id": "31394058", + "name": "灯-点亮", + "font_class": "deng-dianliang", + "unicode": "e7b9", + "unicode_decimal": 59321 + }, + { + "icon_id": "31394059", + "name": "灯-关闭", + "font_class": "deng-guanbi", + "unicode": "e7ba", + "unicode_decimal": 59322 + }, + { + "icon_id": "43469992", + "name": "刷新", + "font_class": "shuaxin", + "unicode": "e878", + "unicode_decimal": 59512 + }, + { + "icon_id": "43469991", + "name": "关闭", + "font_class": "guanbi1", + "unicode": "e879", + "unicode_decimal": 59513 + }, + { + "icon_id": "39274830", + "name": "添加", + "font_class": "tianjia1", + "unicode": "e81f", + "unicode_decimal": 59423 + }, + { + "icon_id": "41007799", + "name": "签到码", + "font_class": "qiandaoma", + "unicode": "e857", + "unicode_decimal": 59479 + }, + { + "icon_id": "40996457", + "name": "日历", + "font_class": "rili1", + "unicode": "e856", + "unicode_decimal": 59478 + }, + { + "icon_id": "40820828", + "name": "分组title", + "font_class": "fenzutitle", + "unicode": "e84c", + "unicode_decimal": 59468 + }, + { + "icon_id": "40820822", + "name": "成功", + "font_class": "chenggong", + "unicode": "e84d", + "unicode_decimal": 59469 + }, + { + "icon_id": "40820826", + "name": "存草稿", + "font_class": "cuncaogao", + "unicode": "e84e", + "unicode_decimal": 59470 + }, + { + "icon_id": "40820824", + "name": "图片", + "font_class": "tupian", + "unicode": "e84f", + "unicode_decimal": 59471 + }, + { + "icon_id": "40820823", + "name": "微信支付", + "font_class": "weixinzhifu1", + "unicode": "e850", + "unicode_decimal": 59472 + }, + { + "icon_id": "40820821", + "name": "删除", + "font_class": "shanchu", + "unicode": "e851", + "unicode_decimal": 59473 + }, + { + "icon_id": "40820818", + "name": "钱包", + "font_class": "qianbao", + "unicode": "e852", + "unicode_decimal": 59474 + }, + { + "icon_id": "40820816", + "name": "男", + "font_class": "nan", + "unicode": "e853", + "unicode_decimal": 59475 + }, + { + "icon_id": "40820819", + "name": "金币", + "font_class": "jinbi", + "unicode": "e854", + "unicode_decimal": 59476 + }, + { + "icon_id": "40820817", + "name": "女", + "font_class": "nv", + "unicode": "e855", + "unicode_decimal": 59477 + }, + { + "icon_id": "40820832", + "name": "分享", + "font_class": "fenxiang1", + "unicode": "e847", + "unicode_decimal": 59463 + }, + { + "icon_id": "40820825", + "name": "修改", + "font_class": "xiugai", + "unicode": "e848", + "unicode_decimal": 59464 + }, + { + "icon_id": "40820830", + "name": "编辑", + "font_class": "bianji", + "unicode": "e849", + "unicode_decimal": 59465 + }, + { + "icon_id": "40820829", + "name": "更多1", + "font_class": "gengduo1", + "unicode": "e84a", + "unicode_decimal": 59466 + }, + { + "icon_id": "40820820", + "name": "上", + "font_class": "shang", + "unicode": "e84b", + "unicode_decimal": 59467 + }, + { + "icon_id": "40820837", + "name": "添加", + "font_class": "tianjia", + "unicode": "e840", + "unicode_decimal": 59456 + }, + { + "icon_id": "40820836", + "name": "更多", + "font_class": "gengduo", + "unicode": "e841", + "unicode_decimal": 59457 + }, + { + "icon_id": "40820835", + "name": "电话", + "font_class": "dianhua", + "unicode": "e842", + "unicode_decimal": 59458 + }, + { + "icon_id": "40820827", + "name": "撤销", + "font_class": "chexiao", + "unicode": "e843", + "unicode_decimal": 59459 + }, + { + "icon_id": "40820831", + "name": "微信", + "font_class": "weixin", + "unicode": "e844", + "unicode_decimal": 59460 + }, + { + "icon_id": "40820834", + "name": "分组", + "font_class": "fenzu", + "unicode": "e845", + "unicode_decimal": 59461 + }, + { + "icon_id": "40820833", + "name": "点名", + "font_class": "dianming", + "unicode": "e846", + "unicode_decimal": 59462 + }, + { + "icon_id": "36759285", + "name": "套餐", + "font_class": "taocan", + "unicode": "e7fc", + "unicode_decimal": 59388 + }, + { + "icon_id": "33120817", + "name": "特价", + "font_class": "tejia", + "unicode": "e7da", + "unicode_decimal": 59354 + }, + { + "icon_id": "33120818", + "name": "满减", + "font_class": "manjian", + "unicode": "e7db", + "unicode_decimal": 59355 + }, + { + "icon_id": "40351403", + "name": "票", + "font_class": "piao", + "unicode": "e83f", + "unicode_decimal": 59455 + }, + { + "icon_id": "848275", + "name": "勾", + "font_class": "gou", + "unicode": "e63d", + "unicode_decimal": 58941 + }, + { + "icon_id": "40248532", + "name": "分享", + "font_class": "fenxiang", + "unicode": "e83e", + "unicode_decimal": 59454 + }, + { + "icon_id": "40216674", + "name": "浏览量", + "font_class": "liulanliang", + "unicode": "e83c", + "unicode_decimal": 59452 + }, + { + "icon_id": "40216675", + "name": "日历", + "font_class": "rili", + "unicode": "e83d", + "unicode_decimal": 59453 + }, + { + "icon_id": "40191312", + "name": "左", + "font_class": "zuo", + "unicode": "e83b", + "unicode_decimal": 59451 + }, + { + "icon_id": "40191242", + "name": "下", + "font_class": "xia", + "unicode": "e83a", + "unicode_decimal": 59450 + }, + { + "icon_id": "40191226", + "name": "定位", + "font_class": "dingwei", + "unicode": "e82d", + "unicode_decimal": 59437 + }, + { + "icon_id": "40191227", + "name": "关闭", + "font_class": "guanbi", + "unicode": "e82e", + "unicode_decimal": 59438 + }, + { + "icon_id": "40191228", + "name": "收藏", + "font_class": "shoucang", + "unicode": "e82f", + "unicode_decimal": 59439 + }, + { + "icon_id": "40191229", + "name": "时间", + "font_class": "shijian", + "unicode": "e830", + "unicode_decimal": 59440 + }, + { + "icon_id": "40191230", + "name": "场馆", + "font_class": "changguan", + "unicode": "e831", + "unicode_decimal": 59441 + }, + { + "icon_id": "40191231", + "name": "未选中", + "font_class": "weixuanzhong", + "unicode": "e832", + "unicode_decimal": 59442 + }, + { + "icon_id": "40191232", + "name": "选择", + "font_class": "xuanze", + "unicode": "e833", + "unicode_decimal": 59443 + }, + { + "icon_id": "40191233", + "name": "选择-fill", + "font_class": "xuanze-fill", + "unicode": "e834", + "unicode_decimal": 59444 + }, + { + "icon_id": "40191234", + "name": "选中", + "font_class": "xuanzhong", + "unicode": "e835", + "unicode_decimal": 59445 + }, + { + "icon_id": "40191236", + "name": "微信支付", + "font_class": "weixinzhifu", + "unicode": "e836", + "unicode_decimal": 59446 + }, + { + "icon_id": "40191237", + "name": "右", + "font_class": "you", + "unicode": "e837", + "unicode_decimal": 59447 + }, + { + "icon_id": "40191238", + "name": "联系我们", + "font_class": "lianxiwomen", + "unicode": "e838", + "unicode_decimal": 59448 + }, + { + "icon_id": "40191239", + "name": "余额", + "font_class": "yue", + "unicode": "e839", + "unicode_decimal": 59449 + } + ] +} diff --git a/src/assets/iconfont/iconfont.svg b/src/assets/iconfont/iconfont.svg new file mode 100644 index 0000000..ab06c11 --- /dev/null +++ b/src/assets/iconfont/iconfont.svg @@ -0,0 +1,125 @@ + + + + Created by iconfont + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/iconfont/iconfont.ttf b/src/assets/iconfont/iconfont.ttf new file mode 100644 index 0000000..857fa5d Binary files /dev/null and b/src/assets/iconfont/iconfont.ttf differ diff --git a/src/assets/iconfont/iconfont.woff b/src/assets/iconfont/iconfont.woff new file mode 100644 index 0000000..ce4c85c Binary files /dev/null and b/src/assets/iconfont/iconfont.woff differ diff --git a/src/assets/iconfont/iconfont.woff2 b/src/assets/iconfont/iconfont.woff2 new file mode 100644 index 0000000..16c7b71 Binary files /dev/null and b/src/assets/iconfont/iconfont.woff2 differ diff --git a/src/assets/styles/index.less b/src/assets/styles/index.less index b5a78cc..05c6e52 100644 --- a/src/assets/styles/index.less +++ b/src/assets/styles/index.less @@ -55,3 +55,13 @@ a { ::-webkit-scrollbar-track { background: transparent; } + +.match-ellipsis-single { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.ant-layout .ant-layout-header { + padding-inline: 15px; +} diff --git a/src/hooks/useBasicLayout.ts b/src/hooks/useBasicLayout.ts index f0b511d..868035a 100644 --- a/src/hooks/useBasicLayout.ts +++ b/src/hooks/useBasicLayout.ts @@ -1,46 +1,99 @@ -import { h } from 'vue'; import { useRouter, useRoute } from 'vue-router'; -import { TrophyOutlined, WalletOutlined } from '@ant-design/icons-vue'; +import { routes } from '@/router/routes'; +import { generateMenu, type MenuItem } from '@/router/utils'; import { useBreadcrumb } from './useBreadcrumb'; +/** + * 基础布局管理 Hook + * + * @description + * 管理页面布局的核心逻辑。菜单、面包屑均由「路由表」派生,路由是唯一数据源: + * - 侧边栏菜单:generateMenu 根据路由的 meta.title / meta.icon 生成; + * - 菜单高亮 / 展开:根据当前路由匹配; + * - 面包屑:见 useBreadcrumb,取自 route.matched。 + */ export function useBasicLayout() { + // 路由实例,用于页面跳转 const router = useRouter(); + // 当前路由信息 const route = useRoute(); + // 获取面包屑数据 const { breadcrumbItems } = useBreadcrumb(); - const menuItems = [ - { - key: '/events', - icon: h(TrophyOutlined), - label: '我的赛事', - }, - // { TODO 本期先做隐藏 - // key: '/wallet', - // icon: h(WalletOutlined), - // label: '钱包管理', - // }, - ]; + /** + * 侧边栏菜单配置 + * 由主布局("/")的子路由生成,不再手写维护 + */ + const layoutChildren = routes.find((r) => r.path === '/' && r.children?.length)?.children ?? []; + const menuItems = generateMenu(layoutChildren); + /** + * 收集所有叶子菜单(可点击、可高亮的具体菜单项)的 key + * 父级 submenu 不参与 selected 高亮,只有叶子项才精确匹配当前路由 + */ + const leafMenuKeys = new Set(); + const collectLeafKeys = (items: MenuItem[]) => { + items.forEach((item) => { + if (item.children?.length) { + collectLeafKeys(item.children); + } else { + leafMenuKeys.add(item.key); + } + }); + }; + collectLeafKeys(menuItems); + + /** + * 获取当前激活的菜单 key(侧边栏高亮项) + * + * 优先级: + * 1. 当前路径命中某个叶子菜单 -> 高亮该菜单项 + * 2. 路由 meta.activeMenu 配置(详情页等路径不等于菜单 key 的场景) + * 3. 默认返回当前路由路径 + */ const getActiveMenuKey = () => { - const matchedRoutes = route.matched; - for (const r of matchedRoutes) { + if (leafMenuKeys.has(route.path)) { + return route.path; + } + for (const r of route.matched) { if (r.meta?.activeMenu) { return r.meta.activeMenu as string; } } - if (route.path.startsWith('/events')) return '/events'; - if (route.path.startsWith('/wallet')) return '/wallet'; return route.path; }; - const handleMenuClick = (info: any) => { + /** + * 获取需要展开的子菜单 keys + * 根据当前路径匹配所有「含子菜单且为当前路径祖先」的父级菜单 + */ + const getOpenKeys = () => { + return menuItems + .filter( + (item) => + item.children?.length && + (route.path === item.key || route.path.startsWith(`${item.key}/`)), + ) + .map((item) => item.key); + }; + + /** + * 菜单点击处理:跳转到对应路由 + */ + const handleMenuClick = (info: { key: string | number }) => { router.push(String(info.key)); }; return { + // 面包屑数据 breadcrumbItems, + // 菜单配置(由路由生成) menuItems, + // 获取激活菜单 key getActiveMenuKey, + // 获取展开菜单 keys + getOpenKeys, + // 菜单点击处理 handleMenuClick, }; } diff --git a/src/hooks/useBreadcrumb.ts b/src/hooks/useBreadcrumb.ts index ae55850..db5bd21 100644 --- a/src/hooks/useBreadcrumb.ts +++ b/src/hooks/useBreadcrumb.ts @@ -1,28 +1,39 @@ import { computed, h } from 'vue'; import { useRoute, useRouter } from 'vue-router'; +/** + * 面包屑管理 Hook + * + * @description + * 面包屑完全由当前路由派生:遍历 route.matched(命中的路由链,从父到子), + * 取每一层 meta.title 作为面包屑标题。层级关系写在路由表里(分组路由 + 子路由), + * 不再维护额外的菜单/映射表。 + * + * @returns breadcrumbs - 原始面包屑数据 + * @returns breadcrumbItems - 处理后的面包屑项(非末项渲染为可点击链接) + */ export function useBreadcrumb() { const route = useRoute(); const router = useRouter(); /** - * 根据当前路径和记录路径,计算拼接后的路径 + * 根据父级已拼接路径与当前路由记录的 path,计算该层的完整跳转路径 + * 兼容子路由使用相对路径或绝对路径两种写法 */ - const buildPath = (currentPath: string, recordPath: string): string => { + const buildPath = (parentPath: string, recordPath: string): string => { if (recordPath.startsWith('/')) { return recordPath; } - return currentPath ? `${currentPath}/${recordPath}`.replace(/\/+/g, '/') : `/${recordPath}`; + return `${parentPath}/${recordPath}`.replace(/\/+/g, '/'); }; const breadcrumbs = computed(() => { - const matched = route.matched; - const items: Array<{ title: string; path?: string }> = []; + const items: Array<{ title: string; path: string }> = []; let currentPath = ''; - for (const record of matched) { + for (const record of route.matched) { currentPath = buildPath(currentPath, record.path); - if (record.meta && record.meta.title) { + if (record.meta?.title) { items.push({ title: record.meta.title as string, path: currentPath, @@ -38,25 +49,19 @@ export function useBreadcrumb() { key: index, title: item.path && index !== breadcrumbs.value.length - 1 - ? h( + ? // 非末项:渲染为可点击链接 + h( 'a', { onClick: (e: Event) => { e.preventDefault(); - // 最保守方案:只有当前路径包含 /bracket/ 且目标路径是 /events/bracket 时才保留 query - // 也就是只有:/events/bracket/xxx → /events/bracket 时才保留 - const targetPath = item.path || '/'; - const isBracketChildPage = - route.path.includes('/bracket/') && targetPath === '/events/bracket'; - router.push({ - path: targetPath, - query: isBracketChildPage ? route.query : {}, - }); + router.push(item.path); }, }, item.title, ) - : item.title, + : // 末项:仅显示文本,表示当前所在位置 + item.title, })); }); diff --git a/src/layouts/BasicLayout.module.less b/src/layouts/BasicLayout.module.less index a3fc109..85d7741 100644 --- a/src/layouts/BasicLayout.module.less +++ b/src/layouts/BasicLayout.module.less @@ -1,57 +1,200 @@ .container { min-height: 100vh; + background: #f0f2f5; } .sider { - box-shadow: 2px 0 8px rgba(0, 0, 0, 0.08); + background: #fff !important; + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.06); + + :deep(.ant-menu) { + background: #fff; + border-right: 1px solid #f0f0f0; + } + + :deep(.ant-menu-item) { + margin: 0; + border-radius: 0; + color: #595959; + + &:hover { + background: #e6f7ff; + color: #1890ff; + } + } + + :deep(.ant-menu-item-selected) { + position: relative; + background: linear-gradient(90deg, #e6f7ff 0%, #bae7ff 100%) !important; + color: #1890ff !important; + font-weight: 500; + + // 左侧高亮指示条,标识当前选中的菜单项 + &::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 3px; + background: #1890ff; + } + } + + :deep(.ant-menu-submenu-title) { + margin: 0; + color: #595959; + + &:hover { + color: #1890ff; + } + } + + :deep(.ant-menu-submenu-selected > .ant-menu-submenu-title) { + color: #1890ff !important; + } } .logo { - height: 56px; + height: 64px; display: flex; align-items: center; - justify-content: center; + padding: 0 24px; overflow: hidden; - color: #fff; - background: rgba(255, 255, 255, 0.08); + color: #262626; + background: #fff; + border-bottom: 1px solid #f0f0f0; + gap: 12px; +} + +.logoIcon { + width: 32px; + height: 32px; + background: linear-gradient(135deg, #1890ff 0%, #722ed1 100%); + border-radius: 8px; + flex-shrink: 0; } .logoText { - font-size: 16px; + font-size: 18px; font-weight: 600; white-space: nowrap; + color: #262626; } .header { - background: #fff; - padding: 0 16px; + background: #ffffff !important; + padding: 0 24px; + display: flex; + align-items: center; + justify-content: space-between; + height: 64px; + border-bottom: 1px solid #f0f0f0; + + :deep(.ant-layout-header) { + background: #ffffff !important; + } +} + +.headerLeft { + display: flex; + align-items: center; + gap: 12px; +} + +.headerRight { display: flex; align-items: center; - box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); } .trigger { font-size: 18px; cursor: pointer; transition: color 0.3s; - padding: 0 12px; + padding: 8px; display: inline-flex; align-items: center; + color: #595959; + border-radius: 4px; &:hover { - color: #1677ff; + color: #1890ff; + background: #f5f5f5; } } -.content { - margin: 16px; - padding: 24px; - background: #fff; - border-radius: 8px; - min-height: 280px; +.refreshIcon { + font-size: 16px; + cursor: pointer; + padding: 8px; + display: inline-flex; + align-items: center; + color: #595959; + border-radius: 4px; + + &:hover { + color: #1890ff; + background: #f5f5f5; + } } -.footer { - text-align: center; - color: rgba(0, 0, 0, 0.45); +.breadcrumb { + margin-left: 12px; + color: #8c8c8c; + + :deep(.ant-breadcrumb-link) { + color: #8c8c8c; + + &:hover { + color: #1890ff; + } + } + + :deep(.ant-breadcrumb-last) { + color: #262626; + } +} + +.headerIcon { + font-size: 16px; + cursor: pointer; + color: #595959; + padding: 8px; + border-radius: 4px; + transition: all 0.3s; + + &:hover { + color: #1890ff; + background: #f5f5f5; + } +} + +.userInfo { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + padding: 6px 12px; + border-radius: 4px; + transition: background 0.3s; + + // &:hover { + // background: #f5f5f5; + // } +} + +.userName { + color: #262626; + font-size: 14px; +} + +.content { + margin: 0; + background: #f0f2f5; + min-height: calc(100vh - 64px); + overflow: auto; +} + +.pageContent { + padding: 15px; } diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx index f01ba2a..211e2b3 100644 --- a/src/layouts/BasicLayout.tsx +++ b/src/layouts/BasicLayout.tsx @@ -1,69 +1,171 @@ import { computed, defineComponent, ref } from 'vue'; -import { useRouter, useRoute, RouterView } from 'vue-router'; -import { Layout, Menu } from 'ant-design-vue'; -import type { MenuProps } from 'ant-design-vue'; +import { Layout, Menu, Breadcrumb, Modal, Avatar, Space } from 'ant-design-vue'; +import { RouterView, useRouter } from 'vue-router'; import { - DashboardOutlined, - InfoCircleOutlined, MenuFoldOutlined, MenuUnfoldOutlined, + ReloadOutlined, + SearchOutlined, + BellOutlined, + FullscreenOutlined, + UserOutlined, } from '@ant-design/icons-vue'; +import { useBasicLayout } from '../hooks/useBasicLayout'; +import { auth } from '@/hooks/useAuth'; import styles from './BasicLayout.module.less'; -const { Header, Sider, Content, Footer } = Layout; +// 解构 Layout 组件 +const { Header, Sider, Content } = Layout; /** - * 基础布局:左侧导航 + 顶部栏 + 内容区 + 页脚 + * 基础布局组件 + * + * @description + * CPMS 运营平台的主布局结构,包含: + * - 左侧侧边栏:Logo + 导航菜单 + * - 顶部 Header:菜单折叠按钮 + 面包屑 + 用户信息 + * - 内容区域:路由视图渲染区域 + * + * @example + * ```typescript + * // 在路由中使用 + * { + * path: '/', + * component: BasicLayout, + * children: [...] + * } + * ``` */ export default defineComponent({ name: 'BasicLayout', setup() { - const route = useRoute(); + // 使用布局 Hook 获取菜单和面包屑数据 + const { breadcrumbItems, menuItems, getActiveMenuKey, getOpenKeys, handleMenuClick } = + useBasicLayout(); + + // 路由实例,用于退出登录后跳转到登录页 const router = useRouter(); + + /** + * 侧边栏折叠状态 + * true: 折叠状态(只显示图标) + * false: 展开状态(显示图标+文字) + */ const collapsed = ref(false); - const selectedKeys = computed(() => [route.path]); - - const menuItems: MenuProps['items'] = [ - { key: '/dashboard', icon: () => , label: '工作台' }, - { key: '/about', icon: () => , label: '关于' }, - ]; - - const handleMenuClick: MenuProps['onClick'] = ({ key }) => { - router.push(key as string); - }; - + /** + * 切换侧边栏折叠/展开状态 + */ const toggleCollapsed = () => { collapsed.value = !collapsed.value; }; + /** + * 退出登录:二次确认后跳转到登录页(不调用接口) + * - 点击用户名弹出确认弹框 + * - 确定 -> 跳转登录页 + * - 取消 -> 关闭弹框,不做任何处理 + */ + const handleLogout = () => { + Modal.confirm({ + title: '确认退出', + content: '确定要退出当前账号吗?', + okText: '确定', + cancelText: '取消', + onOk: () => { + // 清除本地登录态(不调用接口),否则路由守卫会把 /login 重定向回首页 + auth.logout(); + router.replace('/login'); + }, + }); + }; + return () => ( - + {/* 左侧侧边栏 */} + + {/* Logo 区域 */}
- {collapsed.value ? 'CPMS' : 'CPMS 运营平台'} +
+ {collapsed.value ? 'CPMS' : 'CPMS运营平台'}
+ + {/* 导航菜单 */} + {/* 右侧内容区域 */} -
- - {collapsed.value ? : } - + {/* 顶部 Header */} +
+
+ {/* 折叠/展开按钮 */} + + {collapsed.value ? : } + + + {/* 刷新按钮 */} + {/* + + */} + + {/* 面包屑导航 */} + {breadcrumbItems.value.length > 0 && ( + + {breadcrumbItems.value.map((item) => ( + {item.title} + ))} + + )} +
+ + {/* Header 右侧:用户信息 */} +
+ + {/* 搜索图标 */} + {/* + + */} + + {/* 通知图标 */} + {/* + + */} + + {/* 全屏图标 */} + {/* + + */} + + {/* 用户信息:点击弹出退出登录确认 */} +
+ } /> + 用户名 +
+
+
+ {/* 内容区域 */} - +
+ {/* 路由视图:根据当前路由渲染对应页面 */} + +
- -
CPMS 运营平台 ©{new Date().getFullYear()}
); diff --git a/src/main.ts b/src/main.ts index 2ed0b1f..ec0c72d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,7 @@ import 'ant-design-vue/dist/reset.css'; import App from './App'; import router from './router'; import './assets/styles/index.less'; +import './assets/iconfont/iconfont.css'; const app = createApp(App); diff --git a/src/router/index.ts b/src/router/index.ts index ae9e49e..1596db4 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,9 +1,9 @@ -import { createRouter, createWebHistory } from 'vue-router'; +import { createRouter, createWebHashHistory } from 'vue-router'; import { routes } from './routes'; import { auth } from '@/hooks/useAuth'; const router = createRouter({ - history: createWebHistory(import.meta.env.VITE_BASE_URL || '/'), + history: createWebHashHistory(import.meta.env.VITE_BASE_URL || '/'), routes, scrollBehavior: () => ({ left: 0, top: 0 }), }); diff --git a/src/router/routes.ts b/src/router/routes.ts index af168c7..cbf8ae8 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -1,40 +1,23 @@ import type { RouteRecordRaw } from 'vue-router'; +import { + BarChartOutlined, + UserOutlined, + StarOutlined, + FileTextOutlined, + NotificationOutlined, + BorderTopOutlined, + BoxPlotOutlined, + SafetyCertificateOutlined, +} from '@ant-design/icons-vue'; import { createRoute } from './utils'; -/** - * ====== 业务路由模块 ====== - * 实际项目中按模块拆分,如 eventsRoutes、walletRoutes 等, - * 然后在下方 routes 数组的布局 children 中引入。 - * 参考示例(嵌套路由): - * - * const eventsRoutes = { - * path: '/events', - * name: 'EventsModule', - * redirect: '/events/list', - * meta: { title: '我的赛事', activeMenu: '/events' }, - * children: [ - * createRoute('list', () => import('@/pages/events/list'), { title: '赛事列表' }), - * { - * path: 'bracket', - * name: 'BracketPage', - * component: () => import('@/pages/events/bracket'), - * meta: { title: '赛事详情' }, - * children: [ - * createRoute('player-management', () => import('@/pages/events/bracket/player-management'), { title: '选手管理' }), - * createRoute('match-result', () => import('@/pages/events/bracket/match-result'), { title: '比赛结果' }), - * ], - * }, - * ], - * }; - */ - export const routes: RouteRecordRaw[] = [ // ── 登录页(不经过布局)── { path: '/login', name: 'Login', component: () => import('@/pages/login'), - meta: { title: '登录' }, + meta: { title: '登录', hideInMenu: true }, }, // ── 主布局(需要登录)── @@ -42,17 +25,149 @@ export const routes: RouteRecordRaw[] = [ path: '/', component: () => import('@/layouts/BasicLayout'), children: [ - // 业务模块路由在此添加,例如:eventsRoutes, walletRoutes - + // 数据分析 createRoute('/dashboard', () => import('@/views/Dashboard'), { - title: '工作台', - icon: 'DashboardOutlined', + title: '数据分析', + icon: BarChartOutlined, }), + + // 场馆列表(分组) + { + path: '/venue', + name: 'Venue', + redirect: '/venue/list', + meta: { + title: '场馆列表', + icon: 'icon-changguan1', + activeMenu: '/venue', + }, + children: [ + createRoute('/venue/list', () => import('@/views/venue/list'), { + title: '馆客多', + }), + createRoute('/venue/records', () => import('@/views/venue/records'), { + title: '收录', + }), + ], + }, + + // C端用户列表 + createRoute('/users', () => import('@/views/Dashboard'), { + title: 'C端用户列表', + icon: UserOutlined, + }), + // 点评管理 + createRoute('/reviews', () => import('@/views/Dashboard'), { + title: '点评管理', + icon: StarOutlined, + }), + // 广告位管理 + createRoute('/ads', () => import('@/views/Dashboard'), { + title: '广告位管理', + icon: NotificationOutlined, + }), + // 资讯管理 + createRoute('/news', () => import('@/views/Dashboard'), { + title: '资讯管理', + icon: FileTextOutlined, + }), + // 申请入驻管理 + createRoute('/apply', () => import('@/views/Dashboard'), { + title: '申请入驻管理', + icon: UserOutlined, + }), + // 反馈管理 + createRoute('/feedback', () => import('@/views/Dashboard'), { + title: '反馈管理', + icon: BoxPlotOutlined, + }), + + // 活动管理(分组) + { + path: '/activity', + name: 'Activity', + redirect: '/activity/list', + meta: { title: '活动管理', icon: 'icon-rili', activeMenu: '/activity' }, + children: [ + createRoute('/activity/list', () => import('@/views/Dashboard'), { + title: '活动列表', + }), + createRoute('/activity/categories', () => import('@/views/Dashboard'), { + title: '活动分类', + }), + ], + }, + + // 场馆设置(分组) + { + path: '/venue-settings', + name: 'VenueSettings', + redirect: '/venue-settings/basic', + meta: { + title: '场馆设置', + icon: BorderTopOutlined, + activeMenu: '/venue-settings', + }, + children: [ + createRoute('/venue-settings/basic', () => import('@/views/Dashboard'), { + title: '基本设置', + }), + createRoute('/venue-settings/service', () => import('@/views/Dashboard'), { + title: '服务设置', + }), + ], + }, + + // 钱包管理(分组) + { + path: '/wallet', + name: 'Wallet', + redirect: '/wallet/records', + meta: { title: '钱包管理', icon: 'icon-qianbao', activeMenu: '/wallet' }, + children: [ + createRoute('/wallet/records', () => import('@/views/Dashboard'), { + title: '交易记录', + }), + createRoute('/wallet/withdraw', () => import('@/views/Dashboard'), { + title: '提现管理', + }), + createRoute('/wallet/recharge', () => import('@/views/Dashboard'), { + title: '充值管理', + }), + ], + }, + + // 系统管理(分组) + { + path: '/system', + name: 'System', + redirect: '/system/users', + meta: { + title: '系统管理', + icon: SafetyCertificateOutlined, + activeMenu: '/system', + }, + children: [ + createRoute('/system/users', () => import('@/views/Dashboard'), { + title: '用户管理', + }), + createRoute('/system/roles', () => import('@/views/Dashboard'), { + title: '角色管理', + }), + createRoute('/system/permissions', () => import('@/views/Dashboard'), { + title: '权限管理', + }), + createRoute('/system/logs', () => import('@/views/Dashboard'), { + title: '操作日志', + }), + ], + }, + + // 关于(不在侧边栏显示) createRoute('/about', () => import('@/views/About'), { title: '关于', - icon: 'InfoCircleOutlined', + hideInMenu: true, }), - // 默认重定向 { path: '/', redirect: '/dashboard' }, ], diff --git a/src/router/utils.ts b/src/router/utils.ts index 03dc5a2..e008d07 100644 --- a/src/router/utils.ts +++ b/src/router/utils.ts @@ -1,20 +1,25 @@ +import { h, type Component, type VNode } from 'vue'; import type { RouteRecordRaw } from 'vue-router'; /** 路由 meta 类型 */ export type AppMeta = { + /** 页面 / 菜单标题,同时用于面包屑 */ title: string; - icon?: string; + /** + * 菜单图标(仅一级菜单需要),支持两种写法: + * - iconfont 的 class 名,如 'icon-changguan1' + * - 组件,如 ant-design 图标 BarChartOutlined + */ + icon?: Component | string; + /** 是否在侧边栏菜单中隐藏(详情页等) */ hideInMenu?: boolean; + /** 高亮的菜单 key(路径不等于菜单 key 的场景,如详情页) */ activeMenu?: string; }; /** * 快速创建路由记录 * 根据路径自动生成 PascalCase 路由 name - * - * @example - * createRoute('list', () => import('@/pages/home'), { title: '赛事列表' }) - * // => { path: 'list', name: 'List', meta: {...}, component: ... } */ export function createRoute( path: string, @@ -38,3 +43,47 @@ export function createRoute( children, }; } + +/** 侧边栏菜单项(ant-design-vue Menu 的 items 结构) */ +export interface MenuItem { + key: string; + label: string; + icon?: VNode; + children?: MenuItem[]; +} + +/** + * 根据路由表生成侧边栏菜单 + * + * @description + * 菜单完全由路由派生,路由是「菜单 / 面包屑」唯一的数据源: + * - 仅收录带 meta.title 且未标记 hideInMenu 的路由; + * - 有子路由的生成子菜单(父级本身不跳转,点击展开); + * - 图标取自 meta.icon。 + * + * @param routes 需要遍历的路由数组(通常传入主布局的 children) + */ +export function generateMenu(routes: RouteRecordRaw[] = []): MenuItem[] { + const items: MenuItem[] = []; + + for (const route of routes) { + const meta = route.meta as AppMeta | undefined; + if (!meta?.title || meta.hideInMenu) { + continue; + } + + const children = generateMenu(route.children); + const item: MenuItem = { key: route.path, label: meta.title }; + if (meta.icon) { + // 字符串 -> iconfont 图标();组件 -> 直接渲染 + item.icon = + typeof meta.icon === 'string' ? h('i', { class: ['iconfont', meta.icon] }) : h(meta.icon); + } + if (children.length) { + item.children = children; + } + items.push(item); + } + + return items; +} diff --git a/src/types/index.ts b/src/types/index.ts index 9182e7d..cf58fa0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -27,7 +27,8 @@ export interface SelectOption { declare module 'vue-router' { interface RouteMeta { title?: string; - icon?: string; + /** 菜单图标:iconfont 的 class 名(字符串) 或 组件(如 antd 图标) */ + icon?: import('vue').Component | string; /** 是否需要登录 */ requiresAuth?: boolean; /** 是否在菜单中隐藏 */ diff --git a/src/views/Dashboard.module.less b/src/views/Dashboard.module.less deleted file mode 100644 index e816330..0000000 --- a/src/views/Dashboard.module.less +++ /dev/null @@ -1,27 +0,0 @@ -.container { - // 容器 -} - -.title { - margin-top: 0; - margin-bottom: 24px; - font-size: 20px; - font-weight: 600; -} - -.panel { - margin-top: 16px; - - ul { - margin: 12px 0 0; - padding-left: 20px; - line-height: 2; - } - - code { - padding: 2px 8px; - background: #f5f5f5; - border-radius: 4px; - font-size: 13px; - } -} diff --git a/src/views/Dashboard.tsx b/src/views/Dashboard.tsx deleted file mode 100644 index cf3c62a..0000000 --- a/src/views/Dashboard.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { defineComponent } from 'vue'; -import { Card, Col, Row, Statistic } from 'ant-design-vue'; -import styles from './Dashboard.module.less'; - -interface StatItem { - title: string; - value: number; - suffix: string; - precision?: number; -} - -/** - * 工作台 / 仪表盘 - */ -export default defineComponent({ - name: 'Dashboard', - setup() { - const stats: StatItem[] = [ - { title: '今日访问', value: 1280, suffix: '次' }, - { title: '活跃用户', value: 368, suffix: '人' }, - { title: '订单总数', value: 9920, suffix: '单' }, - { title: '处理率', value: 96.8, suffix: '%', precision: 1 }, - ]; - - return () => ( -
-

工作台

- - - {stats.map((item) => ( - - - - - - ))} - - - -

- 欢迎使用 CPMS 运营平台。这是一个基于 Vue 3 + Vite + TypeScript + Ant Design Vue - 搭建的中后台管理项目骨架,采用 TSX 写法,你可以在此基础上继续开发业务功能。 -

-
    -
  • - 开发命令:npm run dev -
  • -
  • - 构建(测试环境):npm run build:test -
  • -
  • - 构建(生产环境):npm run build:prod -
  • -
  • - 代码检查:npm run lint -
  • -
-
-
- ); - }, -}); diff --git a/src/views/Dashboard/components/AddVenueModal.tsx b/src/views/Dashboard/components/AddVenueModal.tsx new file mode 100644 index 0000000..abfc62b --- /dev/null +++ b/src/views/Dashboard/components/AddVenueModal.tsx @@ -0,0 +1,184 @@ +import { defineComponent, reactive, ref, watch } from 'vue'; +import { Modal, Form, Input, Select, message } from 'ant-design-vue'; +import type { Rule } from 'ant-design-vue/es/form'; +import { saveVenue, statusMap, type VenueSaveParams } from '../model'; + +interface AddVenueModalProps { + open: boolean; + onClose: () => void; + onSuccess?: () => void; +} + +/** + * 新增场馆弹框 + */ +export default defineComponent({ + name: 'AddVenueModal', + props: { + open: { + type: Boolean, + default: false, + }, + onClose: { + type: Function, + required: true, + }, + onSuccess: { + type: Function, + default: undefined, + }, + }, + setup(props: AddVenueModalProps) { + const labelCol = { span: 5 }; + const wrapperCol = { span: 18 }; + + // 表单实例 + const formRef = ref(); + // 提交中状态 + const submitting = ref(false); + + // 表单数据初始值 + const getInitialForm = (): VenueSaveParams => ({ + name: '', + type: '', + contact: '', + phone: '', + address: '', + status: 'active', + }); + const formState = reactive(getInitialForm()); + + // 校验规则 + const rules: Record = { + name: [{ required: true, message: '请输入场馆名称', trigger: 'blur' }], + type: [{ required: true, message: '请输入场馆类型', trigger: 'blur' }], + contact: [{ required: true, message: '请输入联系人', trigger: 'blur' }], + phone: [ + { required: true, message: '请输入联系电话', trigger: 'blur' }, + { + pattern: /^1[3-9]\d{9}$/, + message: '手机号格式不正确', + trigger: 'blur', + }, + ], + status: [{ required: true, message: '请选择状态', trigger: 'change' }], + }; + + // 打开时重置表单 + watch( + () => props.open, + (open) => { + if (open) { + Object.assign(formState, getInitialForm()); + formRef.value?.clearValidate?.(); + } + }, + ); + + /** 确定:校验 -> 提交 -> 成功回调 */ + const handleOk = async () => { + try { + await formRef.value?.validate(); + } catch { + // 校验不通过,直接返回 + return; + } + + submitting.value = true; + try { + await saveVenue({ ...formState }); + message.success('新增成功'); + props.onSuccess?.(); + props.onClose(); + } catch { + message.error('新增失败,请重试'); + } finally { + submitting.value = false; + } + }; + + return () => ( + props.onClose()} + > +
+ + { + formState.name = e.target.value; + }} + placeholder="请输入场馆名称" + /> + + + { + formState.type = e.target.value; + }} + placeholder="请输入场馆类型" + /> + + + { + formState.contact = e.target.value; + }} + placeholder="请输入联系人" + /> + + + { + formState.phone = e.target.value; + }} + placeholder="请输入联系电话" + /> + + + + + + { + formState.address = e.target.value; + }} + placeholder="请输入地址" + rows={3} + /> + +
+
+ ); + }, +}); diff --git a/src/views/Dashboard/index.module.less b/src/views/Dashboard/index.module.less new file mode 100644 index 0000000..2f71d60 --- /dev/null +++ b/src/views/Dashboard/index.module.less @@ -0,0 +1,86 @@ +.container { + display: flex; + flex-direction: column; + height: calc(100vh - 64px - 30px); +} + +.filterCard { + flex-shrink: 0; + background: #fff; + border: 1px solid #f0f0f0; + border-radius: 8px; + margin-bottom: 16px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); + + :global(.ant-card-body) { + padding: 20px 24px; + } +} + +.filterRow { + :global(.ant-form) { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + row-gap: 12px; + column-gap: 16px; + } + + :global(.ant-form-item) { + flex-shrink: 0; + margin-right: 0; + margin-bottom: 0; + } +} + +.tableCard { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + background: #fff; + border: 1px solid #f0f0f0; + border-radius: 8px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); + + :global(.ant-card-body) { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + padding: 0; + } +} + +.tableBody { + flex: 1; + min-height: 0; + overflow: auto; +} + +.tableHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 24px; + border-bottom: 1px solid #f0f0f0; +} + +.tableFooter { + flex-shrink: 0; + padding: 16px 24px; + border-top: 1px solid #f0f0f0; + display: flex; + justify-content: flex-end; +} + +.pagination { + display: flex; + align-items: center; + gap: 16px; +} + +.paginationText { + color: #595959; + font-size: 14px; +} diff --git a/src/views/Dashboard/index.tsx b/src/views/Dashboard/index.tsx new file mode 100644 index 0000000..b8ab966 --- /dev/null +++ b/src/views/Dashboard/index.tsx @@ -0,0 +1,247 @@ +import { defineComponent } from 'vue'; +import { + Card, + Table, + Input, + Select, + Button, + Space, + Tag, + Popconfirm, + Tooltip, + Pagination, + Form, +} from 'ant-design-vue'; +import { + SearchOutlined, + EditOutlined, + DeleteOutlined, + PlusOutlined, + EyeOutlined, +} from '@ant-design/icons-vue'; +import { useDashboardModel, type VenueItem } from './model'; +import AddVenueModal from './components/AddVenueModal'; +import styles from './index.module.less'; + +/** + * 数据分析 / 场馆列表 + */ +export default defineComponent({ + name: 'Dashboard', + setup() { + const { + tableData, + currentPage, + formRef, + formState, + statusMap, + addModalOpen, + openAddModal, + closeAddModal, + handleReset, + fetchVenueList, + } = useDashboardModel(); + + const columns = [ + { + title: '序号', + dataIndex: 'id', + key: 'id', + width: 100, + }, + { + title: '场馆名称', + dataIndex: 'name', + key: 'name', + width: 150, + }, + { + title: '场馆类型', + dataIndex: 'type', + key: 'type', + width: 120, + }, + { + title: '联系人', + dataIndex: 'contact', + key: 'contact', + width: 120, + }, + { + title: '联系电话', + dataIndex: 'phone', + key: 'phone', + width: 140, + }, + { + title: '地址', + dataIndex: 'address', + key: 'address', + // 开启文本溢出省略 + 悬浮 tooltip + customRender: ({ record }: { record: VenueItem }) => ( + +
{record.address}
+
+ ), + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + customRender: ({ record }: { record: VenueItem }) => { + const status = statusMap[record.status] || { + color: 'default', + text: '未知', + }; + return {status.text}; + }, + }, + { + title: '', + key: 'avatar', + width: 80, + }, + { + title: '操作', + key: 'action', + width: 230, + fixed: 'right' as const, + customRender: () => ( + + + {/* */} + + + + + + ), + }, + ]; + + return () => ( +
+ +
+
+ + { + formState.name = e.target.value; + }} + placeholder="请输入场馆名称" + prefix={} + style={{ width: '200px' }} + /> + + + { + formState.status = val; + }} + placeholder="请选择状态" + style={{ width: '140px' }} + allowClear={false} + > + {Object.entries(statusMap).map(([value, { text }]) => ( + + {text} + + ))} + + + + { + formState.contact = e.target.value; + }} + placeholder="请输入联系人" + style={{ width: '150px' }} + /> + + + { + formState.phone = e.target.value; + }} + placeholder="请输入联系电话" + style={{ width: '150px' }} + /> + + + + + + + +
+
+
+ + +
+ {/* 场馆列表 */} + +
+
+ + + +
+ { + currentPage.value = page; + }} + total={500} + showSizeChanger + showQuickJumper + showTotal={(total: number) => `共 ${total} 条`} + /> +
+ + + {/* 新增场馆弹框 */} + + + ); + }, +}); diff --git a/src/views/Dashboard/model/index.ts b/src/views/Dashboard/model/index.ts new file mode 100644 index 0000000..4d3b06d --- /dev/null +++ b/src/views/Dashboard/model/index.ts @@ -0,0 +1,2 @@ +export * from './useDashboardModel'; +export * from './services'; diff --git a/src/views/Dashboard/model/services.ts b/src/views/Dashboard/model/services.ts new file mode 100644 index 0000000..912f4ce --- /dev/null +++ b/src/views/Dashboard/model/services.ts @@ -0,0 +1,119 @@ +import { get, post } from '@/utils/request'; + +/** 场馆列表行数据 */ +export interface VenueItem { + key: string; + id: string; + name: string; + type: string; + contact: string; + status: string; + phone: string; + address: string; + createTime: string; +} + +/** 场馆状态映射:状态值 -> 颜色 / 文案 */ +export const statusMap: Record = { + active: { color: 'green', text: '正常' }, + inactive: { color: 'red', text: '停用' }, + pending: { color: 'orange', text: '待审' }, +}; + +/** 场馆列表查询参数 */ +export interface VenueListParams { + page: number; + size: number; + name?: string; + type?: string; + status?: string; + contact?: string; + phone?: string; +} + +const venueListApi = '/venue/page'; + +/** + * 场馆列表接口 + * @description 后端接口就绪后,在 useDashboardModel 中启用替换 mock 数据 + */ +export const getVenueList = (params: VenueListParams) => { + return get(venueListApi, params); +}; + +/** 新增场馆参数 */ +export interface VenueSaveParams { + name: string; + type: string; + contact: string; + phone: string; + address: string; + status: string; +} + +const venueSaveApi = '/venue/save'; + +/** 新增场馆接口 */ +export const saveVenue = (params: VenueSaveParams) => { + return post(venueSaveApi, params); +}; + +/** 临时 mock 数据(接口对接前占位展示) */ +export const mockVenueList: VenueItem[] = [ + { + key: '1', + id: 'V001', + name: '健身中心', + type: '健身馆', + contact: '张三', + status: 'active', + phone: '13999999999', + address: '北京市朝阳区xxx路xxx号', + createTime: '2024-01-15 10:30:00', + }, + { + key: '2', + id: 'V002', + name: '篮球公园', + type: '篮球馆', + contact: '李四', + status: 'active', + phone: '13999999999', + address: '上海市浦东新区xxx路xxx号', + createTime: '2024-01-16 14:20:00', + }, + { + key: '3', + id: 'V003', + name: '游泳馆', + type: '游泳馆', + contact: '王五', + status: 'inactive', + phone: '13999999999', + address: '广州市天河区xxx路xxx号', + createTime: '2024-01-17 09:15:00', + }, + { + key: '4', + id: 'V004', + name: '羽毛球馆', + type: '羽毛球馆', + contact: '马六', + status: 'active', + phone: '13999999999', + address: '深圳市南山区xxx路xxx号', + createTime: '2024-01-18 16:45:00', + }, + { + key: '5', + id: 'V005', + name: '网球中心', + type: '网球馆', + contact: '冯七', + status: 'pending', + phone: '13999999999', + address: + '杭州市西湖区xxx路xxx号杭州市西湖区xxx路xxx号杭州市西湖区xxx路xxx号杭州市西湖区xxx路xxx号11', + createTime: '2024-01-19 11:00:00', + }, +]; diff --git a/src/views/Dashboard/model/useDashboardModel.ts b/src/views/Dashboard/model/useDashboardModel.ts new file mode 100644 index 0000000..a032d5f --- /dev/null +++ b/src/views/Dashboard/model/useDashboardModel.ts @@ -0,0 +1,83 @@ +import { reactive, ref } from 'vue'; +import { getVenueList, mockVenueList, statusMap, type VenueItem } from './services'; + +/** 筛选表单字段 */ +export interface VenueFilterForm { + name: string; + type?: string; + status?: string; + contact: string; + phone: string; +} + +/** + * Dashboard(场馆列表)页面业务逻辑 + * + * @description + * 页面的状态、数据、事件处理都集中在这里,index.tsx 只负责渲染。 + */ +export function useDashboardModel() { + // 表格数据(暂用 mock,接口就绪后由 fetchVenueList 填充) + const tableData = ref(mockVenueList); + // 当前页码 + const currentPage = ref(1); + // 筛选表单实例 + const formRef = ref(); + // 筛选表单数据 + const formState = reactive({ + name: '', + type: undefined, + status: undefined, + contact: '', + phone: '', + }); + + // 新增场馆弹框显隐 + const addModalOpen = ref(false); + /** 打开新增弹框 */ + const openAddModal = () => { + addModalOpen.value = true; + }; + /** 关闭新增弹框 */ + const closeAddModal = () => { + addModalOpen.value = false; + }; + + /** 重置筛选表单 */ + const handleReset = () => { + formRef.value?.resetFields(); + Object.assign(formState, { + name: '', + type: undefined, + status: undefined, + contact: '', + phone: '', + }); + }; + + /** + * 查询场馆列表 + * @description 后端接口就绪后启用,替换默认的 mock 数据 + */ + const fetchVenueList = async () => { + const res: any = await getVenueList({ + page: currentPage.value, + size: 10, + ...formState, + }); + tableData.value = res?.data?.list ?? []; + }; + + return { + tableData, + currentPage, + formRef, + formState, + statusMap, + addModalOpen, + openAddModal, + closeAddModal, + handleReset, + fetchVenueList, + }; +} diff --git a/src/views/venue/list/index.module.less b/src/views/venue/list/index.module.less new file mode 100644 index 0000000..d8812ec --- /dev/null +++ b/src/views/venue/list/index.module.less @@ -0,0 +1,67 @@ +.container { + display: flex; + flex-direction: column; + height: calc(100vh - 64px - 30px); +} + +.filterCard { + flex-shrink: 0; + background: #fff; + border: 1px solid #f0f0f0; + border-radius: 8px; + margin-bottom: 16px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); + + :global(.ant-card-body) { + padding: 20px 24px; + } +} + +.filterRow { + :global(.ant-form) { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + row-gap: 12px; + column-gap: 16px; + } + + :global(.ant-form-item) { + flex-shrink: 0; + margin-right: 0; + margin-bottom: 0; + } +} + +.tableCard { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + background: #fff; + border: 1px solid #f0f0f0; + border-radius: 8px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); + + :global(.ant-card-body) { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + padding: 0; + } +} + +.tableBody { + flex: 1; + min-height: 0; + overflow: auto; +} + +.tableFooter { + flex-shrink: 0; + padding: 16px 24px; + border-top: 1px solid #f0f0f0; + display: flex; + justify-content: flex-end; +} diff --git a/src/views/venue/list/index.tsx b/src/views/venue/list/index.tsx new file mode 100644 index 0000000..02a9fe2 --- /dev/null +++ b/src/views/venue/list/index.tsx @@ -0,0 +1,139 @@ +import { defineComponent } from 'vue'; +import { + Card, + Table, + Input, + Select, + Button, + Space, + Tag, + Popconfirm, + Pagination, + Form, +} from 'ant-design-vue'; +import { SearchOutlined, EditOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons-vue'; +import { useVenueListModel, type VenueListItem } from './model'; +import styles from './index.module.less'; + +/** + * 馆客多 / 场馆列表 + */ +export default defineComponent({ + name: 'VenueList', + setup() { + const { tableData, currentPage, formRef, formState, statusMap, handleReset } = + useVenueListModel(); + + const columns = [ + { title: '序号', dataIndex: 'id', key: 'id', width: 100 }, + { title: '场馆名称', dataIndex: 'name', key: 'name', width: 160 }, + { title: '场馆类型', dataIndex: 'type', key: 'type', width: 140 }, + { title: '联系人', dataIndex: 'contact', key: 'contact', width: 120 }, + { title: '联系电话', dataIndex: 'phone', key: 'phone', width: 160 }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + customRender: ({ record }: { record: VenueListItem }) => { + const status = statusMap[record.status] || { + color: 'default', + text: '未知', + }; + return {status.text}; + }, + }, + { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 180 }, + { + title: '操作', + key: 'action', + width: 230, + fixed: 'right' as const, + customRender: () => ( + + + + + + + + ), + }, + ]; + + return () => ( +
+ +
+
+ + { + formState.name = e.target.value; + }} + placeholder="请输入场馆名称" + prefix={} + style={{ width: '200px' }} + /> + + + + + + + + + + + +
+
+ + +
+
+ + +
+ { + currentPage.value = page; + }} + total={500} + showSizeChanger + showQuickJumper + showTotal={(total: number) => `共 ${total} 条`} + /> +
+ + + ); + }, +}); diff --git a/src/views/venue/list/model/index.ts b/src/views/venue/list/model/index.ts new file mode 100644 index 0000000..f523e80 --- /dev/null +++ b/src/views/venue/list/model/index.ts @@ -0,0 +1,2 @@ +export * from './useVenueListModel'; +export * from './services'; diff --git a/src/views/venue/list/model/services.ts b/src/views/venue/list/model/services.ts new file mode 100644 index 0000000..f14ad83 --- /dev/null +++ b/src/views/venue/list/model/services.ts @@ -0,0 +1,72 @@ +import { get } from '@/utils/request'; + +/** 场馆列表行数据 */ +export interface VenueListItem { + key: string; + id: string; + name: string; + type: string; + contact: string; + status: string; + phone: string; + createTime: string; +} + +/** 场馆状态映射:状态值 -> 颜色 / 文案 */ +export const statusMap: Record = { + active: { color: 'green', text: '正常' }, + inactive: { color: 'red', text: '停用' }, + pending: { color: 'orange', text: '待审' }, +}; + +/** 场馆列表查询参数 */ +export interface VenueListParams { + page: number; + size: number; + name?: string; + status?: string; +} + +const venueListApi = '/venue/list'; + +/** + * 馆客多-场馆列表接口 + * @description 后端接口就绪后,在 useVenueListModel 中启用替换 mock 数据 + */ +export const getVenueList = (params: VenueListParams) => { + return get(venueListApi, params); +}; + +/** 临时 mock 数据(接口对接前占位展示) */ +export const mockVenueList: VenueListItem[] = [ + { + key: '1', + id: 'V001', + name: '健身中心', + type: '健身馆', + contact: '张三', + status: 'active', + phone: '13999999999', + createTime: '2024-01-15 10:30:00', + }, + { + key: '2', + id: 'V002', + name: '篮球公园', + type: '篮球馆', + contact: '李四', + status: 'active', + phone: '13999999999', + createTime: '2024-01-16 14:20:00', + }, + { + key: '3', + id: 'V003', + name: '游泳馆', + type: '游泳馆', + contact: '王五', + status: 'inactive', + phone: '13999999999', + createTime: '2024-01-17 09:15:00', + }, +]; diff --git a/src/views/venue/list/model/useVenueListModel.ts b/src/views/venue/list/model/useVenueListModel.ts new file mode 100644 index 0000000..fc8ef63 --- /dev/null +++ b/src/views/venue/list/model/useVenueListModel.ts @@ -0,0 +1,54 @@ +import { reactive, ref } from 'vue'; +import { getVenueList, mockVenueList, statusMap, type VenueListItem } from './services'; + +/** 筛选表单字段 */ +export interface VenueListFilterForm { + name: string; + status?: string; +} + +/** + * 馆客多(场馆列表)页面业务逻辑 + */ +export function useVenueListModel() { + // 表格数据(暂用 mock,接口就绪后由 fetchList 填充) + const tableData = ref(mockVenueList); + // 当前页码 + const currentPage = ref(1); + // 筛选表单实例 + const formRef = ref(); + // 筛选表单数据 + const formState = reactive({ + name: '', + status: undefined, + }); + + /** 重置筛选表单 */ + const handleReset = () => { + formRef.value?.resetFields(); + Object.assign(formState, { name: '', status: undefined }); + }; + + /** + * 查询场馆列表 + * @description 后端接口就绪后启用,替换默认的 mock 数据 + */ + const fetchList = async () => { + const res: any = await getVenueList({ + page: currentPage.value, + size: 10, + ...formState, + }); + tableData.value = res?.data?.list ?? []; + }; + + return { + tableData, + currentPage, + formRef, + formState, + statusMap, + handleReset, + fetchList, + }; +} diff --git a/src/views/venue/records/index.module.less b/src/views/venue/records/index.module.less new file mode 100644 index 0000000..d8812ec --- /dev/null +++ b/src/views/venue/records/index.module.less @@ -0,0 +1,67 @@ +.container { + display: flex; + flex-direction: column; + height: calc(100vh - 64px - 30px); +} + +.filterCard { + flex-shrink: 0; + background: #fff; + border: 1px solid #f0f0f0; + border-radius: 8px; + margin-bottom: 16px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); + + :global(.ant-card-body) { + padding: 20px 24px; + } +} + +.filterRow { + :global(.ant-form) { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + row-gap: 12px; + column-gap: 16px; + } + + :global(.ant-form-item) { + flex-shrink: 0; + margin-right: 0; + margin-bottom: 0; + } +} + +.tableCard { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + background: #fff; + border: 1px solid #f0f0f0; + border-radius: 8px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03); + + :global(.ant-card-body) { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + padding: 0; + } +} + +.tableBody { + flex: 1; + min-height: 0; + overflow: auto; +} + +.tableFooter { + flex-shrink: 0; + padding: 16px 24px; + border-top: 1px solid #f0f0f0; + display: flex; + justify-content: flex-end; +} diff --git a/src/views/venue/records/index.tsx b/src/views/venue/records/index.tsx new file mode 100644 index 0000000..56a291e --- /dev/null +++ b/src/views/venue/records/index.tsx @@ -0,0 +1,137 @@ +import { defineComponent } from 'vue'; +import { + Card, + Table, + Input, + Select, + Button, + Space, + Tag, + Popconfirm, + Pagination, + Form, +} from 'ant-design-vue'; +import { SearchOutlined, EyeOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons-vue'; +import { useVenueRecordsModel, type VenueRecordItem } from './model'; +import styles from './index.module.less'; + +/** + * 收录 / 收录记录列表 + */ +export default defineComponent({ + name: 'VenueRecords', + setup() { + const { tableData, currentPage, formRef, formState, statusMap, handleReset } = + useVenueRecordsModel(); + + const columns = [ + { title: '序号', dataIndex: 'id', key: 'id', width: 100 }, + { title: '场馆名称', dataIndex: 'venueName', key: 'venueName', width: 180 }, + { title: '来源', dataIndex: 'source', key: 'source', width: 140 }, + { + title: '收录状态', + dataIndex: 'status', + key: 'status', + width: 120, + customRender: ({ record }: { record: VenueRecordItem }) => { + const status = statusMap[record.status] || { + color: 'default', + text: '未知', + }; + return {status.text}; + }, + }, + { title: '收录时间', dataIndex: 'recordTime', key: 'recordTime', width: 180 }, + { + title: '操作', + key: 'action', + width: 240, + fixed: 'right' as const, + customRender: () => ( + + + + + + + + ), + }, + ]; + + return () => ( +
+ +
+
+ + { + formState.venueName = e.target.value; + }} + placeholder="请输入场馆名称" + prefix={} + style={{ width: '200px' }} + /> + + + + + + + + + + + +
+
+ + +
+
+ + +
+ { + currentPage.value = page; + }} + total={500} + showSizeChanger + showQuickJumper + showTotal={(total: number) => `共 ${total} 条`} + /> +
+ + + ); + }, +}); diff --git a/src/views/venue/records/model/index.ts b/src/views/venue/records/model/index.ts new file mode 100644 index 0000000..7dd5fb0 --- /dev/null +++ b/src/views/venue/records/model/index.ts @@ -0,0 +1,2 @@ +export * from './useVenueRecordsModel'; +export * from './services'; diff --git a/src/views/venue/records/model/services.ts b/src/views/venue/records/model/services.ts new file mode 100644 index 0000000..08bbfab --- /dev/null +++ b/src/views/venue/records/model/services.ts @@ -0,0 +1,64 @@ +import { get } from '@/utils/request'; + +/** 收录记录行数据 */ +export interface VenueRecordItem { + key: string; + id: string; + venueName: string; + source: string; + status: string; + recordTime: string; +} + +/** 收录状态映射:状态值 -> 颜色 / 文案 */ +export const statusMap: Record = { + recorded: { color: 'green', text: '已收录' }, + reviewing: { color: 'orange', text: '审核中' }, + rejected: { color: 'red', text: '已驳回' }, +}; + +/** 收录记录查询参数 */ +export interface VenueRecordParams { + page: number; + size: number; + venueName?: string; + status?: string; +} + +const venueRecordApi = '/venue/records'; + +/** + * 收录记录接口 + * @description 后端接口就绪后,在 useVenueRecordsModel 中启用替换 mock 数据 + */ +export const getVenueRecords = (params: VenueRecordParams) => { + return get(venueRecordApi, params); +}; + +/** 临时 mock 数据(接口对接前占位展示) */ +export const mockVenueRecords: VenueRecordItem[] = [ + { + key: '1', + id: 'R001', + venueName: '健身中心', + source: '用户提交', + status: 'recorded', + recordTime: '2024-01-15 10:30:00', + }, + { + key: '2', + id: 'R002', + venueName: '篮球公园', + source: '平台采集', + status: 'reviewing', + recordTime: '2024-01-16 14:20:00', + }, + { + key: '3', + id: 'R003', + venueName: '游泳馆', + source: '用户提交', + status: 'rejected', + recordTime: '2024-01-17 09:15:00', + }, +]; diff --git a/src/views/venue/records/model/useVenueRecordsModel.ts b/src/views/venue/records/model/useVenueRecordsModel.ts new file mode 100644 index 0000000..b5b8b95 --- /dev/null +++ b/src/views/venue/records/model/useVenueRecordsModel.ts @@ -0,0 +1,54 @@ +import { reactive, ref } from 'vue'; +import { getVenueRecords, mockVenueRecords, statusMap, type VenueRecordItem } from './services'; + +/** 筛选表单字段 */ +export interface VenueRecordFilterForm { + venueName: string; + status?: string; +} + +/** + * 收录记录页面业务逻辑 + */ +export function useVenueRecordsModel() { + // 表格数据(暂用 mock,接口就绪后由 fetchList 填充) + const tableData = ref(mockVenueRecords); + // 当前页码 + const currentPage = ref(1); + // 筛选表单实例 + const formRef = ref(); + // 筛选表单数据 + const formState = reactive({ + venueName: '', + status: undefined, + }); + + /** 重置筛选表单 */ + const handleReset = () => { + formRef.value?.resetFields(); + Object.assign(formState, { venueName: '', status: undefined }); + }; + + /** + * 查询收录记录 + * @description 后端接口就绪后启用,替换默认的 mock 数据 + */ + const fetchList = async () => { + const res: any = await getVenueRecords({ + page: currentPage.value, + size: 10, + ...formState, + }); + tableData.value = res?.data?.list ?? []; + }; + + return { + tableData, + currentPage, + formRef, + formState, + statusMap, + handleReset, + fetchList, + }; +} diff --git a/vite.config.ts b/vite.config.ts index 21e6065..1dc21f3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -38,6 +38,7 @@ export default defineConfig(({ mode }) => { }, }, build: { + target: 'es2020', outDir: 'dist', sourcemap: mode !== 'production', chunkSizeWarningLimit: 1500, @@ -50,5 +51,10 @@ export default defineConfig(({ mode }) => { }, }, }, + optimizeDeps: { + esbuildOptions: { + target: 'es2020', + }, + }, }; });