Skip to content

Commit

Permalink
refactor: 重构app-icon组件,优化逻辑,切换到srcipt-setup
Browse files Browse the repository at this point in the history
  • Loading branch information
someGenki committed Apr 26, 2022
1 parent 0b43d28 commit 9e67a47
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 53 deletions.
85 changes: 48 additions & 37 deletions src/components/AppIcon/index.vue
Original file line number Diff line number Diff line change
@@ -1,54 +1,65 @@
<template>
<i v-bind="$attrs" style="display: inline-flex">
<el-icon v-if="iconType === 'ElIcon'" v-bind="iconProp">
<component :is="iconName" />
<i v-bind="$attrs" class="app-icon" :class="{ text }">
<el-icon v-if="isElIcon" :color="color" :size="parseInt(size)">
<component :is="icon" />
</el-icon>
<svg v-else v-bind="iconProp">
<use :xlink:href.attr="iconName" />
</svg>
<span v-if="text">{{ text }}</span>
</i>
</template>

<script>
<script setup>
import { ElIcon } from 'element-plus'
/**
* <app-icon icon="el-icon-right" /> el-开头将渲染成 <i/> el图标支持小驼峰和中划线分割命名
* <app-icon icon="github" size="32" /> 其他则渲染成 <svg>
* @example <app-icon icon="el-icon-right" /> el-开头将渲染成 <i/> el图标支持小驼峰和中划线分割命名
* <app-icon icon="github" size="32" /> 其他则渲染成 <svg>
*
* 附: 动态组件 <component :is="xxx" />模板编译的结果:_resolveDynamicComponent(xxx)
* 该函数被定义在 runtime-core/src/helpers/resolveAssets.ts,内部调用app.context获取对应已注册的组件
* component 内置组件除了支持 is 绑定之外,也支持其他属性绑定和事件绑定
*
* 如果svg标签的path标签已设置fill="color" 则该path的颜色也不会被更改
*/
import { ElIcon } from 'element-plus'
const props = defineProps({
icon: { type: String, required: true },
size: { default: 16 },
color: { default: 'inherit' },
text: { type: [String, Number] },
})
export default {
name: 'AppIcon',
components: { ElIcon },
props: {
icon: { type: String, required: true },
// 如果svg标签的path标签已设置fill="color" 则该path的颜色也不会被更改
color: { type: String },
size: { default: 16 },
},
setup(props) {
const { icon, color, size } = props
let iconType, iconName, iconProp
if (icon.startsWith('el-icon-') || icon.startsWith('elIcon')) {
iconType = 'ElIcon'
iconName = icon
iconProp = { color, size: Number(size) }
} else {
// 根据名字生成id使用svg>symbol里的svg标签(已提前注入到index.html中body下的svg标签)
iconType = 'Custom'
iconName = '#icon-' + icon
const sizePx = size + 'px'
const style = { width: sizePx, height: sizePx }
if (color) {
style.color = color
style.fill = 'currentColor'
}
iconProp = { style }
}
return { iconType, iconName, iconProp }
},
const isElIcon = /^el-?/i.test(props.icon)
let iconName, iconProp
// 如果不是el-icon,则使用svg sprites图标,以 #icon- 为id前缀进行引用
if (!isElIcon) {
iconName = '#icon-' + props.icon
const size = isNaN(props.size) ? props.size : props.size + 'px'
const style = { width: size, height: size }
if (props.color) {
style.color = props.color
style.fill = 'currentColor'
}
iconProp = { style }
}
</script>

<style lang="scss">
@import '/src/styles/variables';
.app-icon {
display: inline-flex;
}
.app-icon.text {
align-items: center;
cursor: pointer;
}
.app-icon.text:hover {
color: #1d7dfa;
}
</style>
59 changes: 59 additions & 0 deletions src/directives/default-img.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import defaultAvatar from '/src/assets/images/dio.jpg'
import defaultBackground from '/src/assets/images/cute.jpg'
import { getStrColor } from '../utils/process'

/**
* 项目难点标记:检测图片存在,如果不存在则用用户的昵称的首字符作为头像,绘制svg
* 检测图片是否存在
* @param url
*/
const imageIsExist = function (url) {
return new Promise((resolve) => {
let img = new Image()
img.src = url
img.onload = () => {
if (img.complete === true) {
resolve(true)
img = null
}
}
img.onerror = () => {
resolve(false)
img = null
}
})
}

function genSvgImg(text, color, size = 36) {
text = text.substring(0, 1)
color = encodeURIComponent(color)
return `data:image/svg+xml;utf8,
<svg viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="${color}"/>
<text x="50%" y="53%" text-anchor="middle" dominant-baseline="middle" fill='%23f1f1f1'>${text}</text>
</svg>`
}

/**
* 当图片加载失败时,显示默认图片
* 参数可选:'avatar' | 'background' | string
* <img :src="xxxx" v-default-img.avatar />
*/
export default async function defaultImg(el, binding) {
// 需要显示默认图片(当图片原本的src属性有错时)的类型
const { value, modifiers } = binding
// 图片原本的src
const realURL = el.src
// 当原本图片不存在时,根据参数返回不同的图片url
const exist = await imageIsExist(realURL)
if (exist) return // 图片可正常加载,不做任何处理
if (value) {
el.setAttribute('src', genSvgImg(value, getStrColor(value)))
} else if (modifiers.avatar) {
el.setAttribute('src', defaultAvatar)
} else if (modifiers.background) {
el.setAttribute('src', defaultBackground)
} else {
el.remove() // 什么都不加 v-default-img 时 移除图片
}
}
22 changes: 11 additions & 11 deletions src/layout/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="app-wrapper">
<sidebar />
<sidebar/>
<!-- ↑ 侧边菜单栏 ↑ -->
<div
class="mask-zIndex99"
Expand All @@ -10,25 +10,25 @@
<!-- ↑ 移动端模式下展开侧边栏所出现的遮罩层 ↑ -->
<div class="main-container">
<header :class="{ 'fixed-header': fixedHeader }">
<nav-bar />
<tab-bar v-if="showTabBar" />
<nav-bar/>
<tab-bar v-if="showTabBar"/>
</header>
<app-main />
<app-main/>
<!-- ↑ 内容主体展示区域 ↑ -->
</div>
<settings />
<settings/>
<!-- ↑ 默认隐藏在右边的设置面板 ↑ -->
</div>
</template>

<script setup>
// <script setup>教程:https://v3.cn.vuejs.org/api/sfc-script-setup.html
import { toRefs } from 'vue'
import { throttle } from '/src/utils/util'
import { useStyleStore } from '/src/store/style'
import { useLayoutStore } from '/src/store/layout'
import { batchSaveSetting } from '/src/utils/storage'
import { AppMain, NavBar, Settings, Sidebar, TabBar } from './components'
import {toRefs} from 'vue'
import {throttle} from '/src/utils/util'
import {useStyleStore} from '/src/store/style'
import {useLayoutStore} from '/src/store/layout'
import {batchSaveSetting} from '/src/utils/storage'
import {AppMain, NavBar, Settings, Sidebar, TabBar} from './components'
const styleStore = useStyleStore()
const layoutStore = useLayoutStore()
Expand Down
5 changes: 3 additions & 2 deletions src/store/layout.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineStore } from 'pinia'
import { getSetting } from '/src/utils/storage'
import {defineStore} from 'pinia'
import {getSetting} from '/src/utils/storage'
// import variables from '/src/styles/vars.module.scss'

const noRecordViewPath = ['/login']

Expand Down
7 changes: 7 additions & 0 deletions src/styles/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ $scrollbar-width: 8px;
$xl-width: 1920px; // 大大屏幕尺寸
$lg-width: 1200px; // 大屏幕桌面尺寸
$sm-width: 768px; // 小屏幕平板尺寸

:export {
smWidth: $sm-width;
navbarHeight: $navbar-height;
tabBarHeight: $tabBar-height;
sidebarLogoHeight: $sidebar-logo-height;
}
11 changes: 11 additions & 0 deletions src/styles/vars.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
$sidebar-logo-height: 42px;
$navbar-height: 42px;
$tabBar-height: 30px;
$sm-width: 768px;

:export {
smWidth: $sm-width;
navbarHeight: $navbar-height;
tabBarHeight: $tabBar-height;
sidebarLogoHeight: $sidebar-logo-height;
}
4 changes: 1 addition & 3 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,7 @@ export default ({command}) => {
css: {
preprocessorOptions: {
scss: {
additionalData: `
@import "/src/styles/_variables";
`,
additionalData: `@import "/src/styles/_variables";\n`,
},
},

Expand Down

0 comments on commit 9e67a47

Please sign in to comment.