Introducing new features. 完成本地svg图标的导入使用

This commit is contained in:
aeizzz 2023-03-03 14:39:17 +08:00
parent 606f918cf7
commit 184708724b
10 changed files with 246 additions and 97 deletions

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,72 @@
import { readFileSync, readdirSync } from 'fs';
let idPerfix = '';
const iconNames: string[] = [];
const svgTitle = /<svg([^>+].*?)>/;
const clearHeightWidth = /(width|height)="([^>+].*?)"/g;
const hasViewBox = /(viewBox="[^>+].*?")/g;
const clearReturn = /(\r)|(\n)/g;
// 清理 svg 的 fill
const clearFill = /(fill="[^>+].*?")/g;
function findSvgFile(dir: string): string[] {
const svgRes = [];
const dirents = readdirSync(dir, {
withFileTypes: true,
});
for (const dirent of dirents) {
iconNames.push(`${idPerfix}-${dirent.name.replace('.svg', '')}`);
if (dirent.isDirectory()) {
svgRes.push(...findSvgFile(dir + dirent.name + '/'));
} else {
const svg = readFileSync(dir + dirent.name)
.toString()
.replace(clearReturn, '')
.replace(clearFill, 'fill=""')
.replace(svgTitle, ($1, $2) => {
let width = 0;
let height = 0;
let content = $2.replace(clearHeightWidth, (s1: string, s2: string, s3: number) => {
if (s2 === 'width') {
width = s3;
} else if (s2 === 'height') {
height = s3;
}
return '';
});
if (!hasViewBox.test($2)) {
content += `viewBox="0 0 ${width} ${height}"`;
}
return `<symbol id="${idPerfix}-${dirent.name.replace('.svg', '')}" ${content}>`;
})
.replace('</svg>', '</symbol>');
svgRes.push(svg);
}
}
return svgRes;
}
export const svgBuilder = (path: string, perfix = 'local') => {
if (path === '') return;
idPerfix = perfix;
const res = findSvgFile(path);
console.log(res, 'res');
return {
name: 'svg-transform',
transformIndexHtml(html: string) {
/* eslint-disable */
return html.replace(
'<body>',
`
<body>
<svg id="local-icon" data-icon-name="${iconNames.join(
','
)}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
${res.join('')}
</svg>
`
);
/* eslint-enable */
},
};
};

View File

@ -15,9 +15,7 @@
<SvgIcon
:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
class="font14"
v-if="state.fontIconPrefix === '' ? prepend?.indexOf('ele-') > -1 : state.fontIconPrefix?.indexOf('ele-') > -1"
/>
<i v-else :class="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"></i>
</template>
</el-input>
<el-popover
@ -42,6 +40,9 @@
<el-tab-pane lazy label="awe" name="awe">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
<el-tab-pane lazy label="local" name="local">
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
</el-tab-pane>
</el-tabs>
</div>
</template>
@ -116,6 +117,7 @@ const state = reactive({
ali: [],
ele: [],
awe: [],
local: []
},
});
@ -148,6 +150,7 @@ const fontIconTabNameList = () => {
if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
else if (state.fontIconTabActive === 'local') iconList = state.fontIconList.local;
return iconList;
};
// icon
@ -162,6 +165,7 @@ const initFontIconName = () => {
if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
else if (props.modelValue!.indexOf('local') > -1) name = 'local';
// tab
state.fontIconTabActive = name;
return name;
@ -186,7 +190,12 @@ const initFontIconData = async (name: string) => {
await initIconfont.awe().then((res: any) => {
state.fontIconList.awe = res.map((i: string) => `fa ${i}`);
});
}
}else if(name === 'local'){
if (state.fontIconList.local.length > 0) return;
await initIconfont.local().then((res: any) => {
state.fontIconList.local = res.map((i: string) => `${i}`);
});
}
// input placeholder
// https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
state.fontIconPlaceholder = props.placeholder;

View File

@ -5,7 +5,11 @@
<div v-else-if="isShowIconImg" :style="setIconImgOutStyle">
<img :src="getIconName" :style="setIconSvgInsStyle" />
</div>
<svg v-else-if="isShowLocalSvg" class="svg-icon icon" :style="setIconImgOutStyle">
<use :href="`#${getIconName}`" />
</svg>
<i v-else :class="getIconName" :style="setIconSvgStyle" />
</template>
<script setup lang="ts" name="svgIcon">
@ -43,6 +47,10 @@ const isShowIconSvg = computed(() => {
const isShowIconImg = computed(() => {
return linesString.find((str) => props.name?.startsWith(str));
});
const isShowLocalSvg = computed(() => {
return props?.name?.startsWith('local-');
})
//
const setIconSvgStyle = computed(() => {
return `font-size: ${props.size}px;color: ${props.color};`;

View File

@ -42,14 +42,13 @@
<a target="_blank" :href="item.repUrl"><img :src="item.repUrl" style="width: 100px"></a>
</div>
<div v-if="item.repType === 'voice'">
<img :src="WxVoice" style="width: 100px" @click="loadVideo(item)">
<el-button link @click="loadVideo(item)"></el-button>
<SvgIcon name="local-wx-voice" :size="80" @click="loadVideo(item)"></SvgIcon>
</div>
<div v-if="item.repType === 'video'" style="text-align: center">
<img :src="WxVideo" style="width: 100px" @click="loadVideo(item)">
<SvgIcon name="local-wx-video" :size="80" @click="loadVideo(item)"></SvgIcon>
</div>
<div v-if="item.repType === 'shortvideo'" style="text-align: center">
<img :src="WxVideo" style="width: 100px" @click="loadVideo(item)">
<svg-icon name="local-wx-video" :size="80" @click="loadVideo(item)"></svg-icon>
</div>
<div v-if="item.repType === 'location'">
<el-link
@ -97,8 +96,6 @@
<script setup lang="ts" name="wx-msg">
import { fetchList,addObj } from '/@/api/mp/wx-fans-msg'
import {useMessage} from "/@/hooks/message";
import WxVideo from '/@/assets/icon/wx-video.svg'
import WxVoice from '/@/assets/icon/wx-voice.svg'
import {getMaterialVideo} from "/@/api/mp/wx-material";
const WxReply = defineAsyncComponent(() => import("../wx-reply/index.vue"))

View File

@ -27,7 +27,7 @@
</el-button>
</el-col>
<el-col :span="12" class="col-add">
<wx-file-upload :data="uploadData" @success="handelImage"></wx-file-upload>
<wx-file-upload :data="uploadData" @success="handelUpload"></wx-file-upload>
</el-col>
</el-row>
</div>
@ -40,7 +40,7 @@
<div v-if="objData.repName" class="select-item">
<p class="item-name">{{ objData.repName }}</p>
<div class="item-infos">
<!-- <WxVoicePlayer :obj-data="Object.assign(tempPlayerObj,{repMediaId: objData.media_id, repName: objData.repName})"></WxVoicePlayer>-->
<img :src="WxVoice" style="width: 100px" @click="loadVideo(item)">
</div>
<el-row class="ope-row">
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj"></el-button>
@ -53,7 +53,7 @@
</el-button>
</el-col>
<el-col :span="12" class="col-add">
<wx-file-upload :data="uploadData"></wx-file-upload>
<wx-file-upload :data="uploadData" @success="handelUpload"></wx-file-upload>
</el-col>
</el-row>
</div>
@ -62,7 +62,7 @@
<el-tab-pane name="video" label="video">
<template #label><i class="el-icon-share"></i> 视频</template>
<el-row style="text-align: center">
<el-row style="text-align: center;flex: 1">
<el-input v-model="objData.repName" placeholder="请输入标题"></el-input>
<el-input v-model="objData.repDesc" placeholder="请输入描述"></el-input>
<div style="text-align: center;">
@ -106,7 +106,8 @@
<script setup lang="ts" name="wx-reply">
import {getMaterialVideo} from "/@/api/mp/wx-material";
import {useMessage} from "/@/hooks/message";
import WxVideo from '/@/assets/icon/wx-video.svg'
import WxVoice from '/@/assets/icon/wx-voice.svg'
const WxMaterialSelect = defineAsyncComponent(() => import("/@/components/wechart/wx-material-select/main.vue"))
const WxFileUpload = defineAsyncComponent(() => import("/@/components/wechart/fileUpload/index.vue"))
@ -136,10 +137,30 @@ const uploadData = reactive({
appId: props.objData.appId
})
const tempObj = ref() as any
const handleClick = (tab) => {
uploadData.mediaType = tab.paneName
uploadData.appId = props.objData.appId
const tempObjItem = tempObj.value[props.objData.repType]
if (tempObjItem) {
props.objData.repName = tempObjItem.repName ? tempObjItem.repName : null
props.objData.repMediaId = tempObjItem.repMediaId ? tempObjItem.repMediaId : null
props.objData.media_id = tempObjItem.media_id ? tempObjItem.media_id : null
props.objData.repUrl = tempObjItem.repUrl ? tempObjItem.repUrl : null
props.objData.content = tempObjItem.content ? tempObjItem.content : null
props.objData.repDesc = tempObjItem.repDesc ? tempObjItem.repDesc : null
} else {
props.objData.repName = ''
props.objData.repMediaId = ''
props.objData.media_id = ''
props.objData.repUrl = ''
props.objData.content = ''
props.objData.repDesc = ''
}
}
const deleteObj = () => {
@ -185,14 +206,18 @@ const selectMaterial = (item, appId) => {
mediaId: item.mediaId,
appId: appId
}).then(response => {
const data = response.data.data
tempObjItem.repDesc = data.description
const data = response.data
tempObjItem.repDesc = data.description || ''
tempObjItem.repUrl = data.downUrl
props.objData.repName = data.title
props.objData.repDesc = data.description || ''
props.objData.repUrl = data.downUrl
})
}
tempObj.value[props.objData.repType] = tempObjItem
}
const handelImage = (response) => {
const handelUpload = (response) => {
if (response.code === 0) {
const item = response.data
selectMaterial(item,props.objData.appId)
@ -201,6 +226,16 @@ const handelImage = (response) => {
}
}
const loadVideo = (item) => {
getMaterialVideo({
mediaId: item.repMediaId,
appId: item.appId
}).then(response => {
const data = response.data
window.open(data.downUrl,'target','');
})
}
</script>
<style scoped lang="scss">

View File

@ -76,6 +76,29 @@ const getAwesomeIconfont = () => {
});
};
/*
*
* /src/assets/iconssvg
*/
const getLocalIconfontNames = () => {
return new Promise<string[]>((resolve, reject) => {
nextTick(() => {
let iconfonts: string[] = [];
const svgEl = document.getElementById('local-icon');
if (svgEl?.dataset.iconName) {
iconfonts = (svgEl?.dataset.iconName as string).split(',');
}
if (iconfonts.length > 0) {
resolve(iconfonts);
} else {
reject('No Local Icons');
}
});
});
};
/**
* `document.styleSheets`
* @method ali `<i class="iconfont 图标类名"></i>`
@ -95,6 +118,9 @@ const initIconfont = {
awe: () => {
return getAwesomeIconfont();
},
local: () => {
return getLocalIconfontNames();
},
};
// 导出方法

View File

@ -8,7 +8,7 @@
</div>
<div @click="handleClick('wechat')">
<span :style="{ backgroundColor: '#6ba2d6' }" class="container">
<i icon-class="wechat" class="iconfont icon-weixin"/>
<i icon="wechat" class="iconfont icon-weixin"/>
</span>
<p class="title">微信</p>
</div>

View File

@ -1,95 +1,97 @@
import vue from '@vitejs/plugin-vue';
import {resolve} from 'path';
import {defineConfig, loadEnv, ConfigEnv} from 'vite';
import { resolve } from 'path';
import { defineConfig, loadEnv, ConfigEnv } from 'vite';
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
// vue3 自动引入
import AutoImport from 'unplugin-auto-import/vite'
import topLevelAwait from 'vite-plugin-top-level-await'
import AutoImport from 'unplugin-auto-import/vite';
import topLevelAwait from 'vite-plugin-top-level-await';
import { svgBuilder } from '/@/components/iconSelector/index';
// 按需加载
import {createStyleImportPlugin, VxeTableResolve} from 'vite-plugin-style-import'
import viteCompression from "vite-plugin-compression";
import { createStyleImportPlugin, VxeTableResolve } from 'vite-plugin-style-import';
import viteCompression from 'vite-plugin-compression';
const pathResolve = (dir: string) => {
return resolve(__dirname, '.', dir);
return resolve(__dirname, '.', dir);
};
const alias: Record<string, string> = {
'/@': pathResolve('./src/'),
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js',
'/@': pathResolve('./src/'),
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js',
};
const viteConfig = defineConfig((mode: ConfigEnv) => {
const env = loadEnv(mode.mode, process.cwd());
return {
plugins: [vue(), vueSetupExtend(), AutoImport({
imports: [
'vue',
'vue-router',
'pinia'
],
dts: './auto-imports.d.ts',
}), createStyleImportPlugin({
resolves: [
VxeTableResolve()
],
}), topLevelAwait({
// The export name of top-level await promise for each chunk module
promiseExportName: '__tla',
// The function to generate import names of top-level await promise in each chunk module
promiseImportName: i => `__tla_${i}`
}), viteCompression({
deleteOriginFile: true
})
],
root: process.cwd(),
resolve: {alias},
base: mode.command === 'serve' ? './' : env.VITE_PUBLIC_PATH,
optimizeDeps: {
include: ['element-plus/lib/locale/lang/zh-cn', 'element-plus/lib/locale/lang/en'],
},
server: {
host: '0.0.0.0',
port: env.VITE_PORT as unknown as number,
open: env.VITE_OPEN,
hmr: true,
proxy: {
'/admin': {
target: env.VITE_ADMIN_PROXY_PATH,
ws: true,
changeOrigin: true,
},
'/gen': {
target: env.VITE_GEN_PROXY_PATH,
ws: true,
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
chunkSizeWarningLimit: 1500,
rollupOptions: {
output: {
entryFileNames: `assets/[name].[hash].js`,
chunkFileNames: `assets/[name].[hash].js`,
assetFileNames: `assets/[name].[hash].[ext]`,
compact: true,
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],
echarts: ['echarts'],
},
},
},
},
css: {preprocessorOptions: {css: {charset: false}}},
define: {
__VUE_I18N_LEGACY_API__: JSON.stringify(false),
__VUE_I18N_FULL_INSTALL__: JSON.stringify(false),
__INTLIFY_PROD_DEVTOOLS__: JSON.stringify(false),
__VERSION__: JSON.stringify(process.env.npm_package_version),
},
};
const env = loadEnv(mode.mode, process.cwd());
return {
plugins: [
vue(),
svgBuilder('./src/assets/icons/'),
vueSetupExtend(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
dts: './auto-imports.d.ts',
}),
createStyleImportPlugin({
resolves: [VxeTableResolve()],
}),
topLevelAwait({
// The export name of top-level await promise for each chunk module
promiseExportName: '__tla',
// The function to generate import names of top-level await promise in each chunk module
promiseImportName: (i) => `__tla_${i}`,
}),
viteCompression({
deleteOriginFile: true,
}),
],
root: process.cwd(),
resolve: { alias },
base: mode.command === 'serve' ? './' : env.VITE_PUBLIC_PATH,
optimizeDeps: {
include: ['element-plus/lib/locale/lang/zh-cn', 'element-plus/lib/locale/lang/en'],
},
server: {
host: '0.0.0.0',
port: env.VITE_PORT as unknown as number,
open: env.VITE_OPEN,
hmr: true,
proxy: {
'/admin': {
target: env.VITE_ADMIN_PROXY_PATH,
ws: true,
changeOrigin: true,
},
'/gen': {
target: env.VITE_GEN_PROXY_PATH,
ws: true,
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
chunkSizeWarningLimit: 1500,
rollupOptions: {
output: {
entryFileNames: `assets/[name].[hash].js`,
chunkFileNames: `assets/[name].[hash].js`,
assetFileNames: `assets/[name].[hash].[ext]`,
compact: true,
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],
echarts: ['echarts'],
},
},
},
},
css: { preprocessorOptions: { css: { charset: false } } },
define: {
__VUE_I18N_LEGACY_API__: JSON.stringify(false),
__VUE_I18N_FULL_INSTALL__: JSON.stringify(false),
__INTLIFY_PROD_DEVTOOLS__: JSON.stringify(false),
__VERSION__: JSON.stringify(process.env.npm_package_version),
},
};
});
export default viteConfig;