包括 element-plus 的安装、登录、svg icons 的处理(坑:svg 不显示的问题)
一、安装 element-plus
1 2 3
| npm install @element-plus/icons yarn add @element-plus/icons pnpm install @element-plus/icons
|
安装成功,会出现一个 element-plus 安装成功和一个按钮
二、公开路由表配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { createRouter, createWebHistory } from "vue-router";
const publicRoutes = [ { path: "/login", component: () => import("@/views/login/index"), }, ];
const router = createRouter({ history: createWebHistory(), routes: publicRoutes, });
export default router;
|
三、登录页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <template> <div class="login-container"> <el-form class="login-form" ref="loginFormRef" :model="loginForm" :rules="loginRules" > <div class="title-container"> <h3 class="title">后台管理系统</h3> </div> <el-form-item prop="username"> <span class="svg-container"> <svg-icon icon="user" /> </span> <el-input placeholder="username" name="username" type="text" v-model="loginForm.username" ></el-input> </el-form-item> <el-form-item> <span class="svg-container"> <svg-icon icon="password" /> </span> <el-input placeholder="password" name="password" :type="passwordType" v-model="loginForm.password" ></el-input> <span class="show-pwd"> <svg-icon :icon="passwordType === 'password' ? 'eye' : 'eye-open'" @click="onChangePwdType" ></svg-icon> </span> </el-form-item> <el-button type="primary" style="width:100%;margin-bottom: 30px" :loading="loading" @click="handleLogin" >login </el-button> </el-form> </div> </template>
|
js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| <script setup> import { ref, computed } from 'vue' import { validatePassword } from './rules' import { useStore } from 'vuex' import { useRouter } from 'vue-router'
const loginForm = ref({ username: 'super-admin', password: '123456' })
const loginRules = ref({ username: [{ required: true, trigger: 'blur', message: computed(() => { return 'error' }) } ], password: [ { required: true, trigger: 'blur', validator: validatePassword() } ] })
const passwordType = ref('password') const onChangePwdType = () => { if (passwordType.value === 'password') { passwordType.value = 'text' } else { passwordType.value = 'password' } }
const loading = ref(false) const loginFormRef = ref(null) const router = useRouter() const store = useStore() const handleLogin = () => { loginFormRef.value.validate(valid => { if (!valid) return loading.value = false store.dispatch('user/login', loginForm.value).then(() => { loading.value = false router.push('/') }).catch(err => { console.log(err) loading.value = false }) }) }
</script>
|
校验规则:
1 2 3 4 5 6 7 8 9 10
| export const validatePassword = () => { return (rule, value, callback) => { if (value.length < 6) { callback(new Error("error")); } else { callback(); } }; };
|
登录页样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| <style lang="scss" scoped> $bgColor: #2d3a4b; $dark_gray: #889aa4; $light_gray: #eee; $cursor: #fff;
.login-container { min-height: 100%; width: 100%; background-color: $bgColor; overflow: hidden;
.login-form { position: relative; width: 520px; max-width: 100%; padding: 160px 35px 0; margin: 0 auto; overflow: hidden;
::v-deep .el-form-item { border: 1px solid rgba(255, 255, 255, 0.1); background-color: rgba(0, 0, 0, 0.1); border-radius: 5px; color: #454545; }
::v-deep .el-input { display: inline-block; height: 47px; width: 85%;
input { background: transparent; border: 0px; -webkit-appearance: none; border-radius: 0px; padding: 12px 5px 12px 15px; color: $light_gray; height: 47px; caret-color: $cursor; } } } }
.svg-container { padding: 6px 5px 6px 15px; color: $dark_gray; vertical-align: middle; display: inline-block; }
.title-container { position: relative;
.title { font-size: 26px; color: $light_gray; margin: 0px auto 40px auto; text-align: center; font-weight: bold; }
::v-deep .lang-select { position: absolute; top: 4px; right: 0; background-color: white; font-size: 22px; padding: 4px; border-radius: 4px; cursor: pointer; } }
.show-pwd { position: absolute; right: 10px; top: 7px; font-size: 16px; color: $dark_gray; cursor: pointer; user-select: none; } </style>
|
全局样式:src/styles/index.scss
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| html, body { height: 100%; margin: 0; padding: 0; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; }
#app { height: 100%; }
*, *:before, *:after { box-sizing: inherit; margin: 0; padding: 0; }
a:focus, a:active { outline: none; }
a, a:focus, a:hover { cursor: pointer; color: inherit; text-decoration: none; }
div:focus { outline: none; }
.clearfix { &:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } }
|
导入全局样式
1
| import "./styles/index.scss";
|
这里有个坑:
登录页输入框的 svg 小图标不显示
四、icon 图标处理方法:SvgIcon
结合 vue-element-admin 项目的借鉴,将 icon 图标分为 element-plus 图标和自定义引入图标
自定义 SVG 图标组件的能力:显示外部引入图标和项目内的 svg 图标
封装 SvgIcon 组件
1 2 3 4 5 6 7 8 9 10 11
| <template> <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" :class="className" /> <svg v-else class="svg-icon" :class="className" aria-hidden="true"> <use :xlink:href="iconName" /> </svg> </template>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
export function isExternal(path) { return /^(https?:|mailto:|tel:)/.test(path) }
<script setup> import { isExternal as external } from '@/utils/validate' import { defineProps, computed } from 'vue'
const props = defineProps({ icon: { type: String, required: true }, className: { type: String, default: '' } })
const isExternal = computed(() => external(props.icon))
const styleExternalIcon = computed(() => ({ mask: `url(${props.icon}) no-repeat 50% 50%`, '-webkit-mask': `url(${props.icon}) no-repeat 50% 50%` }))
const iconName = computed(() => `#icon-${props.icon}`)
</script>
|
样式
1 2 3 4 5 6 7 8 9 10 11 12 13
| .svg-icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; }
.svg-external-icon { background-color: currentColor; mask-size: cover !important; display: inline-block; }
|
上面只处理了外部 svg 图标的展示,还要处理 element-plus 的图标
// 创建 icons 文件夹 内部包含:内部 svg 图标、SvgIcon 全局注册
1 2 3 4 5 6 7 8 9 10 11 12
| import SvgIcon from "@/components/SvgIcon";
const svgRequire = require.context("./svg", false, /\.svg$/);
svgRequire.keys().forEach((SvgIcon) => svgRequire(SvgIcon));
export default (app) => { app.component("svg-icon", SvgIcon); };
|
main.js 引入 svgIcon
1 2 3
| import installIcons from "@/icons"; installIcons(app);
|
此时,svg图标仍然不显示
,还需要使用 svg-sprite-loader 处理 svg 图标
1
| npm i --save-dev svg-sprite-loader@6.0.9
|
配置 vue.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const path = require("path"); function resolve(dir) { return path.join(__dirname, dir); }
module.exports = { chainWebpack(config) { config.module.rule("svg").exclude.add(resolve("src/icons")).end(); config.module .rule("icons") .test(/\.svg$/) .include.add(resolve("src/icons")) .end() .use("svg-sprite-loader") .loader("svg-sprite-loader") .options({ symbolId: "icon-[name]", }) .end(); }, };
|
至此,svg 图标可以正常显示。