GeneralAdmin

项目搭建

创建项目

perl 复制代码
//====第一步创建项目========================
1.npm init vue
2.输入项目名称
3.选择ts语法,router,pinia
4.npm i
5.npm run dev

//====第二步进入项目删除无用的代码,保证页面是空白的
保留 src/assets/base.css 

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
}

//将这个引入到main.js中,这个放在最上面,初始化这个模块

//安装必要的依赖
npm install --save-dev @arco-design/web-vue
npm i axios
npm i imockjs

//安装less 因为要预留arco的主题色,所以使用less作为css的预处理会更加方便

npm install less -D  
npm install less-loader -D

main.ts

javascript 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import "@/assets/base.css"//引入初始化样式(放在最前面)
import App from './App.vue'
import router from './router'
import ArcoVue from '@arco-design/web-vue'; //引入arco_design
import '@arco-design/web-vue/dist/arco.css';//引入arco_design 样式
import ArcoVueIcon from '@arco-design/web-vue/es/icon'; //引入arco_design 图标
const app = createApp(App)

app.use(createPinia())
app.use(router)
app.use(ArcoVue); //注册arco_design
app.use(ArcoVueIcon);//注册arco_design图标
app.mount('#app')

路由构建

src\router\index.ts

javascript 复制代码
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      name:"home",
      path:"/",
      redirect:"/admin"
    },
    {
      name:"web",
      path:"/web",
      component:()=>import("@/views/web/index.vue")
    },
    {
      name:"login",
      path:"/login",
      component:()=>import("@/views/login/index.vue")
    },
    {
      name:"admin",
      path:"/admin",
      component:()=>import("@/views/admin/index.vue")
    }
  ],
})

export default router

创建目录

bash 复制代码
└─views
    ├─admin
    ├─login
    └─web

项目设计

admin样式初调

src\views\admin\index.vue

javascript 复制代码
<script setup lang='ts'>
import { RouterView } from 'vue-router';


</script>

<template>
    <div class="g_admin">
        <div class="g_aside">
            <div class="g_logo"></div>
            <div class="g_menu"></div>
        </div>
        <div class="g_main">
            <div class="g_head">
                <div class="g_breadcrumbs"></div>
                <div class="g_actions">
                    <icon-home />
                    <icon-sun-fill />
                    <icon-moon-fill />
                    <icon-fullscreen />
                    <icon-fullsreen-exit />
                    <div class="g_user_info_action">

                    </div>

                </div>
            </div>
            <div class="g_tabs">

            </div>
            <div class="g_container">
                <RouterView></RouterView>
            </div>
        </div>
    </div>
</template>

<style lang='less'>
.g_admin {
    display: flex;

    .g_aside {
        width: 240px;
        height: 100vh;
        overflow-y: auto;
        overflow-x: hidden;
        border-right: 1px solid var(--color-neutral-2);

        .g_logo {
            width: 100%;
            height: 90px;
            border-bottom: 1px solid var(--color-neutral-2);
        }
    }

    .g_main {
        width: calc(100% - 240px);

        .g_head {
            width: 100%;
            height: 60px;
            border-bottom: 1px solid var(--color-neutral-2);
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0 20px;
        }

        .g_tabs {
            width: 100%;
            height: 30px;
            border-bottom: 1px solid var(--color-neutral-2);
        }

        .g_container {
            width: 100%;
            height: calc(100vh - 90px);
            overflow-y: auto;
            overflow-x: hidden;
        }
    }
}</style>

使用arco desgin的less变量

  • 在vite.config.ts中配置vite帮忙预处理,可以配置处理lessyu
javascript 复制代码
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueDevTools(),],
    // 配置less预处理
    css:{
      preprocessorOptions:{
        less:{
          additionalData:'@import "@arco-design/web-vue/es/style/theme/global.less";',
          javascriptEnabled:true,
        }
      }
    },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    },
  },
})
  • 使用,首先可以使用变量直接代替原来的元素配置,--color-neutral-2是arco-desigin的变量语法
javascript 复制代码
    .g_aside {
        width: 240px;
        height: 100vh;
        overflow-y: auto;
        overflow-x: hidden;
        border-right: 1px solid @color-border-1;

        .g_logo {
            width: 100%;
            height: 90px;
            border-bottom: 1px solid var(--color-neutral-2);
        }
    }
  • 也可以提取出来将border-right: 1px solid @color-border-1; 作为一个完整的变量,这样就直接使用一个简介明了的变量名称就可
javascript 复制代码
<style lang='less'>
@g_border:1px solid @color-border-1;
.g_admin {
    display: flex;

    .g_aside {
        width: 240px;
        height: 100vh;
        overflow-y: auto;
        overflow-x: hidden;
        border-right: @g_border;

        .g_logo {
            width: 100%;
            height: 90px;
            border-bottom: 1px solid var(--color-neutral-2);
        }
    }
  • 但是我们将这个提取到变量中,这样就可以全局使用这个变量,方便代码在全局中使用

src\assets\var.less【封装这个变量在这个文件中】

javascript 复制代码
@import "@arco-design/web-vue/es/style/theme/global.less";

@g_border:1px solid @color-border-1;

src\views\admin\index.vue【在实际的代码中使用只用写这个变量名即可】

javascript 复制代码
<script setup lang='ts'>
import { RouterView } from 'vue-router';


</script>

<template>
    <div class="g_admin">
        <div class="g_aside">
            <div class="g_logo"></div>
            <div class="g_menu"></div>
        </div>
        <div class="g_main">
            <div class="g_head">
                <div class="g_breadcrumbs"></div>
                <div class="g_actions">
                    <icon-home />
                    <icon-sun-fill />
                    <icon-moon-fill />
                    <icon-fullscreen />
                    <icon-fullsreen-exit />
                    <div class="g_user_info_action">

                    </div>

                </div>
            </div>
            <div class="g_tabs">

            </div>
            <div class="g_container">
                <RouterView></RouterView>
            </div>
        </div>
    </div>
</template>

<style lang='less'>
.g_admin {
    display: flex;

    .g_aside {
        width: 240px;
        height: 100vh;
        overflow-y: auto;
        overflow-x: hidden;
        border-right: @g_border;

        .g_logo {
            width: 100%;
            height: 90px;
            border-bottom: @g_border;
        }
    }

    .g_main {
        width: calc(100% - 240px);

        .g_head {
            width: 100%;
            height: 60px;
            border-bottom: @g_border;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0 20px;
        }

        .g_tabs {
            width: 100%;
            height: 30px;
            border-bottom: @g_border;
        }

        .g_container {
            width: 100%;
            height: calc(100vh - 90px);
            overflow-y: auto;
            overflow-x: hidden;
        }
    }
}</style>

vite.config.ts【记得修改配置文件将预处理的文件路径进行修改】

javascript 复制代码
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueDevTools(),],
    // 配置less预处理
    css:{
      preprocessorOptions:{
        less:{
          modifyVars:{
            // 'primary-6':"red"  //修改arco-disgin的主题色,默认是蓝色
          },
          additionalData:'@import "@/assets/var.less";',
          javascriptEnabled:true,
        }
      }
    },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    },
  },
})

主题切换

src\views\admin\index.vue【已可以实现出题切换】

javascript 复制代码
<script setup lang='ts'>
import { RouterView } from 'vue-router';
import { ref } from 'vue';
const theme = ref('')
// setTheme 切换主题色函数,根据arco-disgin的特性,将arco-theme设置成dark就是默认是黑夜模式

function setTheme(val: string){
    if (val === 'dark'){
        document.body.setAttribute('arco-theme','dark')
    }else{
        document.body.removeAttribute('arco-theme')
    }
    theme.value = val
}

</script>

<template>
    <div class="g_admin">
        <div class="g_aside">
            <div class="g_logo"></div>
            <div class="g_menu"></div>
        </div>
        <div class="g_main">
            <div class="g_head">
                <div class="g_breadcrumbs"></div>
                <div class="g_actions">
                    <icon-home />
                    <icon-sun-fill v-if="theme === 'dark'" @click="setTheme('')"/>
                    <icon-moon-fill v-if="theme === ''" @click="setTheme('dark')"/>
                    <icon-fullscreen />
                    <icon-fullsreen-exit />
                    <div class="g_user_info_action">

                    </div>

                </div>
            </div>
            <div class="g_tabs">

            </div>
            <div class="g_container">
                <RouterView></RouterView>
            </div>
        </div>
    </div>
</template>

<style lang='less'>
.g_admin {
    display: flex;
    background-color: var(--color-bg-1); //设置背景主题色
    color: @color-text-1; //设置文本颜色

    .g_aside {
        width: 240px;
        height: 100vh;
        overflow-y: auto;
        overflow-x: hidden;
        border-right: @g_border;
     

        .g_logo {
            width: 100%;
            height: 90px;
            border-bottom: @g_border;
        }
    }

    .g_main {
        width: calc(100% - 240px);

        .g_head {
            width: 100%;
            height: 60px;
            border-bottom: @g_border;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0 20px;
        }

        .g_tabs {
            width: 100%;
            height: 30px;
            border-bottom: @g_border;
        }

        .g_container {
            width: 100%;
            height: calc(100vh - 90px);
            overflow-y: auto;
            overflow-x: hidden;
            background-color: @color-fill-2; //背景色,颜色稍微和外面的背景色有少许的不一样
        }
    }
}</style>

主题切换代码抽离并暴露出去

src\components\common\g_theme.ts【将主题切换函数封装入ts中并暴露出去供其他调用】

javascript 复制代码
import { ref } from 'vue';
export const theme = ref('')
// setTheme 切换主题色函数,根据arco-disgin的特性,将arco-theme设置成dark就是默认是黑夜模式
export function setTheme(val: string){
    if (val === 'dark'){
        document.body.setAttribute('arco-theme','dark')
    }else{
        document.body.removeAttribute('arco-theme')
    }
    theme.value = val
    localStorage.setItem("g_theme",val) //保存到缓存中
}
// loadTheme 获取主题色函数
export function loadTheme(){
    const val = localStorage.getItem("g_theme") //从缓存中获取主题
    if (val){
        if (val === 'dark'){
            theme.value = val
            setTheme(val) //切换主题
        }
    }
}
loadTheme() //执行函数

src\components\common\g_theme.vue【主题切换样式】

javascript 复制代码
<script setup lang='ts'>
import { theme,setTheme,loadTheme } from './g_theme';
</script>

<template>
    <!-- title在span标签中才会在页面中展示,所有在外层套一个span标签 -->
<span v-if="theme === 'dark'" title="白天模式">   <icon-sun-fill  @click="setTheme('')"/></span>
<span v-if="theme === ''" title="黑夜模式"><icon-moon-fill @click="setTheme('dark')"/></span>
    
</template>

<style scoped>

</style>

src\views\admin\index.vue【使用g_theme组件】

javascript 复制代码
<script setup lang='ts'>
import { RouterView } from 'vue-router';
import { ref } from 'vue';
import G_theme from '../../components/common/g_theme.vue';


</script>

<template>
    <div class="g_admin">
        <div class="g_aside">
            <div class="g_logo"></div>
            <div class="g_menu"></div>
        </div>
        <div class="g_main">
            <div class="g_head">
                <div class="g_breadcrumbs"></div>
                <div class="g_actions">
                    <icon-home />
                   <G_theme></G_theme>
                    <icon-fullscreen />
                    <icon-fullsreen-exit />
                    <div class="g_user_info_action">

                    </div>

                </div>
            </div>
            <div class="g_tabs">

            </div>
            <div class="g_container">
                <RouterView></RouterView>
            </div>
        </div>
    </div>
</template>

<style lang='less'>
.g_admin {
    display: flex;
    background-color: @color-fill-1; //设置背景主题色
    color: @color-text-1; //设置文本颜色

    .g_aside {
        width: 240px;
        height: 100vh;
        overflow-y: auto;
        overflow-x: hidden;
        border-right: @g_border;
     

        .g_logo {
            width: 100%;
            height: 90px;
            border-bottom: @g_border;
        }
    }

    .g_main {
        width: calc(100% - 240px);

        .g_head {
            width: 100%;
            height: 60px;
            border-bottom: @g_border;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0 20px;
        }

        .g_tabs {
            width: 100%;
            height: 30px;
            border-bottom: @g_border;
        }

        .g_container {
            width: 100%;
            height: calc(100vh - 90px);
            overflow-y: auto;
            overflow-x: hidden;
        }
    }
}</style>

全屏切换

src\components\common\g_screen.vue

javascript 复制代码
<script setup lang='ts'>
import { ref } from "vue"

const isFullSreen = ref(false)
//全屏
function fullScreen() {
    document.documentElement?.requestFullscreen()
    isFullSreen.value = true
}
//退出全屏
function exitFullScreen() {
    document?.exitFullscreen()
    isFullSreen.value = false
}
</script>

<template>
    <!-- title在span标签中才会在页面中展示,所有在外层套一个span标签 -->
    <span v-if="!isFullSreen" title="全屏"> <icon-fullscreen  @click="fullScreen" /></span>
    <span v-else title="退出全屏"> <icon-fullscreen-exit @click="exitFullScreen" /></span>
   
   
</template>

<style scoped lang='scss'></style>

然后调用

src\views\admin\index.vue

javascript 复制代码
<template>
    <div class="g_admin">
        <div class="g_aside">
            <div class="g_logo"></div>
            <div class="g_menu"></div>
        </div>
        <div class="g_main">
            <div class="g_head">
                <div class="g_breadcrumbs"></div>
                <div class="g_actions">
                    <icon-home />
                   <G_theme></G_theme>
                   <G_screen></G_screen>
                    <div class="g_user_info_action">

                    </div>

                </div>
            </div>
            <div class="g_tabs">

            </div>
            <div class="g_container">
                <RouterView></RouterView>
            </div>
        </div>
    </div>
</template>

菜单配置

【点击菜单进行路由跳转、刷新在当前路由时自动展开路由层级、菜单栏侧边按钮展开收缩实现】

src\components\admin\g_menu.vue

(记得创建响应的视图目录结构,可以参考router/index.ts)

javascript 复制代码
<script setup lang='ts'>
import { Component } from 'vue';
import { IconHome, IconUser, IconSettings } from "@arco-design/web-vue/es/icon"
import G_iconComponent from '../common/g_iconComponent.vue';
import {collapsed} from "../../components/admin/g_menu"
import { ref } from 'vue';
import router from '@/router';
import { useRoute } from 'vue-router';
const route = useRoute()
interface MenuType {
    title: string
    name: string
    icon?: string | Component
    children?: MenuType[]
}

const menuList: MenuType[] = [
    { title: "首页", name: "home", icon: IconHome },
    {
        title: "个人中心", name: "userCenter", icon: IconUser, children: [
            { title: "用户信息", name: "userinfo" },
        ]
    },
    {
        title: "用户管理", name: "userManage", icon: IconUser, children: [
            { title: "用户列表", name: "userlist" },
        ]
    },
    {
        title: "系统设置", name: "systemCenter", icon: IconSettings, children: [
            { title: "系统信息", name: "systeminfo" },
        ]
    },
    
]

// menuItemClick 菜单点击跳转函数
function menuItemClick(key:string) {
   router.push({
    name:key
   })
    
}

const openkeys = ref<string[]>([])
// 路由初始化函数,判断路由的层级是否是三级,将中间的路由名称赋值给openkeys可以实现层级展开,只适用于三级的路层级,三级以上的路由层级需要遍历处理给openkeys,
function initRoute(){
    if (route.matched.length ===3){
        openkeys.value = [route.matched[1].name as string]
    }
}
initRoute()
console.log(route);

</script>

<template>
    <div class="g_menu" :class="{collapsed:collapsed}">
        <div class="g_menu_inner scrollbar">
            <a-menu 
            @menu-item-click="menuItemClick"
            v-model:collapsed="collapsed"
            v-model:open-keys="openkeys"
            :default-selected-keys="[route.name]"
            show-collapse-button >
            <!-- 注意这里的key必须使用name字段,因为在处理route时是通过name字段来进行判断的 -->
                <template v-for="menu in menuList">
                    <a-menu-item :key="menu.name" v-if="!menu.children">
                        <template #icon>
                            <G_iconComponent :is="menu.icon">
                    
                                {{ menu.icon }}</G_iconComponent>
                        </template>
                        {{ menu.title }} 
                    </a-menu-item>
                    <a-sub-menu :key="menu.name" v-else :title="menu.title">
                        <template #icon>
                            <G_iconComponent :is="menu.icon"></G_iconComponent>
                        </template>
                        <a-menu-item :key="sub.name" v-for="sub in menu.children">
                            {{ sub.title }}
                            <template #icon>
                                <G_iconComponent :is="sub.icon"></G_iconComponent>
                            </template>
                        </a-menu-item>
                    </a-sub-menu>
                </template>
            </a-menu>
        </div>

    </div>
</template>

<style lang='less'>
.g_menu {
    height: calc(100vh - 90px);
    position: relative;
    &.collapsed{
        .arco-menu-collapse-button {
                left: 48px !important; 

            }

    }



    &:hover {
        .arco-menu-collapse-button {
            opacity: 1 !important;
        }
    }

    .g_menu_inner {
        height: 100%;
        overflow-y: auto;
        overflow-x: hidden;

        .arco-menu {
            position: inherit;
            .arco-menu-collapse-button {
                top: 50%;
                transform: translate(-50%, -50%);
                left: 240px;
                transition: all .3s;
                opacity: 0;
               
             
            }

        }

    }
}
</style>

src\components\admin\g_menu.ts

javascript 复制代码
import { ref } from 'vue';
// 使用ts的方式暴露出去,方便后续别的地方调佣
export const collapsed = ref(false)

src\components\common\g_iconComponent.vue

javascript 复制代码
<script setup lang='ts'>
import type { Component } from 'vue';
interface Props {
    is?: Component | string
}
const props = defineProps<Props>()
</script>


<!-- 处理图标显示,如果是 Component类型,直接使用component渲染,如果是字符串,使用i标签渲染-->
<template>
    <component v-if="typeof props.is === 'object'" :is="props.is"></component>
    <i :class="props.is" v-else></i>
</template>

src\router\index.ts

javascript 复制代码
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      name: "home",
      path: "/",
      redirect: "/admin"
    },
    {
      name: "web",
      path: "/web",
      component: () => import("@/views/web/index.vue")
    },
    {
      name: "login",
      path: "/login",
      component: () => import("@/views/login/index.vue")
    },
    {
      name: "admin",
      path: "/admin",
      component: () => import("@/views/admin/index.vue"),
      meta:{title:"首页"},
      children: [

        { name: "home", path: "", component: () => import("@/views/admin/home/index.vue") },

        {
          name: "userCenter", path: "user_Center", children: [{name: "userinfo",path: "user_info",component: () => import("@/views/admin/user_center/index.vue"),meta:{title:"用户信息"} }],
          meta:{title:"用户中心"} 
        },
        {
          name: "userManage", path: "user_Manage", children: [
            { name: "userlist", path: "user_list", component: () => import("@/views/admin/user_manage/index.vue"),meta:{title:"用户列表"}  }],
            meta:{title:"用户管理"} 
        },
        {
          name: "systemCenter", path: "system_Center", children: [
            { name: "systeminfo", path: "system_info", component: () => import("@/views/admin/system_manage/index.vue"),meta:{title:"系统管理"}  }],
            meta:{title:"系统信息"} 
        },
      ]
    }
  ],
})

export default router

src\views\admin\index.vue

javascript 复制代码
<script setup lang='ts'>
import { RouterView } from 'vue-router';
import G_theme from '../../components/common/g_theme.vue';
import G_screen from '../../components/common/g_screen.vue';
import G_menu from '../../components/admin/g_menu.vue'
import {collapsed} from "../../components/admin/g_menu"

</script>

<template>
    <div class="g_admin" >
        <!-- 给gasid 添加动态的collapsed类,在触发收缩时添加,方便给aside设置宽度 -->
        <div class="g_aside" :class="{collapsed:collapsed}"> 
            <div class="g_logo"></div>
         <G_menu></G_menu>
        </div>
        <div class="g_main" :class="{collapsed:collapsed}">
            <div class="g_head">
                <div class="g_breadcrumbs"></div>
                <div class="g_actions">
                    <icon-home />
                   <G_theme></G_theme>
                   <G_screen></G_screen>
                    <div class="g_user_info_action">

                    </div>

                </div>
            </div>
            <div class="g_tabs">

            </div>
            <div class="g_container">
                <RouterView></RouterView>
            </div>
        </div>
    </div>
</template>

<style lang='less'>
.g_admin {
    display: flex;
    background-color: var(--color-bg-1); //设置背景主题色
    color: @color-text-1; //设置文本颜色

    .g_aside {
        // 不能使用overflow。否则,收缩展开按钮会被覆盖
        width: 240px;
        height: 100vh;
        border-right: @g_border;
        transition: width .3s;

     

        .g_logo {
            width: 100%;
            height: 90px;
            border-bottom: @g_border;
        }
        //g_main下添加了collapsed类,如果存在就将这边框设置为48px即为收起状态
        &.collapsed{ 
            width: 48px;
            //  &~只有下兄弟标签适用,将g_main的宽度进行调整
            &~.g_main{
                width: calc(100% - 48px);
            }

        }
    }

    .g_main {
        width: calc(100% - 240px);
        transition: width .3s;

        .g_head {
            width: 100%;
            height: 60px;
            border-bottom: @g_border;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0 20px;
        }

        .g_tabs {
            width: 100%;
            height: 30px;
            border-bottom: @g_border;
        }

        .g_container {
            width: 100%;
            height: calc(100vh - 90px);
            overflow-y: auto;
            overflow-x: hidden;
            background-color: @color-fill-2; //背景色,颜色稍微和外面的背景色有少许的不一样
          
        }
    }
}</style>

面包屑配置

在admin/index.vue中将g_breadcrumbs换成该组件

src\components\admin\g_breadcrumbs.vue

javascript 复制代码
<script setup lang='ts'>
import {type RouteMeta,useRoute} from "vue-router"

const route =useRoute()

export interface MetaType extends RouteMeta{
  title:string
}
</script>
<!-- 利用router的meta的title显示中文作为面包屑,但是的严格按照路由的配置来,这里也可以添加跳转点击 -->
<template>
    <a-breadcrumb>
      <template v-for="r in route.matched">
         <a-breadcrumb-item v-if="r.name !=='home'">{{ (r.meta as MetaType)?.title }}</a-breadcrumb-item>
      </template>
     
      
    </a-breadcrumb>
  </template>

<style scoped lang='scss'>

</style>

iconfont图标引入

iconfont-阿里巴巴矢量图标库 中选入好图标,添加到项目中,下载下来保存为iconfont.css到assets下,然后在main.ts中import "@/assets/iconfont.css" 即可使用

用户下拉部分

src\components\common\g_user_dropdown.vue

javascript 复制代码
<script setup lang='ts'>
import router from '@/router';


function handleSelect(val:string){
  if (val === 'logout'){
    // 注销登录
      return
  }
  // 其他选项跳转到相应的页面中去
   router.push({
    name:val
   })
}

interface OptionType {
  name:string
  title:string
}
// name必须与路由对上才能正确跳转
const options :OptionType[] = [
  {title:"用户中心",name:"userinfo"},
  {title:"用户管理",name:"userlist"},
  {title:"系统信息",name:"systeminfo"},
  {title:"用户注销",name:"logout"},
]
</script>

<template>
  <a-dropdown @select="handleSelect" :popup-max-height="false">
    <div class="g_user_dropdown_com">
 <a-avatar :size="35" image-url="data:image/jpeg;base64,HE+CeI9ZpFpZsTIzT7GiSKpi6Qj5xJJotxeVhTaG0xibRF2fbD7Q+3lZQ8QPtj7D7Z//2Q=="></a-avatar>
 <span class="user_name">王五</span> 
 <icon-down></icon-down>  
</div>  
      <template #content>
        <a-doption v-for="item in options" :value="item.name">{{ item.title }}</a-doption>
        
      </template>
    </a-dropdown>
</template>

<style lang='less'>
.g_user_dropdown_com{
  cursor: pointer;
  .user_name{
    margin: 0 5px;
  }
  svg{
    margin-right: 0 !important;
  }
}

</style>

src\views\admin\index.vue【引入组件并调节下拉部分的样式】

javascript 复制代码
<script setup lang='ts'>
import { RouterView } from 'vue-router';
import G_theme from '../../components/common/g_theme.vue';
import G_screen from '../../components/common/g_screen.vue';
import G_menu from '../../components/admin/g_menu.vue'
import G_breadcrumbs from "../../components/admin/g_breadcrumbs.vue"
import G_user_deopdown from "../../components/common/g_user_dropdown.vue"
import {collapsed} from "../../components/admin/g_menu"
import router from '@/router';

function goHome(){
    router.push({
        name:"web"
    })
}
</script>

<template>
    <div class="g_admin" >
        <!-- 给gasid 添加动态的collapsed类,在触发收缩时添加,方便给aside设置宽度 -->
        <div class="g_aside" :class="{collapsed:collapsed}"> 
            <div class="g_logo"></div>
         <G_menu></G_menu>
        </div>
        <div class="g_main" :class="{collapsed:collapsed}">
            <div class="g_head">
              <G_breadcrumbs></G_breadcrumbs>
                <div class="g_actions">
                   <span title="去首页" @click="goHome"> <icon-home></icon-home></span>
                   <G_theme></G_theme>
                   <G_screen></G_screen>
                    <G_user_deopdown></G_user_deopdown>

                </div>
            </div>
            <div class="g_tabs">

            </div>
            <div class="g_container">
                <RouterView></RouterView>
            </div>
        </div>
    </div>
</template>

<style lang='less'>
.g_admin {
    display: flex;
    background-color: var(--color-bg-1); //设置背景主题色
    color: @color-text-1; //设置文本颜色

    .g_aside {
        // 不能使用overflow。否则,收缩展开按钮会被覆盖
        width: 240px;
        height: 100vh;
        border-right: @g_border;
        transition: width .3s;

     

        .g_logo {
            width: 100%;
            height: 90px;
            border-bottom: @g_border;
        }
        //g_main下添加了collapsed类,如果存在就将这边框设置为48px即为收起状态
        &.collapsed{ 
            width: 48px;
            //  &~只有下兄弟标签适用,将g_main的宽度进行调整
            &~.g_main{
                width: calc(100% - 48px);
            }

        }
    }

    .g_main {
        width: calc(100% - 240px);
        transition: width .3s;

        .g_head {
            width: 100%;
            height: 60px;
            border-bottom: @g_border;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0 20px;
            
            .g_actions{
                display: flex;
                align-items: center;
            }
            svg{
                font-size: 18px;
                cursor: pointer;
                margin-right: 10px;
            }
        }
        

        .g_tabs {
            width: 100%;
            height: 30px;
            border-bottom: @g_border;
        }

        .g_container {
            width: 100%;
            height: calc(100vh - 90px);
            overflow-y: auto;
            overflow-x: hidden;
            background-color: @color-fill-2; //背景色,颜色稍微和外面的背景色有少许的不一样
          
        }
    }
}</style>

src\components\admin\g_menu.vue【调整菜单展开的逻辑,不使用默认的,而是监听路由变化】

javascript 复制代码
<script setup lang='ts'>
import { Component } from 'vue';
import { IconHome, IconUser, IconSettings } from "@arco-design/web-vue/es/icon"
import G_iconComponent from '../common/g_iconComponent.vue';
import {collapsed} from "../../components/admin/g_menu"
import { ref,watch } from 'vue';
import router from '@/router';
import { useRoute } from 'vue-router';
const route = useRoute()
interface MenuType {
    title: string
    name: string
    icon?: string | Component
    children?: MenuType[]
}

const menuList: MenuType[] = [
    { title: "首页", name: "home", icon: IconHome },
    {
        title: "个人中心", name: "userCenter", icon: "iconfont icon-user-manage", children: [
            { title: "用户信息", name: "userinfo",icon:"iconfot icon-account-pin-circle-line" },
        ]
    },
    {
        title: "用户管理", name: "userManage", icon: "iconfont icon-user-manage", children: [
            { title: "用户列表", name: "userlist", icon: "iconfont icon-yonghuliebiao" },
        ]
    },
    {
        title: "系统设置", name: "systemCenter", icon: "iconfont icon-xitongguanli", children: [
            { title: "系统信息", name: "systeminfo", icon: "iconfont icon-xitongxinxi" },
        ]
    },
    
]

// menuItemClick 菜单点击跳转函数
function menuItemClick(key:string) {
   router.push({
    name:key
   })
    
}

const openkeys = ref<string[]>([])
const selectedKeys = ref<string[]>([])
// 路由初始化函数,判断路由的层级是否是三级,将中间的路由名称赋值给openkeys可以实现层级展开,只适用于三级的路层级,三级以上的路由层级需要遍历处理给openkeys,
function initRoute(){
    if (route.matched.length ===3){
        openkeys.value = [route.matched[1].name as string]
    }
    // 将路由的name添加到elected-keys中,默认展开路由
    selectedKeys.value = [route.name as string]
}
//监听路由变化,如果路由变化就执行一遍initRoute
watch(()=>route.name,()=>{
    initRoute()
},{immediate:true})


</script>

<template>
    <div class="g_menu" :class="{collapsed:collapsed}">
        <div class="g_menu_inner scrollbar">
            <a-menu 
            @menu-item-click="menuItemClick"
            v-model:collapsed="collapsed"
            v-model:open-keys="openkeys"
            v-model:selected-keys ="selectedKeys"
            show-collapse-button >
            <!-- 注意这里的key必须使用name字段,因为在处理route时是通过name字段来进行判断的 -->
                <template v-for="menu in menuList">
                    <a-menu-item :key="menu.name" v-if="!menu.children">
                        <template #icon>
                            <G_iconComponent :is="menu.icon">
                    
                                {{ menu.icon }}</G_iconComponent>
                        </template>
                        {{ menu.title }} 
                    </a-menu-item>
                    <a-sub-menu :key="menu.name" v-else :title="menu.title">
                        <template #icon>
                            <G_iconComponent :is="menu.icon"></G_iconComponent>
                        </template>
                        <a-menu-item :key="sub.name" v-for="sub in menu.children">
                            {{ sub.title }}
                            <template #icon>
                                <G_iconComponent :is="sub.icon"></G_iconComponent>
                            </template>
                        </a-menu-item>
                    </a-sub-menu>
                </template>
            </a-menu>
        </div>

    </div>
</template>

<style lang='less'>
.g_menu {
    height: calc(100vh - 90px);
    position: relative;
    &.collapsed{
        .arco-menu-collapse-button {
                left: 48px !important; 

            }

    }



    &:hover {
        .arco-menu-collapse-button {
            opacity: 1 !important;
        }
    }

    .g_menu_inner {
        height: 100%;
        overflow-y: auto;
        overflow-x: hidden;

        .arco-menu {
            position: inherit;
            .arco-menu-collapse-button {
                top: 50%;
                transform: translate(-50%, -50%);
                left: 240px;
                transition: all .3s;
                opacity: 0;
               
             
            }

        }

    }
}
</style>

tabs组件

env.d.ts

javascript 复制代码
/// <reference types="vite/client" />

// 给  RouteMeta 声明一个title类型,这样在声明对象时不会报警告,也不会飘红
import "vue-router"
declare module "ue-router"{
    interface RouteMeta{
        title:string
    }
}

安装 swiper npm i swiper

src\components\admin\g_tabs.vue

javascript 复制代码
<script setup lang='ts'>
import { ref } from 'vue';
import { IconClose } from '@arco-design/web-vue/es/icon';
import router from '@/router';
import { useRoute } from 'vue-router';
import { watch } from 'vue';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { onMounted } from 'vue';
const route = useRoute()
interface TabType {
    name: string
    title: string
}
const tabs = ref<TabType[]>([
    { title: "首页", name: "home" },
    

])
function check(itme: TabType) {
    router.push({
        name: itme.name
    })
    saveTabs()

}

function removeItem(itme: TabType) {
    if (itme.name === 'home') {
        return
    }
    const index = tabs.value.findIndex((value) => itme.name === value.name)
    if (index != -1) {
        //判断我删除的这个元素,是不是我当前所在的
        if (itme.name === route.name) {
            router.push({
                name: tabs.value[index - 1].name
            })
        }
        tabs.value.splice(index, 1)
    }
    saveTabs()

}
function removeAllItem() {
    tabs.value = [
        { title: "首页", name: "home" },
    ]
    router.push({ name: "home" })
    saveTabs()

}
// 持久化tabs
function saveTabs() {
    localStorage.setItem("g_tabs", JSON.stringify(tabs.value))
}
// 初始化tabs
function loadTabs() {
    const g_tabs = localStorage.getItem("g_tabs")
    if (g_tabs) {
        try {
            tabs.value = JSON.parse(g_tabs)
        } catch (e) {
            console.log(e);

        }
    }
}
loadTabs()


watch(() => route.name, () => {
    //判断当前路由的名称,在不在 tabs 里面,如果不在就加入进去
    const index = tabs.value.findIndex((value) => route.name === value.name)
    if (index === -1) {
        tabs.value.push({
            name: route.name as string,
            title: route.meta.title as string,
        })
    }
}, { immediate: true })


const slidesCount = ref(100)
onMounted(() => {
    // 先算总宽度
    // 算实际宽度(scrollWidth)有没有超出总宽度
    // 如果超出了总宽度,就遍历所有的 swiper-slide
    // 从前往后加,如果超过了总宽度,那个时候的个数,就是实际显示的个数

    // 显示总宽度
    const swiperDom = document.querySelector(".g_tabs_swiper") as HTMLDivElement
    const swiperWidth = swiperDom.clientWidth
    // 显示实际的总宽度
    const wrapperDom = document.querySelector(".g_tabs_swiper .swiper-wrapper") as HTMLDivElement
    const wrapperWidth = wrapperDom.scrollWidth
    if (swiperWidth > wrapperWidth) {
        return
    }
    // 如果实际的总宽度大雨了显示的总宽度
    // 遍历 swiper-slide然后从后往前加
    const slideList = document.querySelectorAll(".g_tabs_swiper .swiper-slide")
    let allWith = 0
    let index = 1
    for (const sliderListElement of slideList) {
        // 加上margin的宽度 20
        allWith += (sliderListElement.clientWidth + 20)
        index++
        if (allWith >= swiperWidth) {
            break
        }
    }

    slidesCount.value = index

    // 用户刚进来这个页面时,选中高亮元素
      const activateSlide = document.querySelector(".g_tabs_swiper .swiper-slide.activate")  as HTMLDivElement
      if (activateSlide.offsetLeft > swiperWidth){
        const offsetLeft = swiperWidth - activateSlide.offsetLeft
        setTimeout(()=>{
            wrapperDom.style.transform = `translate3d(${offsetLeft}px,0px,0px)`
        })
      }
})
</script>

<template>
    <div class="g_tabs">
        <swiper :slides-per-view="slidesCount" class="g_tabs_swiper">
            <swiper-slide v-for="item in tabs" :class="{ activate: route.name === item.name }">
                <div class="item" @click="check(item)" @mousedown.middle.stop="removeItem(item)"
                    :class="{ activate: route.name === item.name }">{{ item.title }}
                    <span class="close" title="删除" @click.stop="removeItem(item)" v-if="item.name != 'home'">
                        <IconClose></IconClose>
                    </span>
                </div>
            </swiper-slide>
        </swiper>

        <div class="item" @click="removeAllItem">删除全部</div>


    </div>
</template>

<style  lang='less'>
.g_tabs {
    display: flex;
    align-items: center;
    padding: 0 10px;
    justify-content: space-between;

    .swiper {
        width: calc(100% - 100px);
        display: flex;
        overflow-y: hidden;
        overflow-x: hidden;

        .swiper-wrapper {

            display: flex;
            align-items: center;
        }

        .swiper-slide {
            width: fit-content !important;
            flex-shrink: 0;
        }
    }

    .item {
        padding: 1px 8px;
        background-color: var(--color-bg-1);
        border: @g_border;
        margin-right: 10px;
        cursor: pointer;
        border-radius: 5px;
        flex-shrink: 0;

        &:hover {
            background-color: var(--color-fill-1);
        }

        &.activate {
            background-color: @primary-6;
            color: white;
        }
    }
}</style>

然后在admin/idnex.vue中引入该组件

logo组件

src\components\admin\g_logo.vue

javascript 复制代码
<script setup lang='ts'>
import { collapsed } from './g_menu';
</script>

<template>
 <div class="g_logo" :class="{collapsed:collapsed}">
 <img src="../../assets/logo.svg" alt="logo" class="logo">
 <div class="slogan">
    <div>通用后台</div>
    <div>Genernaladmin</div>
 </div>
 </div>
</template>

<style  lang='less'>

.g_logo{
    display: flex;
    justify-content: center;
    align-items: center;

    &.collapsed{
        .slogan{
            transform: scale(0);
            transform-origin: left;
            opacity: 0;
        }
        .logo{
            transform: translateX(50px);
            width: 40px;
            height: 40px;
          
        }
    }
    .logo{
        width: 50px;
        height: 50px;
    }
    .slogan{
            margin-left: 10px;
            div:nth-child(1){
                font-size: 22px;
                font-weight: 600;
            }
            div:nth-child(2){
                font-size: 12px;
               margin-top: 1px;
            }
        }

   
}

</style>

路由切换动画+container样式优化

src\views\admin\index.vue

javascript 复制代码
<script setup lang='ts'>
import { RouterView } from 'vue-router';
import G_theme from '../../components/common/g_theme.vue';
import G_screen from '../../components/common/g_screen.vue';
import G_menu from '../../components/admin/g_menu.vue'
import G_breadcrumbs from "../../components/admin/g_breadcrumbs.vue"
import G_user_deopdown from "../../components/common/g_user_dropdown.vue"
import {collapsed} from "../../components/admin/g_menu"
import router from '@/router';
import G_tabs from '@/components/admin/g_tabs.vue';
import G_logo from '@/components/admin/g_logo.vue';

function goHome(){
    router.push({
        name:"web"
    })
}
</script>

<template>
    <div class="g_admin" >
        <!-- 给gasid 添加动态的collapsed类,在触发收缩时添加,方便给aside设置宽度 -->
        <div class="g_aside" :class="{collapsed:collapsed}"> 
           <G_logo></G_logo>
         <G_menu></G_menu>
        </div>
        <div class="g_main" :class="{collapsed:collapsed}">
            <div class="g_head">
              <G_breadcrumbs></G_breadcrumbs>
                <div class="g_actions">
                   <span title="去首页" @click="goHome"> <icon-home></icon-home></span>
                   <G_theme></G_theme>
                   <G_screen></G_screen>
                    <G_user_deopdown></G_user_deopdown>

                </div>
            </div>
           <G_tabs></G_tabs>
           <!-- 路由切换动画配置 -->
            <div class="g_container scrollbar">
                <router-view v-slot="{Component}" class="g_base_view">
                    <transition name="fade" mode="out-in">
                        <component :is="Component"></component>
                    </transition>
                </router-view>
            </div>
        </div>
    </div>
</template>

<style lang='less'>
.g_admin {
    display: flex;
    background-color: var(--color-bg-1); //设置背景主题色
    color: @color-text-1; //设置文本颜色

    .g_aside {
        // 不能使用overflow。否则,收缩展开按钮会被覆盖
        width: 240px;
        height: 100vh;
        border-right: @g_border;
        transition: width .3s;

     

        .g_logo {
            width: 100%;
            height: 90px;
            border-bottom: @g_border;
        }
        //g_main下添加了collapsed类,如果存在就将这边框设置为48px即为收起状态
        &.collapsed{ 
            width: 48px;
            //  &~只有下兄弟标签适用,将g_main的宽度进行调整
            &~.g_main{
                width: calc(100% - 48px);
            }

        }
    }

    .g_main {
        width: calc(100% - 240px);
        transition: width .3s;

        .g_head {
            width: 100%;
            height: 60px;
            border-bottom: @g_border;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0 20px;
            
            .g_actions{
                display: flex;
                align-items: center;
            }
            svg{
                font-size: 18px;
                cursor: pointer;
                margin-right: 10px;
            }
        }
        

        .g_tabs {
            width: 100%;
            height: 30px;
            border-bottom: @g_border;
        }

        .g_container {
            width: 100%;
            height: calc(100vh - 90px);
            overflow-y: auto;
            overflow-x: hidden;
            background-color: @color-fill-2; //背景色,颜色稍微和外面的背景色有少许的不一样
            padding:  20px 0px 20px 20px;
            .g_base_view{
                background-color: var(--color-bg-1);
                border-radius: 5px;
               
            }
          
        }
    }
}


// 组件刚开始离开
.fade-leave-active {
}

// 组件离开结束
.fade-leave-to {
  transform: translateX(30px);
  opacity: 0;
}

// 组件刚开始进入
.fade-enter-active {
  transform: translateX(-30px);
  opacity: 0;
}

// 组件进入完成
.fade-enter-to {
  transform: translateX(0);
  opacity: 1;
}

// 正在进入和离开
.fade-leave-active, .fade-enter-active {
  transition: all .3s ease-out;
}


</style>

路由进度条使用

1.安装

javascript 复制代码
npm install --save nprogress
npm i -D @types/nprogress

2.在路由组件中使用

javascript 复制代码
import NProgress from "nprogress";

router.beforeEach((to, from, next) => {
  NProgress.start();//开启进度条
  next()
})
//当路由进入后:关闭进度条
router.afterEach(() => {
  // 在即将进入新的页面组件前,关闭掉进度条
  NProgress.done()//完成进度条
})

在main.js中引入

javascript 复制代码
import "nprogress/nprogress.css";

环境变量

1.首先修改package.json中的dev启动配置,可以通过不同的配置读取不同的.env文件中的内容

javascript 复制代码
  "scripts": {
    "dev": "vite", //会去读取.env文件中带有前缀VITE的变量
    "dev1":"vite --mode dev1", //会去读取.env.dev1文件中带有前缀VITE的变量
    "build": "run-p type-check \"build-only {@}\" --",
    "preview": "vite preview",
    "build-only": "vite build",
    "type-check": "vue-tsc --build"
  },

2.配置.env类似中的文件如.env.dev1

javascript 复制代码
VITE_SERVERNAME='测试环境1'
VITE_SERVER_URL=http://127.0.0.1:8000

3.使用函数方式编辑配置文件vite.config.ts

javascript 复制代码
import { EnvMeta } from './env.d'; //添加loadEnv的类型
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import { loadEnv } from 'vite' //加载env函数
import { log } from 'node:console';
const envDir = './' //.env文件的地址
// 使用函数方式配置
export default defineConfig((config)=>{
  const env:EnvMeta =loadEnv(config.mode,envDir) as EnvMeta
  log(env.VITE_SERVER_URL) //声明EnvMeta类型后可以直接点出来VITE_SERVER_URL。但是不声明的话也可以,只不过不会自动弹出来。可以在env.d.ts中声明
  return {
    plugins: [
      vue(),
      vueDevTools(),],
      // 配置less预处理
      css:{
        preprocessorOptions:{
          less:{
            modifyVars:{
              // 'primary-6':"red"  //修改arco-disgin的主题色,默认是蓝色
            },
            additionalData:'@import "@/assets/var.less";',
            javascriptEnabled:true,
          }
        }
      },
    resolve: {
      alias: {
        '@': fileURLToPath(new URL('./src', import.meta.url))
      },
    },
    // 前端地址端口以及ip配置
    server:{
      host:"0.0.0.0",
      port:8080
    },
    //配置env地址
    envDir:envDir
  }
})

env.d.ts

javascript 复制代码
/// <reference types="vite/client" />

// 给  RouteMeta 声明一个title类型,这样在声明对象时不会报警告,也不会飘红
import "vue-router"
declare module "ue-router"{
    interface RouteMeta{
        title:string
    }
}

// 声明类型,继承自Record
export interface EnvMeta  extends Record<string,string>{
    VITE_SERVER_URL:string 
    VITE_SERVER_NAME:string
}

跨域配置

javascript 复制代码
  // 前端地址端口以及ip配置
    server:{
      host:"0.0.0.0",
      port:8080,
      // 跨域配置,如果后端的路由是以/api开始的,识别到后转成同源的路径进行请求
      proxy:{
        "/api":{
          target:env.VITE_SERVER_URL, 
          rewrite:(path:string)=>path.replace("/api","")  //这个是重写路径,如果后端的接口不满足以api开头的,那么就进行替换为空,也能完成解决跨域
        }
      }
    },

axios封装

src\api\index.ts

javascript 复制代码
import axios from "axios";
import {Message} from "@arco-design/web-vue";
// import {userStorei} from "@/stores/user_store";
import type {Ref} from "vue";
// 基础的响应类型,一般和后端项目的响应类型一致
export interface baseResponse<T> {
    code: number
    msg: string
    data: T
}
// 列表类型的响应类型
export interface listResponse<T> {
    list: T[]
    count: number
}
// URL的Query Parameters参数
export interface paramsType {
    key?: string
    limit?: number
    page?: number
    sort?: string
    [key: string]: any
}

export const useAxios = axios.create({
    timeout: 6000,
    baseURL: "", // 在使用前端代理的情况下,这里必须留空,不然会跨域
})
// axios请求拦截
useAxios.interceptors.request.use((config) => {
    // const userStore = userStorei() //先去这个全局的store中拿到这个用户的token
    // config.headers.set("token", userStore.userInfo.token) //将token配置到请求头中
    return config
})
// axios响应拦截
useAxios.interceptors.response.use((res) => {
    // 响应拦截时先做一个判断,如果状态是200才是响应成功,将数据返回出去
    if (res.status === 200) {
        return res.data
    }
    return res
}, (res) => {
    // 如果有错误,将错误展示出来
    Message.error(res.message)
})
// 默认删除的接口,接受一个url,一个id_list列表,返回基础的响应数据(因为后端的删除接口的请求参数都是是id_list,所以可以放在前端作为一个默认的接口方便调用)
export function defaultDeleteApi(url: string, id_list: number[]): Promise<baseResponse<string>> {
    return useAxios.delete(url, {data: {id_list}})
}
// 默认post的接口,接受一个url,一个data,返回基础的响应数据
export function defaultPostApi(url: string, data: any): Promise<baseResponse<string>> {
    return useAxios.post(url, data)
}

export function defaultPutApi(url: string, data: any): Promise<baseResponse<string>> {
    return useAxios.put(url, data)
}
export interface optionsType {
    label: string
    value: number | string
}

export type optionsFunc = (params?: paramsType) => Promise<baseResponse<optionsType[]>>

export function getOptions(ref: Ref<optionsType[]>, func: optionsFunc, params?: paramsType){
    func(params).then((res)=>{
        ref.value = res.data
    })
}
相关推荐
GIS思维1 天前
ArcGIS汉化不成功的解决方案
arcgis·arcgis汉化
xyt11722281771 天前
宗地四至提取工具
python·arcgis
智航GIS1 天前
ArcGIS大师之路500技---055矢量数据去带号
arcgis
GIS思维1 天前
ArcGIS Pro3.5.2安装包+安装详细教程+系统需求
arcgis·arcgispro
GIS思维1 天前
ArcGIS土地利用现状图制作全流程
arcgis·地表覆盖·天地图·土地利用现状图
GIS思维1 天前
ArcGIS Pro查看多期数据变化!卷帘+多地图联动齐上架
arcgis·arcgispro·天地图
YuanYWRS2 天前
ArcGIS基础:如何删除同类多个图斑中面积较小,只保留其中面积最大的一个图斑的操作
arcgis·多个图斑取面积最大的一个
智航GIS2 天前
ArcGIS大师之路500技---054字段顺序调整
arcgis
YuanYWRS2 天前
ArcGIS基础:如何在字段计算器里,从1-100进行顺序编号
arcgis