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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Unicode
+ - Font class
+ - Symbol
+
+
+
查看项目
+
+
+
+
+
+
+ -
+
+
场馆
+ 
+
+
+ -
+
+
灯-点亮
+ 
+
+
+ -
+
+
灯-关闭
+ 
+
+
+ -
+
+
刷新
+ 
+
+
+ -
+
+
关闭
+ 
+
+
+ -
+
+
添加
+ 
+
+
+ -
+
+
签到码
+ 
+
+
+ -
+
+
日历
+ 
+
+
+ -
+
+
分组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">3</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,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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 {
+ 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 @@
+
+
+
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运营平台'}
+
+ {/* 导航菜单 */}
+ {/* 右侧内容区域 */}
-
);
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 () => (
+
+ );
+ },
+});
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' }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ },
+});
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' }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ },
+});
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',
+ },
+ },
};
});