1695b7c4 by tianhaohao@pashanhoo.com

Merge branch 'dev' of http://yun.pashanhoo.com:9090/bdc/bdcdj-web into dev

2 parents 5c3473de 5d390fda
......@@ -18,6 +18,7 @@
"diagram-js": "^6.8.2",
"js-cookie": "2.2.0",
"lodash": "^4.17.21",
"mxdraw": "^0.1.157",
"node-sass": "^4.14.1",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
......
{
"TITLE": "不动产登记系统",
"SERVERAPI": "/bdcdj"
"SERVERAPI": "/bdcdj",
"ip": "http://192.168.2.38"
}
\ No newline at end of file
......
{
"TITLE": "不动产登记系统",
"SERVERAPI": "/bdcdj"
"SERVERAPI": "/bdcdj",
"ip": "http://192.168.2.38"
}
\ No newline at end of file
......
{
"TITLE": "不动产登记系统",
"SERVERAPI": "service-bdcdj-th"
"SERVERAPI": "service-bdcdj-th",
"ip": "http://192.168.2.38"
}
\ No newline at end of file
......
<!--
* @Description:
* @Autor: renchao
* @LastEditTime: 2023-05-16 09:50:40
* @LastEditTime: 2023-05-31 16:04:35
-->
<!DOCTYPE html>
<html>
......@@ -19,7 +19,19 @@
</head>
<script>
window.baseUrl = location.origin || location.protocol + '//' + location.host
const authorization = "bearer ATT-6-80pnPMkrvVc-i1qIGQrdkjKIPc-cH1mD"
window._config = {
// 是否微服务模式,业务系统根据需要读取
cloudEnable: false,
// 是否启用单点登录
casEnable: true,
// cas 基地址
casBaseURL: 'http://192.168.2.38/cas',
services: {
// 配置到 contextPath 前一级
management: 'http://192.168.2.38',
business: 'http://localhost:7001'
}
}
fetch('<%= BASE_URL %>config.json')
.then(response => response.json())
.then(config => {
......
......@@ -231,5 +231,31 @@ export function getZjgcdyList (data) {
})
}
// 数据上报推送查询
export function getSjsbPushList (data) {
return request({
url: SERVER.SERVERAPI + '/rest/system/sysPushRecord/list',
method: 'post',
data
})
}
// 数据上报推送
export function pushSjsbRecord (data) {
return request({
url: SERVER.SERVERAPI + '/rest/system/sysPushRecord/push',
method: 'post',
data
})
}
// 数据上报推送
export function detail (bsm) {
return request({
url: SERVER.SERVERAPI + '/rest/system/sysPushRecord/detail?bsm=' + bsm,
method: 'get'
})
}
......
......@@ -27,6 +27,7 @@
</div>
</template>
<script>
import axios from 'axios'
import { mapGetters } from 'vuex'
import NoticeBar from '@/components/NoticeBar/index'
import {
......@@ -72,9 +73,20 @@
})
},
logout () {
const url = baseUrl + "/sso-logout?redirect_uri=" + baseUrl + "/bdcdj";
window.open(url, "_self");
sessionStorage.removeItem("navList");
axios.post(this.BASE_API.ip + "/management/logout").then(() => {
localStorage.removeItem('token')
if (window._config.casEnable) {
window.location.href = window._config.casBaseURL + '/logout?service=' + encodeURIComponent(window.location.href);
} else {
this.$router.push({
path: '/login',
replace: true,
query: {
redirect: router.currentRoute.value.fullPath
}
})
}
})
},
themeChange (val) {
......
/*
* @Description:
* @Autor: renchao
* @LastEditTime: 2023-05-06 09:14:47
* @LastEditTime: 2023-05-30 15:28:52
*/
import Vue from 'vue'
import App from './App'
......@@ -60,7 +60,7 @@ Object.keys(filters).forEach(key => {
})
Vue.config.productionTip = false
axios.get("./config.json")
axios.get("/config.json")
.then((res) => {
Vue.prototype.BASE_API = res.data
localStorage.setItem('ApiUrl', JSON.stringify(res.data));
......
/*
* @Description: 项目权限
* @Autor: renchao
* @LastEditTime: 2023-05-16 14:10:26
* @LastEditTime: 2023-05-31 15:55:14
*/
import Vue from 'vue'
import router from './router'
import store from './store'
import axios from 'axios'
import { getMenuInfo } from '@/api/user'
import { getUrlParam } from '@/utils/operation'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import getPageTitle from '@/utils/get-page-title'
......@@ -19,6 +21,42 @@ router.beforeEach(async (to, from, next) => {
document.title = getPageTitle(to.meta.title)
let hasAddDict = store.state.dict.addDict
let hasAddRoute = store.state.permission.addRoutes
// cas操作
const token = localStorage.getItem("token")
if (to.path === '/login') {
if (token) {
next('/')
} else {
next()
}
return
}
if (window._config.casEnable === true) {
let locationUrl = window.location.protocol + '//' + window.location.host + window.location.pathname;
if (!token) {
let ticket = getUrlParam('ticket');
if (ticket) {
axios.get(Vue.prototype.BASE_API.ip + "/management/cas/validate", {
params: {
'ticket': ticket,
'service': locationUrl
}
}).then(async (res) => {
localStorage.setItem('token', res.data.content.accessToken)
window.location.href = localStorage.getItem('location')
}).catch(e => {
console.log(e)
})
} else {
localStorage.setItem("location", window.location.href)
window.location.href = window._config.casBaseURL + '/login?service=' + encodeURIComponent(locationUrl);
permission()
}
} else {
permission()
}
async function permission () {
if (!hasAddDict) {
store.dispatch('dict/generateDic')
}
......@@ -38,6 +76,24 @@ router.beforeEach(async (to, from, next) => {
next('/home')
}
}
}
} else {
if (!token) {
const redirectData = {
path: '/login',
replace: true,
}
if (to.path) {
redirectData.query = {
...redirectData.query,
redirect: to.path,
};
}
next(redirectData)
return
}
next()
}
NProgress.done()
})
router.afterEach(to => {
......
/*
* @Description: 全局路由
* @Autor: renchao
* @LastEditTime: 2023-05-16 14:09:37
* @LastEditTime: 2023-05-26 17:11:19
*/
import Vue from 'vue'
import Router from 'vue-router'
......
@import "~@/styles/mixin.scss";
.dialogBox {
border-radius: 8px;
border-radius: 4px;
overflow: hidden;
background: #FFFFFF;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.10);
......@@ -8,15 +8,33 @@
.dialog_title {
display: flex;
position: relative;
top: -2px;
top: -4px;
b {
@include flex-center;
display: flex;
justify-content:left;
align-items: center;
flex: 1;
width: 100%;
margin-left: 10px;
width: 79px;
height: 12px;
font-size: 16px;
font-family: AlibabaPuHuiTi_2_65_Medium;
color: #31333C;
line-height: 10px;
}
}
.dialog_title::before{
content: "";
display: block;
width: 4px;
height: 18px;
background: #2E74D8;
position: absolute;
top: -4px;
left: 0px;
}
.dialog_full {
position: absolute;
top: 0;
......@@ -46,10 +64,7 @@
}
.el-dialog__header {
margin-bottom: 10px;
color: #FFFFFF;
background-color: #FCFDFD;
border-bottom: 1px solid #E4EBF4;
background: linear-gradient(270deg, #F2F3FB 0%, #5C95E5 100%);
}
.el-dialog__body {
......
......@@ -179,7 +179,7 @@
// margin: 0 10px;
// border-radius: 6px;
span {
margin-left: 10px;
margin-left: 5px;
}
}
}
......
......@@ -22,7 +22,7 @@ $menuHover:#249af4;
$subMenuBg:#202B3D;
$subMenuHover:#0e6fba;
$sideBarWidth: 170px;
$sideBarWidth: 180px;
$sideBarFontSize:15px;
// border颜色
......
......@@ -110,3 +110,26 @@ export function down (index, data) {
data.splice(index, 0, downData);
}
}
export function getUrlParam (paraName) {
let url = document.location.toString();
let arrObj = url.split('?');
if (arrObj.length > 1) {
let arrPara = arrObj[1].split('&');
let arr;
for (let i = 0; i < arrPara.length; i++) {
arr = arrPara[i].split('=');
if (arr != null && arr[0] === paraName) {
const index = arr[1].indexOf("#");
return arr[1].substring(0, index);
}
}
return '';
} else {
return '';
}
}
......
/*
* @Description: 此文件主要创建 axios 实例,然后添加请求拦截器和响应拦截器
* @Autor: renchao
* @LastEditTime: 2023-05-16 14:07:58
* @LastEditTime: 2023-05-31 15:30:02
*/
import axios from 'axios'
import { Message } from 'element-ui'
......@@ -23,13 +23,14 @@ const service = axios.create({
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (process.env.NODE_ENV === "production") {
return config;
const token = localStorage.getItem('token')
// 添加请求头
if (token) {
config.headers['Authorization'] = "Bearer " + token
} else {
config.headers.Authorization = authorization;
return config;
config.headers.delete('Authorization')
}
return config;
},
error => {
// do something with request error
......@@ -55,6 +56,27 @@ service.interceptors.response.use(
},
error => {
endLoadingSubCount()
if (error.response.status === 401) {
//todo: 需要解决 一个页面多个请求,刷新后此处会触发多次
if (window.__isNeedLogin) {
window.__isNeedLogin = false
this.$message.error('token失效,请重新登录');
let locationUrl = window.location.protocol + '//' + window.location.host + window.location.pathname;
localStorage.removeItem('token')
if (window._config.casEnable) {
window.location.href = window._config.casBaseURL + '/logout?service=' + encodeURIComponent(locationUrl);
} else {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.value.fullPath
}
})
return false
}
}
} else {
// 对响应错误做点什么
Message({
message: '服务器异常,请联系管理员',
......@@ -62,6 +84,7 @@ service.interceptors.response.use(
duration: 5 * 1000,
customClass: 'messageIndex'
})
}
return Promise.reject(error);
}
)
......
<!--
* @Description:
* @Autor: renchao
* @LastEditTime: 2023-05-26 15:40:23
-->
<template>
<div class="loadingtext">
<el-input
type="textarea"
:rows="8"
v-model="formData.json">
</el-input>
<div class="text-center pt-10">
<el-button type="primary" @click="pushRecord">推送</el-button>
</div>
</div>
</template>
<script>
import { pushSjsbRecord } from "@/api/zhcx.js";
export default {
props: {
formData: {
type: Object,
default: () => {
return {}
}
}
},
methods: {
pushRecord () {
this.$startLoading()
pushSjsbRecord(this.formData).then((res) => {
this.$endLoading()
if (res.code === 200) {
this.$message.success("推送成功");
} else {
this.$message.warning(res.message);
}
this.$popupCacel()
})
}
}
}
</script>
import filter from '@/utils/filter.js'
let vm = null
const sendThis = (_this) => {
vm = _this
}
class data extends filter {
constructor() {
super()
}
columns () {
return [
{
label: '序号',
type: 'index',
width: '50',
render: (h, scope) => {
return (
<div>
{(vm.pageData.currentPage - 1) * vm.pageData.pageSize + scope.$index + 1}
</div>
)
}
},
{
prop: "ywh",
label: "业务号",
// width: '110',
},
{
label: "权属状态",
// width: '80',
render: (h, scope) => {
let obj = {
"0": {
text: '成功',
color: '#4BD863'
},
"1": {
text: '失败',
color: 'red'
}
}
let textName = obj[scope.row.status]?.text || ''
let colorName = obj[scope.row.status]?.color || ''
return (
<div>
<span style={`color:${colorName}`}>&nbsp;</span>
<span >{textName}</span>
</div>
)
}
},
{
prop: "createtime",
label: "创建时间",
// width: '180',
},
{
label: '操作',
width: '130',
render: (h, scope) => {
return (
<div>
<el-button type="text" icon="el-icon-edit-outline" onClick={() => { vm.detail(scope.row) }}>详情</el-button>
</div>
)
}
}
]
}
}
let datas = new data()
export {
datas,
sendThis
}
<template>
<div class="from-clues">
<!-- 表单部分 -->
<div class="from-clues-header">
<el-form :model="queryForm" ref="queryForm" @submit.native.prevent label-width="70px">
<el-row>
<el-col :span="5">
<el-form-item label="业务号">
<el-input placeholder="请输入业务号" v-model="queryForm.ywh" clearable class="width100">
</el-input>
</el-form-item>
</el-col>
<el-col :span="4" class="btnColRight">
<el-form-item>
<el-button type="primary" native-type="submit" @click="handleSearch">查询</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<!-- 表格 -->
<div class="from-clues-content">
<lb-table :page-size="pageData.size" class="loadingtext"
:current-page.sync="pageData.current" :total="tableData.total" @size-change="handleSizeChange"
@p-current-change="handleCurrentChange" :column="tableData.columns" :data="tableData.data">
</lb-table>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import table from "@/utils/mixin/table";
import { datas, sendThis } from "./djbcxdata";
import { getSjsbPushList, detail } from "@/api/zhcx.js";
export default {
name: "djbcx",
mixins: [table],
mounted () {
sendThis(this);
this.queryClick()
},
data () {
return {
queryForm: {
qllx: "",
bdcdyh: "",
bdcqzh: "",
ywh: "",
},
pageData: {
current: 1,
size: 10,
total: 0,
},
tableData: {
columns: datas.columns(),
data: [],
}
}
},
computed: {
...mapGetters(["dictData"])
},
methods: {
// 初始化数据
queryClick () {
this.$startLoading()
getSjsbPushList({ ...this.queryForm, ...this.pageData }).then((res) => {
this.$endLoading()
if (res.code === 200) {
let { total, records } = res.result;
this.tableData.data = records;
this.tableData.total = total;
}
});
},
openDialog (scroll) {
const h = this.$createElement;
this.$msgbox({
title: '推送',
message: h('p', null, [
h('span', null, '是否推送 '),
h('i', { style: 'color: teal' }, scroll.ywh),
h('span', null, ' 记录')
]),
showCancelButton: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
this.pushRecord(scroll, done);
} else {
done();
}
}
}).then(action => { });
},
detail (row) {
detail(row.bsm).then((res) => {
this.$endLoading()
if (res.code === 200) {
this.$popupDialog("详情", "sjgx/sbxtsjts/dialog/detail", res.result, "60%")
} else {
this.$message.warning(res.message);
}
})
}
}
}
</script>
<style scoped lang="scss">
@import "~@/styles/public.scss";
.icon-circle {
position: relative;
}
.icon-circle::before {
content: "";
width: 4px;
height: 4px;
border-radius: 50%;
background: #000;
top: 0px;
left: 0px;
}
</style>
......@@ -46,8 +46,8 @@ class data extends filter {
label: '登记情形编码',
render: (h, scope) => {
return (
<el-input placeholder="登记情形编码" class={{ repeat: scope.row.repeat }} value={scope.row[scope.column.property]}
onInput={(val) => { scope.row[scope.column.property] = val; orderNoChange() }} maxlength="8">
<el-input placeholder="登记情形编码" value={scope.row[scope.column.property]}
onInput={(val) => { scope.row[scope.column.property] = val; vm.orderNoChange() }} maxlength="8">
</el-input>
)
}
......@@ -58,7 +58,7 @@ class data extends filter {
render: (h, scope) => {
return (
<el-input placeholder="登记情形名称" value={scope.row[scope.column.property]}
onInput={(val) => { scope.row[scope.column.property] = val; orderNoChange() }}>
onInput={(val) => { scope.row[scope.column.property] = val; vm.orderNoChange() }}>
</el-input>
)
}
......
......@@ -153,7 +153,9 @@
activeName: "1",
form: {
bsmSqyw: '',
ywDetail: {}
ywDetail: {},
djqx: [],
clxx: []
},
djqxCol: datas.djqxCol(),
clxxCol: datas.clxxCol(),
......@@ -205,15 +207,16 @@
},
//获取业务具体明细内容
getDetail (bsmSqyw) {
let _this = this
getSqdjywDetail(bsmSqyw).then((res) => {
if (res.code === 200) {
// this.form.bsmSqyw = res.result.bsmSqyw
// this.form.ywDetail = res.result.ywDetail
this.$set(this.form, 'djqx', res.result.djqx)
// this.$set(this.form, 'sxql', res.result.sxql)
this.$set(this.form, 'clxx', res.result.clxx)
// this.$set(this.form, 'sxzt', res.result.sxzt)
// this.$set(this.form, 'ywDetail', res.result.ywDetail)
_this.form.bsmSqyw = res.result.bsmSqyw
_this.form.ywDetail = res.result.ywDetail
_this.$set(_this.form, 'djqx', res.result.djqx)
_this.$set(_this.form, 'sxql', res.result.sxql)
_this.$set(_this.form, 'clxx', res.result.clxx)
_this.$set(_this.form, 'sxzt', res.result.sxzt)
_this.$set(_this.form, 'ywDetail', res.result.ywDetail)
} else {
this.$alert(res.message)
}
......@@ -289,7 +292,6 @@
.el-radio-group {
white-space: nowrap;
}
.form {
background: #eee;
padding: 0 10px;
......
No preview for this file type
<!--
* @Description:
* @Autor: renchao
* @LastEditTime: 2023-05-17 10:40:23
* @LastEditTime: 2023-05-31 16:04:44
-->
<template>
<div class="from-clues loadingtext" v-Loading="loading" element-loading-text="拼命加载中..." style="height:720px">
......@@ -11,7 +11,8 @@
v-for="(item, index) in headTabBdcqz" :key="index"></el-tab-pane>
</el-tabs>
<div class="no-data" v-if="headTabBdcqz.length == 0">暂无数据</div>
<img :src="previewImage" class="imgClass">
<!-- <img :src="previewImage" class="imgClass"> -->
<canvas ref="canvas" :width="canvasWidth" :height="canvasHeight"></canvas>
</div>
</template>
......@@ -20,8 +21,6 @@
import { getSlsqBdcqzList, bdcqzPreview } from "@/api/bdcqz.js"
export default {
name: "zsyl",
components: {
},
props: {
formData: {
type: Object,
......@@ -30,6 +29,10 @@
},
data () {
return {
imgSrc: require('@/image/bdcqz/bdcqzs2.jpg'),
canvasWidth: 1018,
canvasHeight: 720,
loading: false,
//印刷序列号集合
ysxlh: [],
......@@ -79,28 +82,54 @@
this.activeName = res.result[0].bsmBdcqz
this.bdcqz = res.result[0]
this.headTabBdcqz = res.result
this.getBdcqzPreview();
} else {
this.loading = false
this.drawTextOnImage();
}
}
this.loading = false
})
},
//tab表头切换方法
handleClick (e) {
this.bdcqz = this.headTabBdcqz[e.index - 0]
this.activeName = this.headTabBdcqz.bsmBdcqz
this.getBdcqzPreview();
},
getBdcqzPreview () {
bdcqzPreview(this.bdcqz).then(res => {
this.loading = false
let blob = new Blob([res]);
let url = window.URL.createObjectURL(blob);
this.previewImage = url;
})
// this.getBdcqzPreview();
this.drawTextOnImage()
},
// getBdcqzPreview () {
// bdcqzPreview(this.bdcqz).then(res => {
// this.loading = false
// let blob = new Blob([res]);
// let url = window.URL.createObjectURL(blob);
// this.previewImage = url;
// this.drawTextOnImage()
// })
// },
drawTextOnImage () {
const canvas = this.$refs.canvas;
const context = canvas.getContext('2d');
const image = new Image();
image.onload = () => {
context.drawImage(image, 0, 0);
context.font = '15px 楷体';
context.fillStyle = '#000000';
context.fillText(this.bdcqz.sjjc ? this.bdcqz.sjjc : '', 60, 56);
context.fillText(this.bdcqz.djnd ? this.bdcqz.djnd : '', 113, 56);
context.fillText(this.bdcqz.sxqc ? this.bdcqz.sxqc : '', 180, 56);
context.fillText(this.bdcqz.sxh ? this.bdcqz.sxh : '', 370, 56);
context.fillText(this.bdcqz.qlr ? this.bdcqz.qlr : '', 138, 97);
context.fillText(this.bdcqz.gyqk ? this.bdcqz.gyqk : '', 138, 138);
context.fillText(this.bdcqz.zl ? this.bdcqz.zl : '', 138, 180);
context.fillText(this.bdcqz.bdcdyh ? this.bdcqz.bdcdyh : '', 138, 223);
context.fillText(this.bdcqz.qllx ? this.bdcqz.qllx : '', 138, 263);
context.fillText(this.bdcqz.qlxz ? this.bdcqz.qlxz : '', 138, 303);
context.fillText(this.bdcqz.yt ? this.bdcqz.yt : '', 138, 346);
context.fillText(this.bdcqz.mj ? this.bdcqz.mj : '', 138, 386);
context.fillText(this.bdcqz.syqx ? this.bdcqz.syqx : '', 138, 429);
context.fillText(this.bdcqz.qlqtzk ? this.bdcqz.qlqtzk : '', 138, 469);
context.fillText(this.bdcqz.fj ? this.bdcqz.fj : '', 580, 100);
}
image.src = this.imgSrc;
}
}
}
</script>
......
<!--
功能:初始化功能描述
作者:calliope
-->
<template>
<canvas id="mxcad">
</canvas>
</template>
<script>
import Mx from "mxdraw"
export default {
mounted () {
// 动态加载 js库核心代码
Mx.loadCoreCode().then(() => {
// Mx.MxFun.setMxServer("ws://localhost:5090") // 开启socket通信 可编辑图纸
// 创建控件对象
Mx.MxFun.createMxObject({
canvasId: "mxcad", // canvas元素的id
cadFile: "./cad.dwg", // http方式(预览): 加载public/demo文件夹下转换后的图纸
// cadFile: "test2.dwg", // socket通信方式请直接提供图纸名称 如:text.dwg
callback: (mxDraw, {
canvas,
canvasParent
}) => {
// 可以拿到canvas元素和它的父级元素
console.log(canvas, canvasParent)
console.log(mxDraw)
// 拿到图层数据
mxDraw.addEvent('uiSetLayerData', (listLayer) => {
console.log(listLayer)
})
},
isNewFile: true // 是否新建文件
})
})
}
}
</script>
<style scoped lang='scss'>
#cad-container {
width: 100%;
height: 100%;
}
</style>
\ No newline at end of file
/*
* @Description:
* @Autor: renchao
* @LastEditTime: 2023-05-17 10:38:37
* @LastEditTime: 2023-05-29 14:39:11
*/
//流程环节操作按钮
export function getForm (tabName, djywbm) {
......@@ -86,6 +86,9 @@ export function getForm (tabName, djywbm) {
case "fzxx":
form = require("@/views/workflow/components/fzxx.vue");
break;
case "zdt":
form = require("@/views/workflow/components/zdt.vue");
break;
default:
form = require("@/views/error-page/404.vue");
break;
......
......@@ -6,7 +6,6 @@ function resolve (dir) {
return path.join(__dirname, dir)
}
const name = defaultSettings.title
const port = process.env.port || process.env.npm_config_port || 8888 // dev port
// All configuration item explanations can be find in https://cli.vuejs.org/config/
......@@ -19,7 +18,7 @@ module.exports = {
* Detail: https://cli.vuejs.org/config/#publicpath
*/
// 加载资源的路径
publicPath: '/bdcdj/',
publicPath: '/dj/',
// 设置项目打包生成的文件的存储目录,可以是静态路径也可以是相对路径
outputDir: 'dist',
// 设置放置打包生成的静态资源 (js、css、img、fonts) 的目录
......@@ -60,7 +59,6 @@ module.exports = {
},
// configureWebpack通过操作对象的形式,来修改默认的webpack配置
configureWebpack: {
name: name,
entry: {
app: './src/main.js'
},
......@@ -69,7 +67,7 @@ module.exports = {
'@': resolve('src')
}
},
devtool: '#eval-source-map' //测试
devtool: process.env.NODE_ENV === 'development' ? '#eval-source-map' : false
},
// chainWebpack通过链式编程的形式,来修改默认的webpack配置
chainWebpack (config) {
......@@ -83,8 +81,8 @@ module.exports = {
include: 'initial'
}
])
config.plugins.delete('prefetch')
// when there are many pages, it will cause too many meaningscss requests
config.plugins.delete('prefetch')
config.module
.rule('svg')
.exclude.add(resolve('src/image/icons'))
......