Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save U-Recommend/ab6d78d9779cf0dd9283cf7365801488 to your computer and use it in GitHub Desktop.
Save U-Recommend/ab6d78d9779cf0dd9283cf7365801488 to your computer and use it in GitHub Desktop.
Vue

创建项目

  • 创建项目目录
mkdir testproject && cd testproject
  • 安装 vue-cli
yarn add vue-cli
  • 创建项目
vue create testproject
  • 进入项目目录
cd testproject/
  • 启动项目
yarn serve

项目设置

element-plus

  • 安装 element-plus
yarn add element-plus --save
  • 更新main.js,增加element-plus导入
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const app=createApp(App)
app.use(ElementPlus)
app.mount('#app')

路由 vue-router

  • 安装路由
yarn add vue-router --save
  • 在src目录下新疆router文件夹,再新建index.js

  • main.js 中配置路由

import router from './router/index.js'

app.use(router)
  • App.vue中加入路由模块
// 删除原有示例代码 
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
// 替换为路由组件
<router-view></router-view>

axios

  • 安装 axios
yarn add axios --save
  • 配置axios

    • src下新建文件夹 utils -> http
    • 在http下建 axios.js 和 http.js 文件
  • axios.js代码

import axios from "axios";
import { ElMessage } from "element-plus";
// 1. 创建axios实例
const instance = axios.create({
    // 接口
    baseURL: '/api',
    // 超时时间
    timeout: 50000
});
// 2.请求拦截
instance.interceptors.request.use(
    config => {
        let token = sessionStorage.getItem('token0')
        if (token) {
            config.headers['token'] = token
        }
        return config
    },
    error => {

    }
)
// 3. 响应拦截
instance.interceptors.response.use(
    res => {
        return res
    },
    error => {
        if (error && error.response){
            const status = error.response.status
            switch (status){

            }
        }else{
            if (JSON.stringify(err).includes('timeout')){
                ElMessage.error('服务器响应超时,请刷新页面');
            }
            ElMessage.error('链接服务器失败');
        }
        return Promise.reject(error);
    }
)
// 4.导出 axios 实例
export default instance;
  • http.js代码
import { reject, resolve } from "core-js/fn/promise";
import { de } from "element-plus/es/locale";
import instance from "./axios";

const post = (url, data) => {
    return new Promise((resolve, reject) => {
        instance.post(url, data).then(res => {
            resolve(res)
        }).catch(err => {
            reject(err)
        })
    })
}

const get = (url, data) => {
    return new Promise((resolve, reject) => {
        instance.get(url, { params: data }).then(res => {
            resolve(res)
        }).catch(err => {
            reject(err)
        })
    })
}

const put = (url, data) => {
    return new Promise((resolve, reject) => {
        instance.put(url, data).then(res => {
            resolve(res)
        }).catch(err => {
            reject(err)
        })
    })
}

const del = (url, data) => {
    return new Promise((resolve, reject) => {
        instance.delete(url, { params: data }).then(res => {
            resolve(res)
        }).catch(err => {
            reject(err)
        })
    })
}

export default { post, get, put, del }
  • 配置跨域

vite.config.js

...

全局样式

新建文件夹和文件 src -> assets -> css -> style.css style.css编辑

html, body {
    padding: 0px;
    margin: 0px;
    height: 100%;
    background: #fff;
}

/* 表格统一样式 */
.el-table .el-table__header-wrapper thead th,
.el-table .el-table__fixed-header-wrapper thead th {
    padding: 0px;
    background-color: #f9f9f9;
    height: 50px;
    line-height: 40px;
    color: #283543;
}

main.js 中导入

import '@/assets/css/style.css'

eslintrc规则设置

在src同级目录下新建 .eslintrc.js 文件

module.exports = {
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": "plugin:vue/vue3-essential",
    "overrides": [
    ],
    "parserOptions": {
        "ecmaVersion": 12,
        "sourceType": "module"
    },
    "plugins": [
        "vue"
    ],
    "rules": {
        'vue/no-reserved-component-names': 'off', //不允许在组件定义中使用保留名称
        'vue/multi-word-component-names': 'off', //要求组件名称始终为多字
        'vue/no-deprecated-slot-attribute': 'off',
        "vue/no-unused-components": "off",
        'no-unused-vars': 0

    }
}

Element Plus 配置中文

// main.js
import local from 'element-plus/lib/locale/zh-cn'

app.use(ElementPlus, {local})

API接口

  • src 目录下新建 api 文件夹
  • api 目录中新建 user.js 用于存储 用户接口
  • 编辑 user.js
import http from '../utils/http/http.js'

const login=(data)=>{
    return http.post('/user/login', data)
}

const getUserList=(data)=>{
    return http.get('/user/list', data)
}

const saveUser=(data)=>{
    return http.post('/user/save', data)
}

const delUser=(data)=>{
    return http.del('/user/delete', data)
}

const getUserDetail=(data)=>{
    return http.get('/user/detail', data)
}

export default {
    login, getUserList, saveUser, delUser, getUserDetail
}

页面实现

路由设置

  • 进入 src/router/index.js 设置页面路由
import { createRouter, createWebHashHistory } from "vue-router";

const routes = [
    {
        path: '/',
        redirect: '/login'
    },
    {
        path: '/login',
        name: 'login',
        meta: {
            title: '登录'
        },
        component: () => import('../view/login.vue')
    },
    {
        path: '/home',
        name: 'index',
        meta:{title:'index path'},
        component: ()=>import('../view/Home.vue'),
        redirect: '/index',
        children: [
            {
                path: '/index',
                meta: {title: 'index path'},
                component: ()=>import('../view/Welcome.vue')
            },
            {
                path: '/user/list',
                meta: {title: '用户管理'},
                component: ()=> import('../view/user/Index.vue'),
            },
            {
                path:'/user/detail',
                meta: {title: '用户详情'},
                component: ()=> import('../view/user/Detail.vue')
            },
        ]
    }
]

const router = createRouter({
    history: createWebHashHistory(),
    routes
});

export default router;

页面代码注意

  • script js代码要用<script setup>方式, 和以前的 export default 方式不一样. 用该方式是将属性和方法都写在setup函数里,逻辑比较清晰
  • setup 方式定义的属性要用到 vuerefreactive 属性
  • refreactive 区别:
    • 相同:创建一个响应式对象。
    • 不同:
      • reactive 接受入参必须是对象形式,而 ref 可以是对象形式,也可以是一个单值。
      • 读取/赋值不一样,ref 必须从.value 属性中读取值
      • ref存在异步问题

登录页面

  • 配置路由 (上一步已完成)
  • src/view目录下新建 login.vue文件
  • 增加登陆代码
<template>
  <div class="login-container">
    <el-form class="login-form" :model="form" :rules="rules" ref="ruleFormRef">
      <h1 class="login-title">Login</h1>
      <el-form-item prop="username">
        <el-input v-model="form.username" placeholder="Username"></el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input
          type="password"
          v-model="form.password"
          placeholder="Password"
        ></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submitForm()">Login</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
  
  <script setup>
import router from "@/router";
import userApi from "../api/user";
import { ElMessage } from "element-plus";
import { reactive, ref } from "vue";

const form = reactive({
  username: "",
  password: "",
});
const ruleFormRef = ref();
const rules = reactive({
  username: [
    { required: true, message: "Please input your username", trigger: "blur" },
  ],
  password: [
    { required: true, message: "Please input your password", trigger: "blur" },
  ],
});

const submitForm = () => {
  router.push("/home");
  if (!ruleFormRef.value) return;
  ruleFormRef.value.validate(async(valid) => {
    if (valid) {
      const res = await userApi.login(form);
      if (res.data) {
        if (res.data.success) {
          router.push("/home");
        } else {
          ElMessage.error(res.data.message);
        }
      } else {
        ElMessage.error("服务器错误");
      }
    } else {
      return false;
    }
  });
}


</script>
  
  <style scoped>
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-color: #f5f5f5;
}

.login-form {
  width: 400px;
  background-color: #fff;
  padding: 20px;
  border-radius: 5px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.login-title {
  font-size: 24px;
  text-align: center;
  margin-bottom: 20px;
}
</style> 

首页

  • 配置路由 (上一步已完成)
  • src/view目录下新建 Home.vue文件
  • 增加代码
<template>
  <div class="main-container">
    <el-header class="header">
      <div class="logo">创斯维</div>
      <el-button class="logout-btn" type="text" @click="logout()"
        >退出系统</el-button
      >
    </el-header>
    <el-container>
      <el-aside class="aside">
        <el-menu
          default-active="1"
          class="el-menu-vertical-demo"
          @open="handleOpen"
          @close="handleClose"
        >
          <el-menu-item index="1" @click="saveActiveNav('/index')"
            ><i class="el-icon-s-home"></i>首页</el-menu-item
          >
          <el-submenu index="2">
            <template #title>
              <i class="el-icon-setting"></i>
              <span>系统设置</span>
            </template>
            <el-menu-item index="2-1" @click="saveActiveNav('/index')"
              >基本设置</el-menu-item
            >
            <el-menu-item index="2-2" @click="saveActiveNav('/index')"
              >高级设置</el-menu-item
            >
          </el-submenu>
          <el-menu-item index="3" @click="saveActiveNav('/user/index')"
            ><i class="el-icon-user"></i>用户管理</el-menu-item
          >
        </el-menu>
      </el-aside>

      <el-main class="main">
        <router-view></router-view>
      </el-main>
    </el-container>
  </div>
</template>
  
<script setup>
import { onBeforeMount, ref } from "vue";
import { useRouter } from 'vue-router'
const router = useRouter();

onBeforeMount(() => {
  activePath.value = sessionStorage.getItem("activePath")
    ? sessionStorage.getItem("activePath")
    : "/index";
});
let activePath = ref('');

const handleOpen = (key, keyPath) => {
  console.log(key, keyPath);
};

const handleClose = (key, keyPath) => {
  console.log(key, keyPath);
};

const saveActiveNav = (path) => {
  sessionStorage.setItem("activePath", path);
  activePath.value = path;
};

const logout = () => {
  sessionStorage.clear();
  router.push("/login");
};
</script>
  
<style scoped>
.main-container {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background-color: #409eff;
  color: #fff;
  padding: 0 20px;
}

.logo {
  font-size: 24px;
  font-weight: bold;
}

.logout-btn {
  color: #fff;
}

.aside {
  background-color: #f0f0f0;
}

.main {
  padding: 20px;
}
</style>

首页默认页

  • 配置路由 (上一步已完成)
  • src/view目录下新建 Welcome.vue文件
  • 增加代码(具体业务逻辑待实现)
<template>
    <div class="main">

    </div>
</template>
<style scoped>
.main{
    text-align: center;
    padding: 5px;
    background: white;
    height: 100%;
    background-repeat: no-repeat;
    background-size: 100% 100%;
    background-position-y: bottom;
}
</style>

用户列表页

  • 配置路由 (上一步已完成)
  • src/view目录下新建 user/Index.vue目录和文件
  • 增加代码
<template>
    <div>
        <el-card>
            <el-input style="width:440px;" @clear="searchUser" clearable v-model="searchForm.name" placeholder="请输入用户姓名" class="input-with-select">
            <template>
                <el-button @click="searchUser"><i class="el-icon-s-home"></i></el-button>
            </template>
            </el-input>
            <el-table :data="tableData" border style="width: 100%; margin-top: 20px;">
                <el-table-column prep="name" label="姓名" width="180"/>
                <el-table-column prep="age" label="年龄" width="180"/>
                <el-table-column label="操作" width="330">
                    <template #default="scope">
                        <el-button type="danger" size="small" @click="deleteUser(scope.row.id)">删除</el-button>
                        <el-button size="small" @click="() => router.push('/user/detail', {id: scope.row.id})">详情</el-button>
                    </template>
                </el-table-column>
            </el-table>
            <!--分页-->
            <el-pagination style="margin-top: 20px;" :current_page="searchForm.current" :page-size="searchForm.size" :page-sizes="[10,20,30,40]" layout="->,total, sizes, prev,next, jumper" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
        </el-card>
    </div>
</template>
<script setup>
import userApi from '../../api/user'
import { onMounted, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'

onMounted(()=>{
    getUserList()
})

let tableData = ref([]);
let total = ref(0);

const searchForm = reactive({
    current: 1,
    size: 10,
    name: ''
})

const getUserList = async()=>{
    const res = await userApi.getUserList(searchForm);
    tableData.value = res.data.data.records;
    total.value = res.data.data.total
}

const handleSizeChange = (size)=>{
    searchForm.size  = size;
    getUserList()
}

const handleCurrentChange = (current) => {
    searchForm.current = current;
    getUserList()
}

const searchUser = ()=>{
    searchForm.current=1;
    getUserList()
}

const deleteUser = (id)=>{
    ElMessageBox.confirm(
        '确定要删除该用户吗?',
        {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
        }
    ).then(async()=>{
        const res = await userApi.delUser({id: id});
        if (res.data.success){
            ElMessage.success('删除成功')
            getUserList()
        }else{
            ElMessage.error('删除失败')
        }
    }).catch(()=>{
        ElMessage({
            type:'info',
            message: '取消删除'
        })
    })
}

</script>
<style scoped>

</style>

用户详情页

  • 配置路由 (上一步已完成)
  • src/view/user目录下新建 Detail.vue文件
  • 增加代码
<template>
    <div>
        <el-card class="box-card">
            <template #header>
                <div class="card-header">
                    <span>用户详情</span>
                    <el-button size="mini" style="float: right" @click="router.get(-1)">返回</el-button>
                </div>
            </template>
            <el-form>
                <el-from-item label="姓名:">
                    {{ userDetail.name }}
                </el-from-item>
                <el-from-item label="年纪:">
                    {{ userDetail.age }}
                </el-from-item>
            </el-form>
        </el-card>
    </div>
</template>
<script setup>
import { onBeforeMount, reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router'
import userApi from '../../api/user'
const route = useRoute();
const router = useRouter();
const userDetail = reactive({
    id: ''
})

onBeforeMount(async() => {
    if (route.query.id){
        const res = await userApi.getUserDetail({id: route.query.id});
        Object.assign(userDetail, res.data.data);
    }
})

</script>
<style scoped>

</style>

全局跳转设置

  • vue3 中没有 Prototype 属性,使用 app.config.globalProperties 去替代
  • proxy 可以理解为 vue 的代理对象
  • getCurrentInstance 方法获取当前组件实例
  • 新建全局函数, assets目录下创建文件夹和文件 js/common.js
import router from "@/router";
export default {
    // 路由跳转
    changeView(url, queryParams){
        router.push({
            path: url,
            query: queryParams
        })
    }
}
  • main.js中配置全局函数
import commonJs from './assets/js/common'

app.config.globalProperties.$commonJs = commonJs
  • 页面中导入和使用(以login.vue为例)
import { getCurrentInstance, reactive, ref } from "vue";

const {proxy} = getCurrentInstance();

// 验证成功跳转该为:
//router.push("/home");
proxy.$commonJs.changeView('/home')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment