Explorar o código

支持tailwindcss

zhanghaijun hai 1 ano
pai
achega
2dc8618ce4
Modificáronse 38 ficheiros con 995 adicións e 575 borrados
  1. 1 1
      .env
  2. 2 1
      .prettierrc
  3. 3 0
      package.json
  4. 518 67
      pnpm-lock.yaml
  5. 4 0
      src/assets/styles/tailwind.css
  6. 1 0
      src/main.ts
  7. 0 1
      src/router/routesAlias.ts
  8. 3 23
      src/views/article/comment/index.vue
  9. 1 9
      src/views/auth/forget-password/index.vue
  10. 5 32
      src/views/auth/login/index.vue
  11. 5 34
      src/views/auth/register/index.vue
  12. 2 8
      src/views/change/log/index.vue
  13. 1 8
      src/views/dashboard/console/widget/ActiveUser.vue
  14. 1 4
      src/views/dashboard/console/widget/CardList.vue
  15. 2 15
      src/views/dashboard/console/widget/NewUser.vue
  16. 2 22
      src/views/dashboard/console/widget/SalesOverview.vue
  17. 1 8
      src/views/dashboard/ecommerce/widget/AnnualSales.vue
  18. 1 7
      src/views/dashboard/ecommerce/widget/Banner.vue
  19. 1 9
      src/views/dashboard/ecommerce/widget/CartConversionRate.vue
  20. 1 8
      src/views/dashboard/ecommerce/widget/HotCommodity.vue
  21. 2 12
      src/views/dashboard/ecommerce/widget/HotProductsList.vue
  22. 2 11
      src/views/dashboard/ecommerce/widget/ProductSales.vue
  23. 2 12
      src/views/dashboard/ecommerce/widget/SalesGrowth.vue
  24. 1 6
      src/views/dashboard/ecommerce/widget/SalesTrend.vue
  25. 1 8
      src/views/dashboard/ecommerce/widget/TotalProducts.vue
  26. 1 8
      src/views/dashboard/ecommerce/widget/TransactionList.vue
  27. 4 16
      src/views/examples/forms/search-bar.vue
  28. 17 62
      src/views/examples/permission/button-auth/index.vue
  29. 3 10
      src/views/examples/permission/page-visibility/index.vue
  30. 4 24
      src/views/examples/permission/switch-role/index.vue
  31. 16 58
      src/views/examples/tables/index.vue
  32. 4 25
      src/views/examples/tables/tree.vue
  33. 4 15
      src/views/examples/tabs/index.vue
  34. 1 7
      src/views/outside/Iframe.vue
  35. 5 32
      src/views/system/user-center/index.vue
  36. 17 0
      src/views/train/plan/index copy.vue
  37. 354 12
      src/views/train/plan/index.vue
  38. 2 0
      vite.config.ts

+ 1 - 1
.env

@@ -25,6 +25,6 @@ VITE_OPEN_ROUTE_INFO = false
 VITE_LOCK_ENCRYPT_KEY = s3cur3k3y4adpro
 
 # 是否启用离线图标
-VITE_APP_ICON_OFFLINE=false
+VITE_APP_ICON_OFFLINE=true
 # 图标前缀-提供给icon-picker选择的图标,这里的值建议和src/utils/load-icon.ts中引入的一致
 VITE_APP_ICONIFY_PREFIX = ep,ant-design,carbon,ic,logos,lucide

+ 2 - 1
.prettierrc

@@ -16,5 +16,6 @@
   "proseWrap": "never",
   "htmlWhitespaceSensitivity": "strict",
   "endOfLine": "auto",
-  "rangeStart": 0
+  "rangeStart": 0,
+  "plugins": ["prettier-plugin-tailwindcss"]
 }

+ 3 - 0
package.json

@@ -81,6 +81,7 @@
     "@commitlint/cli": "^19.4.1",
     "@commitlint/config-conventional": "^19.4.1",
     "@eslint/js": "^9.9.1",
+    "@tailwindcss/vite": "^4.1.13",
     "@types/lodash-es": "^4.17.12",
     "@types/node": "^22.1.0",
     "@typescript-eslint/eslint-plugin": "^8.3.0",
@@ -98,6 +99,7 @@
     "husky": "^9.1.5",
     "lint-staged": "^15.5.2",
     "prettier": "^3.5.3",
+    "prettier-plugin-tailwindcss": "^0.6.14",
     "rollup-plugin-visualizer": "^5.12.0",
     "sass": "^1.81.0",
     "stylelint": "^16.20.0",
@@ -106,6 +108,7 @@
     "stylelint-config-recommended-scss": "^14.1.0",
     "stylelint-config-recommended-vue": "^1.5.0",
     "stylelint-config-standard": "^36.0.1",
+    "tailwindcss": "^4.1.13",
     "terser": "^5.36.0",
     "tsx": "^4.20.3",
     "typescript": "~5.6.3",

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 518 - 67
pnpm-lock.yaml


+ 4 - 0
src/assets/styles/tailwind.css

@@ -0,0 +1,4 @@
+/**
+ * Docs: https://tailwindcss.com/docs/theme
+ */
+@import 'tailwindcss';

+ 1 - 0
src/main.ts

@@ -11,6 +11,7 @@ import '@styles/theme-animation.scss'               // 主题切换动画
 import '@styles/el-light.scss'                      // Element 自定义主题(亮色)
 import '@styles/el-dark.scss'                       // Element 自定义主题(暗色)
 import '@styles/dark.scss'                          // 系统主题
+import '@styles/tailwind.css'                       // tailwindcss
 import '@icons/system/iconfont.css'                 // 系统图标
 import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 import { setupGlobDirectives } from './directives'

+ 0 - 1
src/router/routesAlias.ts

@@ -45,7 +45,6 @@ export enum RoutesAlias {
   UserCenter = '/system/user-center', // 用户中心
   Menu = '/system/menu', // 菜单
   Server = '/safeguard/server', // 服务器
-  ChangeLog = '/change/log', // 更新日志
   ExamplesTabs = '/examples/tabs', // 标签页
   ExamplesTablesBasic = '/examples/tables/basic', // 基础表格示例
   ExamplesTables = '/examples/tables', // 高级表格示例

+ 3 - 23
src/views/article/comment/index.vue

@@ -5,13 +5,7 @@
 
     <div class="list">
       <ul class="offset">
-        <li
-          class="comment-box"
-          v-for="item in commentList"
-          :key="item.id"
-          :style="{ background: randomColor() }"
-          @click="openDrawer(item)"
-        >
+        <li class="comment-box" v-for="item in commentList" :key="item.id" :style="{ background: randomColor() }" @click="openDrawer(item)">
           <p class="date">{{ item.date }}</p>
           <p class="content">{{ item.content }}</p>
           <div class="bottom">
@@ -27,13 +21,7 @@
       </ul>
     </div>
 
-    <ElDrawer
-      lDrawer
-      v-model="showDrawer"
-      :lock-scroll="false"
-      :size="360"
-      modal-class="comment-modal"
-    >
+    <ElDrawer lDrawer v-model="showDrawer" :lock-scroll="false" :size="360" modal-class="comment-modal">
       <template #header>
         <h4>详情</h4>
       </template>
@@ -83,15 +71,7 @@
   //   'rgba(225, 230, 254, 0.8)'
   // ])
 
-  const colorList = reactive([
-    '#D8F8FF',
-    '#FDDFD9',
-    '#FCE6F0',
-    '#D3F8F0',
-    '#FFEABC',
-    '#F5E1FF',
-    '#E1E6FE'
-  ])
+  const colorList = reactive(['#D8F8FF', '#FDDFD9', '#FCE6F0', '#D3F8F0', '#FFEABC', '#F5E1FF', '#E1E6FE'])
 
   let lastColor: string | null = null
 

+ 1 - 9
src/views/auth/forget-password/index.vue

@@ -14,19 +14,11 @@
             <span class="input-label" v-if="showInputLabel">账号</span>
             <ElInput :placeholder="$t('forgetPassword.placeholder')" v-model.trim="username" />
           </div>
-
           <div style="margin-top: 15px">
-            <ElButton
-              class="login-btn"
-              type="primary"
-              @click="register"
-              :loading="loading"
-              v-ripple
-            >
+            <ElButton class="login-btn" type="primary" @click="register" :loading="loading" v-ripple>
               {{ $t('forgetPassword.submitBtnText') }}
             </ElButton>
           </div>
-
           <div style="margin-top: 15px">
             <ElButton class="back-btn" plain @click="toLogin">
               {{ $t('forgetPassword.backBtnText') }}

+ 5 - 32
src/views/auth/login/index.vue

@@ -16,10 +16,7 @@
           <template #dropdown>
             <ElDropdownMenu>
               <div v-for="lang in languageOptions" :key="lang.value" class="lang-btn-item">
-                <ElDropdownItem
-                  :command="lang.value"
-                  :class="{ 'is-selected': locale === lang.value }"
-                >
+                <ElDropdownItem :command="lang.value" :class="{ 'is-selected': locale === lang.value }">
                   <span class="menu-txt">{{ lang.label }}</span>
                   <i v-if="locale === lang.value" class="iconfont-sys icon-check">&#xe621;</i>
                 </ElDropdownItem>
@@ -36,21 +33,10 @@
         <div class="form">
           <h3 class="title">{{ $t('login.title') }}</h3>
           <p class="sub-title">{{ $t('login.subTitle') }}</p>
-          <ElForm
-            ref="formRef"
-            :model="formData"
-            :rules="rules"
-            @keyup.enter="handleSubmit"
-            style="margin-top: 25px"
-          >
+          <ElForm ref="formRef" :model="formData" :rules="rules" @keyup.enter="handleSubmit" style="margin-top: 25px">
             <ElFormItem prop="account">
               <ElSelect v-model="formData.account" @change="setupAccount" class="account-select">
-                <ElOption
-                  v-for="account in accounts"
-                  :key="account.key"
-                  :label="account.label"
-                  :value="account.key"
-                >
+                <ElOption v-for="account in accounts" :key="account.key" :label="account.label" :value="account.key">
                   <span>{{ account.label }}</span>
                 </ElOption>
               </ElSelect>
@@ -59,14 +45,7 @@
               <ElInput :placeholder="$t('login.placeholder[0]')" v-model.trim="formData.username" />
             </ElFormItem>
             <ElFormItem prop="password">
-              <ElInput
-                :placeholder="$t('login.placeholder[1]')"
-                v-model.trim="formData.password"
-                type="password"
-                radius="8px"
-                autocomplete="off"
-                show-password
-              />
+              <ElInput :placeholder="$t('login.placeholder[1]')" v-model.trim="formData.password" type="password" radius="8px" autocomplete="off" show-password />
             </ElFormItem>
             <div class="drag-verify">
               <div class="drag-verify-content" :class="{ error: !isPassing && isClickPass }">
@@ -94,13 +73,7 @@
             </div>
 
             <div style="margin-top: 30px">
-              <ElButton
-                class="login-btn"
-                type="primary"
-                @click="handleSubmit"
-                :loading="loading"
-                v-ripple
-              >
+              <ElButton class="login-btn" type="primary" @click="handleSubmit" :loading="loading" v-ripple>
                 {{ $t('login.btnText') }}
               </ElButton>
             </div>

+ 5 - 34
src/views/auth/register/index.vue

@@ -12,52 +12,23 @@
           <p class="sub-title">{{ $t('register.subTitle') }}</p>
           <ElForm ref="formRef" :model="formData" :rules="rules" label-position="top">
             <ElFormItem prop="username">
-              <ElInput
-                v-model.trim="formData.username"
-                :placeholder="$t('register.placeholder[0]')"
-              />
+              <ElInput v-model.trim="formData.username" :placeholder="$t('register.placeholder[0]')" />
             </ElFormItem>
-
             <ElFormItem prop="password">
-              <ElInput
-                v-model.trim="formData.password"
-                :placeholder="$t('register.placeholder[1]')"
-                type="password"
-                autocomplete="off"
-                show-password
-              />
+              <ElInput v-model.trim="formData.password" :placeholder="$t('register.placeholder[1]')" type="password" autocomplete="off" show-password />
             </ElFormItem>
-
             <ElFormItem prop="confirmPassword">
-              <ElInput
-                v-model.trim="formData.confirmPassword"
-                :placeholder="$t('register.placeholder[2]')"
-                type="password"
-                autocomplete="off"
-                @keyup.enter="register"
-                show-password
-              />
+              <ElInput v-model.trim="formData.confirmPassword" :placeholder="$t('register.placeholder[2]')" type="password" autocomplete="off" @keyup.enter="register" show-password />
             </ElFormItem>
-
             <ElFormItem prop="agreement">
               <ElCheckbox v-model="formData.agreement">
                 {{ $t('register.agreeText') }}
-                <router-link
-                  style="color: var(--main-color); text-decoration: none"
-                  to="/privacy-policy"
-                  >{{ $t('register.privacyPolicy') }}</router-link
-                >
+                <router-link style="color: var(--main-color); text-decoration: none" to="/privacy-policy">{{ $t('register.privacyPolicy') }}</router-link>
               </ElCheckbox>
             </ElFormItem>
 
             <div style="margin-top: 15px">
-              <ElButton
-                class="register-btn"
-                type="primary"
-                @click="register"
-                :loading="loading"
-                v-ripple
-              >
+              <ElButton class="register-btn" type="primary" @click="register" :loading="loading" v-ripple>
                 {{ $t('register.submitBtnText') }}
               </ElButton>
             </div>

+ 2 - 8
src/views/change/log/index.vue

@@ -10,9 +10,7 @@
           <template #default="scope">
             <div class="title">{{ scope.row.title }}</div>
             <div v-if="scope.row.detail" style="margin-top: 10px">
-              <div class="detail-item" v-for="(item, index) in scope.row.detail" :key="index">
-                {{ index + 1 }}. {{ item }}
-              </div>
+              <div class="detail-item" v-for="(item, index) in scope.row.detail" :key="index"> {{ index + 1 }}. {{ item }} </div>
             </div>
           </template>
         </ElTableColumn>
@@ -123,11 +121,7 @@
           font-size: 12px;
           font-weight: 600;
           color: white;
-          background: linear-gradient(
-            135deg,
-            var(--el-color-primary),
-            var(--el-color-primary-light-3)
-          );
+          background: linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3));
           border-radius: 20px;
         }
 

+ 1 - 8
src/views/dashboard/console/widget/ActiveUser.vue

@@ -1,13 +1,6 @@
 <template>
   <div class="card art-custom-card">
-    <ArtBarChart
-      class="chart"
-      barWidth="50%"
-      height="13.7rem"
-      :showAxisLine="false"
-      :data="[160, 100, 150, 80, 190, 100, 175, 120, 160]"
-      :xAxisData="['1', '2', '3', '4', '5', '6', '7', '8', '9']"
-    />
+    <ArtBarChart class="chart" barWidth="50%" height="13.7rem" :showAxisLine="false" :data="[160, 100, 150, 80, 190, 100, 175, 120, 160]" :xAxisData="['1', '2', '3', '4', '5', '6', '7', '8', '9']" />
     <div class="text">
       <h3 class="box-title">用户概述</h3>
       <p class="subtitle">比上周 <span class="text-success">+23%</span></p>

+ 1 - 4
src/views/dashboard/console/widget/CardList.vue

@@ -6,10 +6,7 @@
         <ArtCountTo class="number box-title" :target="item.num" :duration="1300" />
         <div class="change-box">
           <span class="change-text">较上周</span>
-          <span
-            class="change"
-            :class="[item.change.indexOf('+') === -1 ? 'text-danger' : 'text-success']"
-          >
+          <span class="change" :class="[item.change.indexOf('+') === -1 ? 'text-danger' : 'text-success']">
             {{ item.change }}
           </span>
         </div>

+ 2 - 15
src/views/dashboard/console/widget/NewUser.vue

@@ -11,15 +11,7 @@
         <el-radio-button value="今年" label="今年"></el-radio-button>
       </el-radio-group>
     </div>
-    <ArtTable
-      class="table"
-      :data="tableData"
-      style="width: 100%"
-      size="large"
-      :border="false"
-      :stripe="false"
-      :header-cell-style="{ background: 'transparent' }"
-    >
+    <ArtTable class="table" :data="tableData" style="width: 100%" size="large" :border="false" :stripe="false" :header-cell-style="{ background: 'transparent' }">
       <template #default>
         <el-table-column label="头像" prop="avatar" width="150px">
           <template #default="scope">
@@ -39,12 +31,7 @@
         </el-table-column>
         <el-table-column label="进度" width="240">
           <template #default="scope">
-            <el-progress
-              :percentage="scope.row.pro"
-              :color="scope.row.color"
-              :stroke-width="4"
-              :aria-label="`${scope.row.username}的完成进度: ${scope.row.pro}%`"
-            />
+            <el-progress :percentage="scope.row.pro" :color="scope.row.color" :stroke-width="4" :aria-label="`${scope.row.username}的完成进度: ${scope.row.pro}%`" />
           </template>
         </el-table-column>
       </template>

+ 2 - 22
src/views/dashboard/console/widget/SalesOverview.vue

@@ -6,33 +6,13 @@
         <p class="subtitle">今年增长<span class="text-success">+15%</span></p>
       </div>
     </div>
-    <ArtLineChart
-      class="chart"
-      height="calc(100% - 40px)"
-      :data="data"
-      :xAxisData="xAxisData"
-      :showAreaColor="true"
-      :showAxisLine="false"
-    />
+    <ArtLineChart class="chart" height="calc(100% - 40px)" :data="data" :xAxisData="xAxisData" :showAreaColor="true" :showAxisLine="false" />
   </div>
 </template>
 
 <script setup lang="ts">
   const data = [50, 25, 40, 20, 70, 35, 65, 30, 35, 20, 40, 44]
-  const xAxisData = [
-    '1月',
-    '2月',
-    '3月',
-    '4月',
-    '5月',
-    '6月',
-    '7月',
-    '8月',
-    '9月',
-    '10月',
-    '11月',
-    '12月'
-  ]
+  const xAxisData = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
 </script>
 
 <style lang="scss" scoped>

+ 1 - 8
src/views/dashboard/ecommerce/widget/AnnualSales.vue

@@ -5,14 +5,7 @@
       <p class="subtitle">按季度统计</p>
     </div>
 
-    <ArtBarChart
-      :showAxisLabel="false"
-      :showAxisLine="false"
-      :showSplitLine="false"
-      :data="[50, 80, 50, 90, 60, 70, 50]"
-      barWidth="26px"
-      height="calc(100% - 155px)"
-    />
+    <ArtBarChart :showAxisLabel="false" :showAxisLine="false" :showSplitLine="false" :data="[50, 80, 50, 90, 60, 70, 50]" barWidth="26px" height="calc(100% - 155px)" />
     <div class="icon-text-widget">
       <div class="item">
         <div class="icon">

+ 1 - 7
src/views/dashboard/ecommerce/widget/Banner.vue

@@ -24,13 +24,7 @@
     <div class="banner-slot">
       <div class="item">
         <p class="title">
-          <ArtCountTo
-            class="number box-title"
-            :target="2340"
-            :duration="1500"
-            prefix="¥"
-            separator=","
-          />
+          <ArtCountTo class="number box-title" :target="2340" :duration="1500" prefix="¥" separator="," />
           <i class="iconfont-sys text-success">&#xe8d5;</i>
         </p>
         <p class="subtitle">今日销售额</p>

+ 1 - 9
src/views/dashboard/ecommerce/widget/CartConversionRate.vue

@@ -1,11 +1,3 @@
 <template>
-  <ArtLineChartCard
-    class="margin-bottom-0"
-    :value="2545"
-    label="购物车转化率"
-    :percentage="1.2"
-    :height="13.5"
-    :chartData="[120, 132, 101, 134, 90, 230, 210]"
-    :showAreaColor="true"
-  />
+  <ArtLineChartCard class="margin-bottom-0" :value="2545" label="购物车转化率" :percentage="1.2" :height="13.5" :chartData="[120, 132, 101, 134, 90, 230, 210]" :showAreaColor="true" />
 </template>

+ 1 - 8
src/views/dashboard/ecommerce/widget/HotCommodity.vue

@@ -4,14 +4,7 @@
       <p class="title">热销商品</p>
       <p class="subtitle">本周销售排行</p>
     </div>
-    <ArtLineChart
-      :showAxisLabel="false"
-      :showAxisLine="false"
-      :showSplitLine="false"
-      :showAreaColor="true"
-      :data="[8, 40, 82, 35, 90, 52, 35]"
-      height="9rem"
-    />
+    <ArtLineChart :showAxisLabel="false" :showAxisLine="false" :showSplitLine="false" :showAreaColor="true" :data="[8, 40, 82, 35, 90, 52, 35]" height="9rem" />
     <div class="content">
       <div class="item" v-for="item in weeklyList" :key="item.title">
         <div class="icon" :class="item.color">

+ 2 - 12
src/views/dashboard/ecommerce/widget/HotProductsList.vue

@@ -6,13 +6,7 @@
     </div>
     <div class="table">
       <el-scrollbar style="height: 21.55rem">
-        <ArtTable
-          :data="tableData"
-          style="margin-top: 0 !important"
-          :border="false"
-          :stripe="false"
-          :header-cell-style="{ background: 'transparent' }"
-        >
+        <ArtTable :data="tableData" style="margin-top: 0 !important" :border="false" :stripe="false" :header-cell-style="{ background: 'transparent' }">
           <template #default>
             <el-table-column label="产品" prop="product" width="220px">
               <template #default="scope">
@@ -40,11 +34,7 @@
             <el-table-column label="销量" prop="sales" />
             <el-table-column label="销售趋势" width="240">
               <template #default="scope">
-                <el-progress
-                  :percentage="scope.row.pro"
-                  :color="scope.row.color"
-                  :stroke-width="4"
-                />
+                <el-progress :percentage="scope.row.pro" :color="scope.row.color" :stroke-width="4" />
               </template>
             </el-table-column>
           </template>

+ 2 - 11
src/views/dashboard/ecommerce/widget/ProductSales.vue

@@ -1,19 +1,10 @@
 <template>
   <div class="card art-custom-card" style="height: 11rem">
     <div class="card-header">
-      <p class="title" style="font-size: 24px"
-        >14.5k<i class="iconfont-sys text-success">&#xe8d5;</i></p
-      >
+      <p class="title" style="font-size: 24px">14.5k<i class="iconfont-sys text-success">&#xe8d5;</i></p>
       <p class="subtitle">销售量</p>
     </div>
 
-    <ArtBarChart
-      :showAxisLabel="false"
-      :showAxisLine="false"
-      :showSplitLine="false"
-      :data="[50, 80, 50, 90, 60, 70, 50]"
-      barWidth="16px"
-      height="4rem"
-    />
+    <ArtBarChart :showAxisLabel="false" :showAxisLine="false" :showSplitLine="false" :data="[50, 80, 50, 90, 60, 70, 50]" barWidth="16px" height="4rem" />
   </div>
 </template>

+ 2 - 12
src/views/dashboard/ecommerce/widget/SalesGrowth.vue

@@ -1,20 +1,10 @@
 <template>
   <div class="card art-custom-card" style="height: 11rem">
     <div class="card-header">
-      <p class="title" style="font-size: 24px"
-        >12%<i class="iconfont-sys text-success">&#xe8d5;</i></p
-      >
+      <p class="title" style="font-size: 24px">12%<i class="iconfont-sys text-success">&#xe8d5;</i></p>
       <p class="subtitle">增长</p>
     </div>
 
-    <ArtLineChart
-      :showAreaColor="true"
-      :showAxisLabel="false"
-      :showAxisLine="false"
-      :showSplitLine="false"
-      :data="[50, 85, 65, 95, 75, 130, 180]"
-      barWidth="16px"
-      height="4rem"
-    />
+    <ArtLineChart :showAreaColor="true" :showAxisLabel="false" :showAxisLine="false" :showSplitLine="false" :data="[50, 85, 65, 95, 75, 130, 180]" barWidth="16px" height="4rem" />
   </div>
 </template>

+ 1 - 6
src/views/dashboard/ecommerce/widget/SalesTrend.vue

@@ -4,11 +4,6 @@
       <p class="title">销售趋势</p>
       <p class="subtitle">月度销售对比</p>
     </div>
-    <ArtDualBarCompareChart
-      :positiveData="[50, 80, 120, 90, 60]"
-      :negativeData="[30, 60, 90, 70, 40]"
-      :xAxisData="['一月', '二月', '三月', '四月', '五月']"
-      height="19rem"
-    />
+    <ArtDualBarCompareChart :positiveData="[50, 80, 120, 90, 60]" :negativeData="[30, 60, 90, 70, 40]" :xAxisData="['一月', '二月', '三月', '四月', '五月']" height="19rem" />
   </div>
 </template>

+ 1 - 8
src/views/dashboard/ecommerce/widget/TotalProducts.vue

@@ -4,13 +4,6 @@
       <p class="title" style="font-size: 24px">55,231</p>
       <p class="subtitle">商品总数</p>
     </div>
-    <ArtBarChart
-      :showAxisLabel="false"
-      :showAxisLine="false"
-      :showSplitLine="false"
-      :data="[50, 80, 40, 90, 60, 70]"
-      height="7rem"
-      barWidth="18px"
-    />
+    <ArtBarChart :showAxisLabel="false" :showAxisLine="false" :showSplitLine="false" :data="[50, 80, 40, 90, 60, 70]" height="7rem" barWidth="18px" />
   </div>
 </template>

+ 1 - 8
src/views/dashboard/ecommerce/widget/TransactionList.vue

@@ -1,12 +1,5 @@
 <template>
-  <ArtDataListCard
-    :maxCount="4"
-    :list="dataList"
-    title="最近活动"
-    subtitle="订单处理状态"
-    :showMoreButton="true"
-    @more="handleMore"
-  />
+  <ArtDataListCard :maxCount="4" :list="dataList" title="最近活动" subtitle="订单处理状态" :showMoreButton="true" @more="handleMore" />
 </template>
 
 <script setup lang="ts">

+ 4 - 16
src/views/examples/forms/search-bar.vue

@@ -2,14 +2,7 @@
 <template>
   <div class="search-bar">
     <h2 class="title">基础示例(默认收起)</h2>
-    <ArtSearchBar
-      ref="searchBarBasicRef"
-      v-model="formDataBasic"
-      :items="formItemsBasic"
-      @reset="handleBasicReset"
-      @search="handleBasicSearch"
-    >
-    </ArtSearchBar>
+    <ArtSearchBar ref="searchBarBasicRef" v-model="formDataBasic" :items="formItemsBasic" @reset="handleBasicReset" @search="handleBasicSearch"> </ArtSearchBar>
 
     <h2 class="title m-15">完整示例(默认展开)</h2>
     <ArtSearchBar
@@ -427,8 +420,7 @@
         h(
           'div',
           {
-            style:
-              'color: var(--art-gray-600); border: 1px solid var(--art-border-dashed-color); padding: 0px 15px; border-radius: 6px'
+            style: 'color: var(--art-gray-600); border: 1px solid var(--art-border-dashed-color); padding: 0px 15px; border-radius: 6px'
           },
           '我是一个自定义组件'
         ),
@@ -571,14 +563,10 @@
   })
 
   // 基础表单处理器
-  const basicFormHandler = computed(() =>
-    createFormHandler(searchBarBasicRef, formDataBasic, '基础')
-  )
+  const basicFormHandler = computed(() => createFormHandler(searchBarBasicRef, formDataBasic, '基础'))
 
   // 高级表单处理器
-  const advancedFormHandler = computed(() =>
-    createFormHandler(searchBarAdvancedRef, formDataAdvanced, '完整')
-  )
+  const advancedFormHandler = computed(() => createFormHandler(searchBarAdvancedRef, formDataAdvanced, '完整'))
 
   // 事件处理函数
   const handleBasicReset = () => basicFormHandler.value.reset()

+ 17 - 62
src/views/examples/permission/button-auth/index.vue

@@ -3,9 +3,7 @@
     <!-- 页面头部 -->
     <div class="page-header">
       <h2>{{ $t('menus.examples.permission.buttonAuth') }}</h2>
-      <p class="description">
-        此页面演示按钮级别的权限控制,不同用户身份下可见的按钮会有所不同。权限控制基于用户角色和权限码实现。
-      </p>
+      <p class="description"> 此页面演示按钮级别的权限控制,不同用户身份下可见的按钮会有所不同。权限控制基于用户角色和权限码实现。 </p>
     </div>
 
     <div class="current-auth-card">
@@ -25,13 +23,7 @@
           <div class="auth-item">
             <span class="label">权限码:</span>
             <div class="permissions-list">
-              <ElTag
-                v-for="permission in currentUserPermissions"
-                :key="permission"
-                size="small"
-                type="info"
-                class="permission-tag"
-              >
+              <ElTag v-for="permission in currentUserPermissions" :key="permission" size="small" type="info" class="permission-tag">
                 {{ permission }}
               </ElTag>
               <span v-if="!currentUserPermissions.length" class="no-data">无权限码</span>
@@ -50,9 +42,7 @@
           </div>
         </template>
         <div class="demo-content">
-          <p class="section-desc">
-            使用 <code>v-roles</code> 指令控制按钮显示,只有拥有指定角色的用户才能看到对应按钮。
-          </p>
+          <p class="section-desc"> 使用 <code>v-roles</code> 指令控制按钮显示,只有拥有指定角色的用户才能看到对应按钮。 </p>
 
           <div class="button-group">
             <div class="button-item">
@@ -65,9 +55,7 @@
 
             <!-- 管理员级别按钮 -->
             <div class="button-item">
-              <ElButton type="warning" plain v-roles="['R_SUPER', 'R_ADMIN']">
-                管理员可见
-              </ElButton>
+              <ElButton type="warning" plain v-roles="['R_SUPER', 'R_ADMIN']"> 管理员可见 </ElButton>
               <div class="button-desc">
                 <code>v-roles="['R_SUPER', 'R_ADMIN']"</code>
                 <span>超级管理员和管理员可见</span>
@@ -76,9 +64,7 @@
 
             <!-- 所有已登录用户可见 -->
             <div class="button-item">
-              <ElButton type="success" plain v-roles="['R_SUPER', 'R_ADMIN', 'R_USER']">
-                所有用户可见
-              </ElButton>
+              <ElButton type="success" plain v-roles="['R_SUPER', 'R_ADMIN', 'R_USER']"> 所有用户可见 </ElButton>
               <div class="button-desc">
                 <code>v-roles="['R_SUPER', 'R_ADMIN', 'R_USER']"</code>
                 <span>所有已登录用户可见</span>
@@ -102,8 +88,7 @@
         <div class="demo-content">
           <p class="section-desc">
             <code>v-auth</code>
-            指令在后端模式下根据当前路由的 <code>meta.authList</code> 配置检查权限。
-            权限列表来源于路由元数据,适用于权限配置由后端统一管理的场景。
+            指令在后端模式下根据当前路由的 <code>meta.authList</code> 配置检查权限。 权限列表来源于路由元数据,适用于权限配置由后端统一管理的场景。
           </p>
 
           <div class="auth-config-info">
@@ -154,11 +139,7 @@
           <div class="mode-note">
             <ElAlert
               :type="isFrontendMode ? 'warning' : 'success'"
-              :title="
-                isFrontendMode
-                  ? '注意:当前为前端模式,v-auth 指令在后端模式下才生效'
-                  : '当前为后端模式,v-auth 指令正常工作'
-              "
+              :title="isFrontendMode ? '注意:当前为前端模式,v-auth 指令在后端模式下才生效' : '当前为后端模式,v-auth 指令正常工作'"
               show-icon
               :closable="false"
             />
@@ -180,8 +161,7 @@
         <div class="demo-content">
           <p class="section-desc">
             <code>hasAuth</code>
-            方法在前端模式下根据用户信息中的 <code>buttons</code> 字段检查权限。
-            权限列表存储在用户状态中,适用于权限完全由前端管理的场景。
+            方法在前端模式下根据用户信息中的 <code>buttons</code> 字段检查权限。 权限列表存储在用户状态中,适用于权限完全由前端管理的场景。
           </p>
 
           <div class="auth-config-info">
@@ -204,11 +184,7 @@
 
             <!-- 动态按钮状态 -->
             <div class="button-item">
-              <ElButton
-                :disabled="!hasAuth('publish')"
-                :type="hasAuth('publish') ? 'success' : 'info'"
-                @click="handlePublish"
-              >
+              <ElButton :disabled="!hasAuth('publish')" :type="hasAuth('publish') ? 'success' : 'info'" @click="handlePublish">
                 {{ hasAuth('publish') ? '发布' : '无发布权限' }}
               </ElButton>
               <div class="button-desc">
@@ -228,15 +204,9 @@
                 </ElButton>
                 <template #dropdown>
                   <ElDropdownMenu>
-                    <ElDropdownItem command="batchEdit" :disabled="!hasAuth('edit')">
-                      批量编辑
-                    </ElDropdownItem>
-                    <ElDropdownItem command="batchDelete" :disabled="!hasAuth('delete')">
-                      批量删除
-                    </ElDropdownItem>
-                    <ElDropdownItem command="batchExport" :disabled="!hasAuth('export')">
-                      批量导出
-                    </ElDropdownItem>
+                    <ElDropdownItem command="batchEdit" :disabled="!hasAuth('edit')"> 批量编辑 </ElDropdownItem>
+                    <ElDropdownItem command="batchDelete" :disabled="!hasAuth('delete')"> 批量删除 </ElDropdownItem>
+                    <ElDropdownItem command="batchExport" :disabled="!hasAuth('export')"> 批量导出 </ElDropdownItem>
                   </ElDropdownMenu>
                 </template>
               </ElDropdown>
@@ -264,19 +234,13 @@
           <p class="section-desc">
             <code>hasAuth</code>
             方法在后端模式下根据当前路由的
-            <code>meta.authList</code> 配置检查权限。与 v-auth
-            指令使用相同的权限来源,但提供编程式的权限检查能力。
+            <code>meta.authList</code> 配置检查权限。与 v-auth 指令使用相同的权限来源,但提供编程式的权限检查能力。
           </p>
 
           <div class="button-group">
             <!-- 动态功能切换 -->
             <div class="button-item">
-              <ElSwitch
-                v-model="dynamicFeatureEnabled"
-                :disabled="!hasAuth('config')"
-                active-text="功能开启"
-                inactive-text="功能关闭"
-              />
+              <ElSwitch v-model="dynamicFeatureEnabled" :disabled="!hasAuth('config')" active-text="功能开启" inactive-text="功能关闭" />
               <div class="button-desc">
                 <code>:disabled="!hasAuth('config')"</code>
                 <span>检查路由权限控制功能开关</span>
@@ -302,22 +266,13 @@
             <!-- 权限状态指示器 -->
             <div class="button-item">
               <div class="permission-indicators">
-                <ElBadge
-                  :value="hasAuth('add') ? '✓' : '✗'"
-                  :type="hasAuth('add') ? 'success' : 'danger'"
-                >
+                <ElBadge :value="hasAuth('add') ? '✓' : '✗'" :type="hasAuth('add') ? 'success' : 'danger'">
                   <ElButton size="small">新增权限</ElButton>
                 </ElBadge>
-                <ElBadge
-                  :value="hasAuth('edit') ? '✓' : '✗'"
-                  :type="hasAuth('edit') ? 'success' : 'danger'"
-                >
+                <ElBadge :value="hasAuth('edit') ? '✓' : '✗'" :type="hasAuth('edit') ? 'success' : 'danger'">
                   <ElButton size="small">编辑权限</ElButton>
                 </ElBadge>
-                <ElBadge
-                  :value="hasAuth('delete') ? '✓' : '✗'"
-                  :type="hasAuth('delete') ? 'success' : 'danger'"
-                >
+                <ElBadge :value="hasAuth('delete') ? '✓' : '✗'" :type="hasAuth('delete') ? 'success' : 'danger'">
                   <ElButton size="small">删除权限</ElButton>
                 </ElBadge>
               </div>

+ 3 - 10
src/views/examples/permission/page-visibility/index.vue

@@ -3,10 +3,7 @@
     <!-- 页面头部 -->
     <div class="page-header">
       <h2>{{ $t('menus.examples.permission.pageVisibility') }}</h2>
-      <p class="description">
-        此页面仅对<strong>超级管理员</strong>用户可见,演示页面级别的权限控制。
-        如果您能看到此页面,说明您拥有相应的访问权限。
-      </p>
+      <p class="description"> 此页面仅对<strong>超级管理员</strong>用户可见,演示页面级别的权限控制。 如果您能看到此页面,说明您拥有相应的访问权限。 </p>
     </div>
 
     <div class="success-card">
@@ -63,9 +60,7 @@
                 <p><strong>权限验证流程:</strong></p>
                 <ul>
                   <li>用户登录后,接口返回用户角色信息(如 R_SUPER、R_ADMIN、R_USER)</li>
-                  <li>
-                    在 <code>beforeEach</code> 路由守卫中检查目标路由的 <code>roles</code> 配置
-                  </li>
+                  <li> 在 <code>beforeEach</code> 路由守卫中检查目标路由的 <code>roles</code> 配置 </li>
                   <li>比较用户角色是否包含在允许访问的角色列表中</li>
                   <li>权限不足时跳转到 403 页面</li>
                 </ul>
@@ -75,9 +70,7 @@
             <ElTimelineItem timestamp="后端控制模式" type="warning" size="large">
               <ElCard shadow="never">
                 <h4>基于菜单接口的权限控制</h4>
-                <p
-                  >在后端控制模式下,页面访问权限由后端统一管理,前端通过解析后端接口返回的菜单列表来生成可访问的路由,从而实现权限控制</p
-                >
+                <p>在后端控制模式下,页面访问权限由后端统一管理,前端通过解析后端接口返回的菜单列表来生成可访问的路由,从而实现权限控制</p>
                 <p>接口地址:src/api/menuApi.ts getMenuList</p>
                 <pre><code>
 {

+ 4 - 24
src/views/examples/permission/switch-role/index.vue

@@ -3,9 +3,7 @@
     <!-- 页面头部 -->
     <div class="page-header">
       <h2>权限切换演示</h2>
-      <p class="description">
-        点击下方按钮切换不同用户身份,模拟不同用户登录系统的效果。切换后会影响整个系统的菜单显示和按钮权限。
-      </p>
+      <p class="description"> 点击下方按钮切换不同用户身份,模拟不同用户登录系统的效果。切换后会影响整个系统的菜单显示和按钮权限。 </p>
     </div>
 
     <!-- 当前用户信息 -->
@@ -31,13 +29,7 @@
             <div class="info-item">
               <span class="label">权限码:</span>
               <div class="buttons-list">
-                <ElTag
-                  v-for="button in currentUser.buttons"
-                  :key="button"
-                  size="small"
-                  type="info"
-                  class="button-tag"
-                >
+                <ElTag v-for="button in currentUser.buttons" :key="button" size="small" type="info" class="button-tag">
                   {{ button }}
                 </ElTag>
                 <span v-if="!currentUser.buttons?.length" class="no-data">无权限码</span>
@@ -57,12 +49,7 @@
           </div>
         </template>
         <div class="role-buttons">
-          <div
-            v-for="account in accounts"
-            :key="account.key"
-            class="role-card"
-            :class="{ active: currentUser.userName === account.userName }"
-          >
+          <div v-for="account in accounts" :key="account.key" class="role-card" :class="{ active: currentUser.userName === account.userName }">
             <div class="role-info">
               <div class="role-details">
                 <h4>{{ account.label }}</h4>
@@ -74,14 +61,7 @@
               </div>
             </div>
             <div class="role-actions">
-              <ElButton
-                v-if="currentUser.userName !== account.userName"
-                type="primary"
-                @click="switchRole(account)"
-                :loading="switching"
-              >
-                切换到此角色
-              </ElButton>
+              <ElButton v-if="currentUser.userName !== account.userName" type="primary" @click="switchRole(account)" :loading="switching"> 切换到此角色 </ElButton>
               <ElTag v-else type="success">当前用户</ElTag>
             </div>
           </div>

+ 16 - 58
src/views/examples/tables/index.vue

@@ -17,8 +17,7 @@
       </template>
       <div class="intro-content">
         <p class="intro-text">
-          集成搜索、刷新、全屏、大小控制、列显示隐藏、拖拽排序、表格样式控制、并内置 useTable
-          组合式函数,提供强大的组合式 API,集成数据获取、智能缓存(LRU算法)、
+          集成搜索、刷新、全屏、大小控制、列显示隐藏、拖拽排序、表格样式控制、并内置 useTable 组合式函数,提供强大的组合式 API,集成数据获取、智能缓存(LRU算法)、
           多种刷新策略等核心功能,全面提升表格开发效率。
         </p>
 
@@ -100,9 +99,7 @@
                 </div>
                 <div class="stat-item request-params">
                   <span class="label">当前请求参数:</span>
-                  <ElText tag="pre" class="params-display">{{
-                    JSON.stringify(requestParams, null, 2)
-                  }}</ElText>
+                  <ElText tag="pre" class="params-display">{{ JSON.stringify(requestParams, null, 2) }}</ElText>
                 </div>
                 <div class="debug-actions">
                   <ElButton size="small" @click="handleCancelRequest">取消请求</ElButton>
@@ -151,13 +148,7 @@
 
       <!-- 表格工具栏 -->
       <!-- fullClass 属性用于设置全屏区域,如果需要设置全屏区域,请使用此属性 -->
-      <ArtTableHeader
-        v-model:columns="columnChecks"
-        :loading="loading"
-        @refresh="handleRefresh"
-        layout="refresh,size,fullscreen,columns,settings"
-        fullClass="art-table-card"
-      >
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="handleRefresh" layout="refresh,size,fullscreen,columns,settings" fullClass="art-table-card">
         <template #left>
           <ElButton type="primary" @click="handleAdd" v-ripple>
             <ElIcon>
@@ -167,19 +158,8 @@
           </ElButton>
 
           <!-- 导出导入功能 -->
-          <ArtExcelExport
-            :data="data as any"
-            :columns="exportColumns as any"
-            filename="用户数据"
-            :auto-index="true"
-            button-text="导出"
-            @export-success="handleExportSuccess"
-          />
-          <ArtExcelImport
-            @import-success="handleImportSuccess"
-            @import-error="handleImportError"
-            style="margin: 0 12px"
-          />
+          <ArtExcelExport :data="data as any" :columns="exportColumns as any" filename="用户数据" :auto-index="true" button-text="导出" @export-success="handleExportSuccess" />
+          <ArtExcelImport @import-success="handleImportSuccess" @import-error="handleImportError" style="margin: 0 12px" />
 
           <ElButton @click="handleClearData" plain v-ripple> 清空数据 </ElButton>
 
@@ -283,12 +263,7 @@
                 </ElIcon>
               </div>
             </template>
-            <ElInput
-              v-model="phoneSearch"
-              placeholder="搜索手机号"
-              size="small"
-              @input="handlePhoneSearch"
-            >
+            <ElInput v-model="phoneSearch" placeholder="搜索手机号" size="small" @input="handlePhoneSearch">
               <template #prefix>
                 <ElIcon>
                   <Search />
@@ -310,9 +285,7 @@
         <div class="demo-group">
           <h5>📊 事件监听演示</h5>
           <div class="demo-buttons">
-            <ElButton @click="toggleEventDemo" :type="eventDemoEnabled ? 'success' : 'primary'">
-              {{ eventDemoEnabled ? '关闭' : '开启' }}事件监听
-            </ElButton>
+            <ElButton @click="toggleEventDemo" :type="eventDemoEnabled ? 'success' : 'primary'"> {{ eventDemoEnabled ? '关闭' : '开启' }}事件监听 </ElButton>
             <ElButton @click="clearEventLogs" v-if="eventDemoEnabled">清空日志</ElButton>
           </div>
           <div v-if="eventDemoEnabled && eventLogs.length > 0" class="event-logs">
@@ -334,12 +307,7 @@
         <div class="demo-group">
           <h5>⚙️ 表格配置演示</h5>
           <div class="demo-buttons">
-            <ElSwitch
-              v-model="tableConfig.fixedHeight"
-              active-text="固定高度 (500px)"
-              inactive-text="自适应高度"
-              style="margin-left: 10px"
-            />
+            <ElSwitch v-model="tableConfig.fixedHeight" active-text="固定高度 (500px)" inactive-text="自适应高度" style="margin-left: 10px" />
           </div>
         </div>
 
@@ -746,9 +714,7 @@
         return records.map((item: any, index: number) => ({
           ...item,
           avatar: ACCOUNT_TABLE_DATA[index % ACCOUNT_TABLE_DATA.length].avatar,
-          department: ['技术部', '产品部', '运营部', '市场部', '设计部'][
-            Math.floor(Math.random() * 5)
-          ],
+          department: ['技术部', '产品部', '运营部', '市场部', '设计部'][Math.floor(Math.random() * 5)],
           score: Math.floor(Math.random() * 5) + 1,
           status: ['1', '2', '3', '4'][Math.floor(Math.random() * 4)]
         }))
@@ -778,9 +744,7 @@
       onSuccess: (data, response) => {
         console.log('📊 响应详情:', response)
         addCacheLog(`✅ 网络请求成功: ${data.length} 条数据`)
-        addCacheLog(
-          `📝 响应信息: total=${response.total}, current=${response.current}, size=${response.size}`
-        )
+        addCacheLog(`📝 响应信息: total=${response.total}, current=${response.current}, size=${response.size}`)
       },
       onError: (error) => {
         console.error('❌ 数据加载失败:', error)
@@ -790,9 +754,7 @@
       onCacheHit: (data, response) => {
         console.log('🎯 缓存命中:', data.length, '条')
         console.log('🔑 缓存来源:', response)
-        addCacheLog(
-          `🎯 缓存命中: ${data.length} 条数据 (current=${response.current}, size=${response.size})`
-        )
+        addCacheLog(`🎯 缓存命中: ${data.length} 条数据 (current=${response.current}, size=${response.size})`)
         ElMessage.info('数据来自缓存')
       },
       resetFormCallback: () => {
@@ -974,15 +936,11 @@
 
   const handleBatchDelete = async () => {
     try {
-      await ElMessageBox.confirm(
-        `确定要删除选中的 ${selectedRows.value.length} 个用户吗?`,
-        '警告',
-        {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning'
-        }
-      )
+      await ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 个用户吗?`, '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
 
       ElMessage.success(`批量删除 ${selectedRows.value.length} 个用户成功`)
       selectedRows.value = []

+ 4 - 25
src/views/examples/tables/tree.vue

@@ -15,25 +15,13 @@
           <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
             <template #left>
               <ElSpace wrap>
-                <ElButton @click="showButtons = !showButtons" v-ripple type="primary" plain
-                  >{{ showButtons ? '收起' : '展开' }}按钮组</ElButton
-                >
-                <ElButton v-show="showButtons" v-ripple v-for="value in 12" :key="value"
-                  >表格自适应</ElButton
-                >
+                <ElButton @click="showButtons = !showButtons" v-ripple type="primary" plain>{{ showButtons ? '收起' : '展开' }}按钮组</ElButton>
+                <ElButton v-show="showButtons" v-ripple v-for="value in 12" :key="value">表格自适应</ElButton>
               </ElSpace>
             </template>
           </ArtTableHeader>
 
-          <ArtTable
-            rowKey="id"
-            :loading="loading"
-            :data="data"
-            :columns="columns"
-            :pagination="pagination"
-            @pagination:size-change="handleSizeChange"
-            @pagination:current-change="handleCurrentChange"
-          >
+          <ArtTable rowKey="id" :loading="loading" :data="data" :columns="columns" :pagination="pagination" @pagination:size-change="handleSizeChange" @pagination:current-change="handleCurrentChange">
           </ArtTable>
         </ElCard>
       </div>
@@ -56,16 +44,7 @@
     name: undefined
   })
 
-  const {
-    data,
-    columns,
-    columnChecks,
-    loading,
-    pagination,
-    refreshData,
-    handleSizeChange,
-    handleCurrentChange
-  } = useTable({
+  const { data, columns, columnChecks, loading, pagination, refreshData, handleSizeChange, handleCurrentChange } = useTable({
     core: {
       apiFn: fetchGetUserList,
       apiParams: {

+ 4 - 15
src/views/examples/tabs/index.vue

@@ -5,16 +5,9 @@
     <!-- 修改标签标题模块 -->
     <ElCard class="module-card" header="修改标签标题" shadow="never">
       <div style="display: flex; gap: 10px">
-        <ElInput
-          v-model="newTabTitle"
-          placeholder="请输入新的标签页标题"
-          clearable
-          style="width: 300px"
-        />
+        <ElInput v-model="newTabTitle" placeholder="请输入新的标签页标题" clearable style="width: 300px" />
 
-        <ElButton type="primary" @click="handleUpdateTabTitle" :disabled="!newTabTitle.trim()">
-          修改
-        </ElButton>
+        <ElButton type="primary" @click="handleUpdateTabTitle" :disabled="!newTabTitle.trim()"> 修改 </ElButton>
         <ElButton @click="handleResetTabTitle"> 重置 </ElButton>
       </div>
     </ElCard>
@@ -26,9 +19,7 @@
       </div>
       <ElRow :gutter="20">
         <ElCol :span="24">
-          <ElButton type="success" plain @click="handleGetCurrentTabTitle(routePath)">
-            获取当前标签页信息
-          </ElButton>
+          <ElButton type="success" plain @click="handleGetCurrentTabTitle(routePath)"> 获取当前标签页信息 </ElButton>
         </ElCol>
       </ElRow>
     </ElCard>
@@ -38,9 +29,7 @@
       <ElRow :gutter="20">
         <ElCol :span="24">
           <ElButton type="danger" plain @click="handleCloseTab(routePath)"> 关闭当前标签 </ElButton>
-          <ElButton type="warning" plain @click="handleCloseOthersTab(routePath)">
-            关闭其他标签
-          </ElButton>
+          <ElButton type="warning" plain @click="handleCloseOthersTab(routePath)"> 关闭其他标签 </ElButton>
           <ElButton type="danger" plain @click="handleCloseAllTab"> 关闭所有标签 </ElButton>
         </ElCol>
       </ElRow>

+ 1 - 7
src/views/outside/Iframe.vue

@@ -1,12 +1,6 @@
 <template>
   <div class="iframe-container" v-loading="isLoading">
-    <iframe
-      ref="iframeRef"
-      :src="iframeUrl"
-      frameborder="0"
-      class="iframe-content"
-      @load="handleIframeLoad"
-    ></iframe>
+    <iframe ref="iframeRef" :src="iframeUrl" frameborder="0" class="iframe-content" @load="handleIframeLoad"></iframe>
   </div>
 </template>
 

+ 5 - 32
src/views/system/user-center/index.vue

@@ -50,26 +50,14 @@
         <div class="info box-style">
           <h1 class="title">基本设置</h1>
 
-          <ElForm
-            :model="form"
-            class="form"
-            ref="ruleFormRef"
-            :rules="rules"
-            label-width="86px"
-            label-position="top"
-          >
+          <ElForm :model="form" class="form" ref="ruleFormRef" :rules="rules" label-width="86px" label-position="top">
             <ElRow>
               <ElFormItem label="姓名" prop="realName">
                 <el-input v-model="form.realName" :disabled="!isEdit" />
               </ElFormItem>
               <ElFormItem label="性别" prop="sex" class="right-input">
                 <ElSelect v-model="form.sex" placeholder="Select" :disabled="!isEdit">
-                  <ElOption
-                    v-for="item in options"
-                    :key="item.value"
-                    :label="item.label"
-                    :value="item.value"
-                  />
+                  <ElOption v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
                 </ElSelect>
               </ElFormItem>
             </ElRow>
@@ -109,30 +97,15 @@
 
           <ElForm :model="pwdForm" class="form" label-width="86px" label-position="top">
             <ElFormItem label="当前密码" prop="password">
-              <ElInput
-                v-model="pwdForm.password"
-                type="password"
-                :disabled="!isEditPwd"
-                show-password
-              />
+              <ElInput v-model="pwdForm.password" type="password" :disabled="!isEditPwd" show-password />
             </ElFormItem>
 
             <ElFormItem label="新密码" prop="newPassword">
-              <ElInput
-                v-model="pwdForm.newPassword"
-                type="password"
-                :disabled="!isEditPwd"
-                show-password
-              />
+              <ElInput v-model="pwdForm.newPassword" type="password" :disabled="!isEditPwd" show-password />
             </ElFormItem>
 
             <ElFormItem label="确认新密码" prop="confirmPassword">
-              <ElInput
-                v-model="pwdForm.confirmPassword"
-                type="password"
-                :disabled="!isEditPwd"
-                show-password
-              />
+              <ElInput v-model="pwdForm.confirmPassword" type="password" :disabled="!isEditPwd" show-password />
             </ElFormItem>
 
             <div class="el-form-item-right">

+ 17 - 0
src/views/train/plan/index copy.vue

@@ -0,0 +1,17 @@
+<!--
+@description: 计划列表
+@creationTime: 2025-09-23
+-->
+
+<template>
+  <view>计划列表</view>
+</template>
+
+<script lang="ts" setup>
+  defineOptions({ name: '计划列表' })
+  /* ------------------------ 导入 与 引用 ----------------------------------- */
+  /* ------------------------ 函数 与 方法 ----------------------------------- */
+  /* ------------------------- 生命周期 -------------------------------------- */
+</script>
+
+<style lang="scss" scoped></style>

+ 354 - 12
src/views/train/plan/index.vue

@@ -1,17 +1,359 @@
-<!--
-@description: 计划列表
-@creationTime: 2025-09-23
--->
-
+<!-- 图标选择器 -->
 <template>
-  <view>计划列表</view>
+  <div class="icon-selector">
+    <div class="select" @click="handleClick" :style="{ width: props.width }" :class="[size, { 'is-disabled': disabled }, { 'has-icon': selectValue }]">
+      <div class="icon">
+        <i :class="`iconfont-sys ${selectValue}`" v-show="props.iconType === IconTypeEnum.CLASS_NAME"></i>
+        <i class="iconfont-sys" v-html="selectValue" v-show="props.iconType === IconTypeEnum.UNICODE"></i>
+        <Icon v-show="selectValue?.includes(':')" :icon="selectValue" />
+      </div>
+      <div class="text"> {{ props.text }} </div>
+      <div class="arrow">
+        <i class="iconfont-sys arrow-icon">&#xe709;</i>
+        <i class="iconfont-sys clear-icon" @click.stop="clearIcon">&#xe83a;</i>
+      </div>
+    </div>
+    <div class="flex gap-4 mt-4 items-center">
+      <i class="iconfont-sys">&#xe83a;</i>
+      <Icon :icon="'svg:download'" />
+      <Icon :icon="'svg:gitee'" />
+      <Icon :icon="'iconsys-tianjia1'" />
+      <Icon :icon="'&#xe66e;'" />
+      <Icon :icon="'&#xe83a;'" />
+    </div>
+
+    <el-dialog title="选择图标" width="40%" v-model="visible" align-center>
+      <el-tabs v-model="activeTab">
+        <el-tab-pane v-for="prefix in iconifyPrefixs" :key="prefix" :label="prefix" :name="prefix">
+          <el-scrollbar height="400px">
+            <el-input v-model="searchText" placeholder="请输入图标名称"></el-input>
+            <ul class="icons-list" style="margin-top: 10px">
+              <li v-for="icon in paginatedIconifyList" :key="icon" @click="selectorIconifyIcon(icon)">
+                <i class="iconfont-sys">
+                  <Icon :icon="icon" />
+                </i>
+              </li>
+            </ul>
+            <el-pagination
+              v-if="filteredIconifyList.length > iconifyPageSize"
+              layout="prev, pager, next"
+              :total="filteredIconifyList.length"
+              :page-size="iconifyPageSize"
+              v-model:current-page="iconifyCurrentPage"
+              size="small"
+              style="margin-top: 10px; justify-content: center"
+            />
+          </el-scrollbar>
+        </el-tab-pane>
+        <el-tab-pane label="Svg图标" name="svg">
+          <el-scrollbar height="400px">
+            <ul class="icons-list">
+              <li v-for="icon in svgList" :key="icon" @click="selectorIconifyIcon(icon)">
+                <i class="iconfont-sys">
+                  <Icon :icon="icon" />
+                </i>
+              </li>
+            </ul>
+          </el-scrollbar>
+        </el-tab-pane>
+        <el-tab-pane label="原内置图标" name="icons">
+          <el-scrollbar height="400px">
+            <ul class="icons-list" v-show="activeName === 'icons'">
+              <li v-for="icon in iconsList" :key="icon.className" @click="selectorIcon(icon)">
+                <span class="text" v-if="iconType === IconTypeEnum.CLASS_NAME">{{ icon.className }}</span>
+                <span class="text" v-if="iconType === IconTypeEnum.UNICODE">{{ icon.unicode }}</span>
+                <i :class="`iconfont-sys ${icon.className}`" v-show="iconType === IconTypeEnum.CLASS_NAME"></i>
+                <i class="iconfont-sys" v-html="icon.unicode" v-show="iconType === IconTypeEnum.UNICODE"></i>
+              </li>
+            </ul>
+          </el-scrollbar>
+        </el-tab-pane>
+      </el-tabs>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="visible = false">取 消</el-button>
+          <el-button type="primary" @click="visible = false">确 定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
 </template>
 
-<script lang="ts" setup>
-  defineOptions({ name: '计划列表' })
-  /* ------------------------ 导入 与 引用 ----------------------------------- */
-  /* ------------------------ 函数 与 方法 ----------------------------------- */
-  /* ------------------------- 生命周期 -------------------------------------- */
+<script setup lang="ts">
+  import { IconTypeEnum } from '@/enums/appEnum'
+  import { extractIconClasses, type IconfontType } from '@/utils/constants'
+  import { Icon } from '@/components/icon'
+  import { listIcons } from '@iconify/vue'
+
+  defineOptions({ name: 'ArtIconSelector' })
+  const searchText = ref('')
+  const activeTab = ref('ant-design')
+  const iconifyCurrentPage = ref(1)
+  const iconifyPageSize = ref(60)
+
+  const svgList = computed(() => {
+    return listIcons('', 'svg')
+  })
+  const iconifyMap = reactive<any>({})
+  const iconifyPrefixs = import.meta.env.VITE_APP_ICONIFY_PREFIX?.split(',') || []
+  const iconifyList = computed(() => {
+    return iconifyMap[activeTab.value]
+  })
+  onMounted(() => {
+    iconifyPrefixs.forEach((prefix: any) => {
+      const icons = listIcons('', prefix)
+      iconifyMap[prefix] = icons
+    })
+  })
+
+  // 过滤后的图标列表(搜索功能)
+  const filteredIconifyList = computed<string[]>(() => {
+    if (!searchText.value) {
+      return iconifyList.value || []
+    }
+    return (iconifyList.value || []).filter((icon: any) => icon.includes(searchText.value))
+  })
+
+  // 分页后的图标列表
+  const paginatedIconifyList = computed<string[]>(() => {
+    const start = (iconifyCurrentPage.value - 1) * iconifyPageSize.value
+    const end = start + iconifyPageSize.value
+    return filteredIconifyList.value.slice(start, end)
+  })
+  // 组件大小类型
+  type ComponentSize = 'large' | 'default' | 'small'
+
+  // Props 接口定义
+  interface Props {
+    /** 图标类型 */
+    iconType?: IconTypeEnum
+    /** v-model 绑定的图标值 */
+    modelValue?: string
+    /** 显示文本 */
+    text?: string
+    /** 组件宽度 */
+    width?: string
+    /** 组件大小 */
+    size?: ComponentSize
+    /** 是否禁用 */
+    disabled?: boolean
+  }
+
+  // Emits 接口定义
+  interface Emits {
+    'update:modelValue': [value: string]
+    getIcon: [value: string]
+  }
+
+  // 使用 withDefaults 定义 props
+  const props = withDefaults(defineProps<Props>(), {
+    iconType: IconTypeEnum.CLASS_NAME,
+    modelValue: '',
+    text: '图标选择器',
+    width: '200px',
+    size: 'default',
+    disabled: false
+  })
+
+  // 定义 emits
+  const emits = defineEmits<Emits>()
+
+  // 响应式数据
+  const selectValue = ref<string>(props.modelValue)
+  const visible = ref<boolean>(false)
+  const activeName = ref<string>('icons')
+
+  // 图标列表 - 使用计算属性优化性能
+  const iconsList = computed<IconfontType[]>(() => extractIconClasses())
+
+  // 监听 modelValue 变化
+  watch(
+    () => props.modelValue,
+    (newVal: string) => {
+      selectValue.value = newVal
+    },
+    { immediate: true }
+  )
+
+  // 当搜索条件变化时,重置到第一页
+  watch(searchText, () => {
+    iconifyCurrentPage.value = 1
+  })
+
+  // 选择图标
+  const selectorIcon = (icon: IconfontType): void => {
+    const iconValue = props.iconType === IconTypeEnum.CLASS_NAME ? icon.className : icon.unicode || ''
+
+    selectValue.value = iconValue
+    visible.value = false
+
+    // 发射 v-model 更新事件和自定义事件
+    emits('update:modelValue', iconValue)
+    emits('getIcon', iconValue)
+  }
+  // 选择图标
+  const selectorIconifyIcon = (icon: string): void => {
+    selectValue.value = icon
+    visible.value = false
+
+    // 发射 v-model 更新事件和自定义事件
+    emits('update:modelValue', icon)
+    emits('getIcon', icon)
+  }
+
+  // 处理点击事件
+  const handleClick = (): void => {
+    if (!props.disabled) {
+      visible.value = true
+      // 重置分页和搜索条件
+      iconifyCurrentPage.value = 1
+      searchText.value = ''
+    }
+  }
+
+  // 清除图标
+  const clearIcon = (): void => {
+    selectValue.value = ''
+
+    // 发射 v-model 更新事件和自定义事件
+    emits('update:modelValue', '')
+    emits('getIcon', '')
+  }
+
+  // 计算属性:当前图标类型(用于模板中的判断)
+  const iconType = computed<IconTypeEnum>(() => props.iconType)
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+  .icon-selector {
+    width: 100%;
+
+    .select {
+      box-sizing: border-box;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      height: var(--el-component-custom-height);
+      padding: 0 15px;
+      cursor: pointer;
+      border: 1px solid var(--art-border-dashed-color);
+      border-radius: calc(var(--custom-radius) / 3 + 2px) !important;
+      transition: border 0.3s;
+
+      @media (width <= 500px) {
+        width: 100% !important;
+      }
+
+      &.large {
+        height: 40px;
+      }
+
+      &.small {
+        height: 24px;
+      }
+
+      &:hover:not(.is-disabled).has-icon {
+        .arrow-icon {
+          display: none;
+        }
+
+        .clear-icon {
+          display: block !important;
+        }
+      }
+
+      &:hover {
+        border-color: var(--art-text-gray-400);
+      }
+
+      .icon {
+        display: flex;
+        align-items: center;
+        width: 20px;
+        color: var(--art-gray-700);
+
+        i {
+          display: block;
+          margin: 0 auto;
+          font-size: 16px;
+        }
+      }
+
+      .text {
+        display: flex;
+        display: inline-block;
+        align-items: center;
+        width: 50%;
+        font-size: 14px;
+        color: var(--art-gray-600);
+
+        @include ellipsis();
+
+        @media (width <= 500px) {
+          display: none;
+        }
+      }
+
+      .arrow {
+        display: flex;
+        align-items: center;
+        height: calc(100% - 2px);
+
+        i {
+          font-size: 13px;
+          color: var(--art-gray-600);
+        }
+
+        .clear-icon {
+          display: none;
+        }
+      }
+
+      &.is-disabled {
+        cursor: not-allowed;
+        background-color: var(--el-disabled-bg-color);
+        border-color: var(--el-border-color-lighter);
+
+        .icon,
+        .text,
+        .arrow {
+          color: var(--el-text-color-placeholder);
+        }
+
+        &:hover {
+          border-color: var(--el-border-color-lighter);
+        }
+      }
+    }
+
+    .icons-list {
+      display: grid;
+      grid-template-columns: repeat(10, 1fr);
+      border-top: 1px solid var(--art-border-color);
+      border-left: 1px solid var(--art-border-color);
+
+      li {
+        box-sizing: border-box;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        aspect-ratio: 1 / 1;
+        color: var(--art-gray-600);
+        text-align: center;
+        border-right: 1px solid var(--art-border-color);
+        border-bottom: 1px solid var(--art-border-color);
+        align-items: center;
+
+        &:hover {
+          cursor: pointer;
+          background: var(--art-gray-100);
+        }
+
+        i {
+          font-size: 22px;
+          color: var(--art-gray-800);
+        }
+      }
+    }
+  }
+</style>

+ 2 - 0
vite.config.ts

@@ -9,6 +9,7 @@ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
 import { fileURLToPath } from 'url'
 import vueDevTools from 'vite-plugin-vue-devtools'
 // import { visualizer } from 'rollup-plugin-visualizer'
+import tailwindcss from '@tailwindcss/vite'
 
 export default ({ mode }: { mode: string }) => {
   const root = process.cwd()
@@ -66,6 +67,7 @@ export default ({ mode }: { mode: string }) => {
     plugins: [
       vue(),
       vueJsx(),
+      tailwindcss(),
       // 自动导入 components 目录下的组件
       Components({
         deep: true,