Commit 87594b7c authored by 陈浩玮's avatar 陈浩玮

Merge branch 'feature/shuiluo' into 'master'

增加编辑器配置文件

See merge request product/kim3-web-vue/starter-web-vue!1
parents 2f2d90a6 ea21c88e
VUE_APP_API_BASE_URL=http://dev.iczer.com VUE_APP_API_BASE_URL=http://platform.kuopu.net:9300
...@@ -27,3 +27,6 @@ $ npm run serve ...@@ -27,3 +27,6 @@ $ npm run serve
### 说明 ### 说明
用户名/密码:admin/666666 用户名/密码:admin/666666
css 工具函数 地址
https://www.tailwindcss.cn/docs/installation#post-css-7
import Vue from 'vue';
declare module 'vue/types/vue' {
interface Vue {
$t: (s: string) => any;
}
}
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
...@@ -20,27 +20,26 @@ export default { ...@@ -20,27 +20,26 @@ export default {
created() { created() {
this.setHtmlTitle(); this.setHtmlTitle();
this.setLanguage(this.lang); this.setLanguage(this.lang);
enquireScreen((isMobile) => this.setDevice(isMobile)); enquireScreen(isMobile => this.setDevice(isMobile));
}, },
mounted() {}, mounted() {},
watch: { watch: {
lang(val) { lang(val) {
this.setLanguage(val); this.setLanguage(val);
this.setHtmlTitle(); this.setHtmlTitle();
}, },
$route() { $route() {
this.setHtmlTitle(); this.setHtmlTitle();
}, },
'theme.mode': function (val) { 'theme.mode': function(val) {
let closeMessage = this.$message.loading(`您选择了主题模式 ${val}, 正在切换...`); let closeMessage = this.$message.loading(`您选择了主题模式 ${val}, 正在切换...`);
changeThemeColor(this.theme.color, val).then(closeMessage); changeThemeColor(this.theme.color, val).then(closeMessage);
}, },
'theme.color': function (val) { 'theme.color': function(val) {
let closeMessage = this.$message.loading(`您选择了主题色 ${val}, 正在切换...`); let closeMessage = this.$message.loading(`您选择了主题色 ${val}, 正在切换...`);
changeThemeColor(val, this.theme.mode).then(closeMessage); changeThemeColor(val, this.theme.mode).then(closeMessage);
}, },
layout: function () { layout: function() {
window.dispatchEvent(new Event('resize')); window.dispatchEvent(new Event('resize'));
}, },
}, },
...@@ -77,6 +76,3 @@ export default { ...@@ -77,6 +76,3 @@ export default {
}, },
}; };
</script> </script>
<style lang="less" scoped>
</style>
import { request, METHOD } from '@/utils';
export function getUserDetailInfoApi() {
return request('/api/v1/detail', METHOD.GET);
}
import Table from './table/index.vue';
export { Table };
<template> <template>
<div :class="['page-header', layout, pageWidth]"> <div :class="['page-header', layout, pageWidth]">
<div class="page-header-wide"> <a-breadcrumb>
<div class="breadcrumb"> <a-breadcrumb-item :key="index" v-for="(item, index) in breadcrumb">
<a-breadcrumb> <span>{{ item }}</span>
<a-breadcrumb-item :key="index" v-for="(item, index) in breadcrumb"> </a-breadcrumb-item>
<span>{{item}}</span> </a-breadcrumb>
</a-breadcrumb-item>
</a-breadcrumb>
</div>
<div class="detail">
<div class="main">
<div class="row">
<h1 v-if="showPageTitle && title" class="title">{{title}}</h1>
<div class="action">
<slot name="action"></slot>
</div>
</div>
<div class="row">
<div v-if="this.$slots.content" class="content">
<div v-if="avatar" class="avatar">
<a-avatar :src="avatar" :size="72" />
</div>
<slot name="content"></slot>
</div>
<div v-if="this.$slots.extra" class="extra">
<slot name="extra"></slot>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</template> </template>
...@@ -63,43 +38,11 @@ export default { ...@@ -63,43 +38,11 @@ export default {
<style lang="less" scoped> <style lang="less" scoped>
.page-header { .page-header {
background: @base-bg-color;
padding: 16px 24px; padding: 16px 24px;
&.head.fixed { &.head.fixed {
margin: auto; margin: auto;
max-width: 1400px; max-width: 1400px;
} }
.page-header-wide {
.breadcrumb {
margin-bottom: 20px;
}
.detail {
display: flex;
.row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.avatar {
margin: 0 24px 0 0;
}
.main {
width: 100%;
.title {
font-size: 20px;
color: @title-color;
margin-bottom: 16px;
}
.content {
display: flex;
flex-wrap: wrap;
color: @text-color-second;
}
.extra {
display: flex;
}
}
}
}
} }
</style> </style>
<template>
<div>
<div :class="$style.card" v-if="$scopedSlots.search">
<a-form-model layout="inline" :model="queryForm">
<slot name="search" :query="queryForm" />
</a-form-model>
<div class="tw-text-right tw-mt-2">
<a-space>
<a-button @click="queryForm = {}">重置</a-button>
<a-button type="primary" @click="getData">查询</a-button>
</a-space>
</div>
</div>
<div class="tw-mt-3" :class="$style.card">
<a-space class="tw-mb-2.5">
<a-button v-if="addBtn" type="primary" key="add" v-bind="addBtn.options" @click="add">
{{ addBtn.text || '新建' }}
</a-button>
<template v-for="btn of buttons">
<a-button :type="btn.primary" :key="btn.text">{{ btn.text }}</a-button>
</template>
</a-space>
<a-table :data-source="data" :loading="loading" v-bind="$attrs">
<slot />
</a-table>
</div>
<!-- 新增 -->
<a-drawer
:title="addBtn.title || '新建'"
placement="right"
:visible="addVisible"
@close="addDrawerClose"
v-if="addBtn"
:maskClosable="addBtn.maskClosable"
>
<slot name="add" />
</a-drawer>
</div>
</template>
<script>
import { request, METHOD } from '@/utils/requestUtil';
const data = [
{
key: 1,
name: 'John Brown sr.',
age: 60,
address: 'New York No. 1 Lake Park',
children: [
{
key: 11,
name: 'John Brown',
age: 42,
address: 'New York No. 2 Lake Park',
},
{
key: 12,
name: 'John Brown jr.',
age: 30,
address: 'New York No. 3 Lake Park',
children: [
{
key: 121,
name: 'Jimmy Brown',
age: 16,
address: 'New York No. 3 Lake Park',
},
],
},
{
key: 13,
name: 'Jim Green sr.',
age: 72,
address: 'London No. 1 Lake Park',
children: [
{
key: 131,
name: 'Jim Green',
age: 42,
address: 'London No. 2 Lake Park',
children: [
{
key: 1311,
name: 'Jim Green jr.',
age: 25,
address: 'London No. 3 Lake Park',
},
{
key: 1312,
name: 'Jimmy Green sr.',
age: 18,
address: 'London No. 4 Lake Park',
},
],
},
],
},
],
},
{
key: 2,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
];
export default {
props: {
url: String,
buttons: {
type: Array,
default: () => [],
},
addBtn: {
type: Object,
},
formatData: Function,
},
data() {
return {
data: [],
queryForm: {},
loading: false,
addVisible: false,
};
},
mounted() {
console.log(this.addBtn);
this.getData();
},
methods: {
async getData() {
this.loading = true;
try {
const res = await request(this.url, METHOD.GET);
if (this.formatData) this.data = this.formatData(res);
else this.data = res;
} catch (error) {
// todo
}
this.loading = false;
},
add() {
this.addVisible = true;
},
addDrawerClose() {
this.addVisible = false;
},
},
};
</script>
<style module>
.card {
@apply tw-bg-white tw-rounded-md tw-p-4;
}
</style>
...@@ -5,29 +5,29 @@ import Viser from 'viser-vue'; ...@@ -5,29 +5,29 @@ import Viser from 'viser-vue';
import Router from 'vue-router'; import Router from 'vue-router';
import { initRouter } from './router'; import { initRouter } from './router';
import VueI18n from 'vue-i18n'; import VueI18n from 'vue-i18n';
import {accountModule, settingModule} from './pages/frame/store'; import { accountModule, settingModule } from './pages/frame/store';
import App from './App.vue'; import App from './App.vue';
import 'tailwindcss/tailwind.css';
import 'animate.css/source/animate.css'; import 'animate.css/source/animate.css';
import './theme/index.less'; import './theme/index.less';
import Plugins from './plugins'; import Plugins from './plugins';
import { loadRoutes, loadGuards, setAppOptions } from './utils/routerUtil'; import { loadRoutes, loadGuards, setAppOptions } from './utils/routerUtil';
import { loadInterceptors } from './utils/requestUtil';
import guards from './router/guards'; import guards from './router/guards';
import interceptors from './router/interceptors'; import { loadResponseInterceptor } from './utils/requestUtil';
import '@/mock'; // import '@/mock';
import 'moment/locale/zh-cn'; import 'moment/locale/zh-cn';
//设置为非生产提示 //设置为非生产提示
Vue.config.productionTip = false; Vue.config.productionTip = false;
//装载Vuex控件 //装载Vuex控件
Vue.use(Vuex) Vue.use(Vuex);
//加载 框架的module包括 命名空间accountModule,settingModule //加载 框架的module包括 命名空间accountModule,settingModule
const store = new Vuex.Store({ const store = new Vuex.Store({
modules: {accountModule, settingModule} modules: { accountModule, settingModule },
}); });
//装载vue-router控件 如果开发时 不用动态可直接修改这里 isAsynRount=false //装载vue-router控件 如果开发时 不用动态可直接修改这里 isAsynRount=false
...@@ -43,11 +43,10 @@ const localeLang = store.state.settingModule.lang; ...@@ -43,11 +43,10 @@ const localeLang = store.state.settingModule.lang;
//default'EN' //default'EN'
const fallbackLang = store.state.settingModule.fallbackLang; const fallbackLang = store.state.settingModule.fallbackLang;
const i18n = new VueI18n({ const i18n = new VueI18n({
locale: localeLang, locale: localeLang,
fallbackLocale: fallbackLang, fallbackLocale: fallbackLang,
silentFallbackWarn: true, silentFallbackWarn: true,
}); });
//装载antd控件 //装载antd控件
Vue.use(Antd); Vue.use(Antd);
...@@ -59,17 +58,26 @@ Vue.use(Plugins); ...@@ -59,17 +58,26 @@ Vue.use(Plugins);
//启动引导方法应用启动时需要执行的操作放在这里 //启动引导方法应用启动时需要执行的操作放在这里
//设置应用配置 //设置应用配置
setAppOptions({ router, store, i18n }); setAppOptions({ router, store, i18n });
// 加载 axios 拦截器
loadInterceptors(interceptors, { router, store, i18n, message: Vue.prototype.$message });
// 加载路由 // 加载路由
loadRoutes(); loadRoutes();
// 加载路由守卫 // 加载路由守卫
loadGuards(guards, { router, store, i18n, message: Vue.prototype.$message }); loadGuards(guards, { router, store, i18n, message: Vue.prototype.$message });
// this.$notification.open({
// message: 'Notification Title',
// description:
// 'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
// onClick: () => {
// console.log('Notification Clicked!');
// },
// });
loadResponseInterceptor({ router });
new Vue({ new Vue({
router, router,
store, store,
i18n, i18n,
render: h => h(App), render: h => h(App),
}).$mount('#app'); }).$mount('#app');
import Mock from 'mockjs';
Mock.mock(`${process.env.VUE_APP_API_BASE_URL}/table`, 'post', () => ({
data: {
pageNum: 1,
pageSize: 10,
records: [
{
regionId: 1403455275008032,
regionName: '陈浩玮',
organizationId: 146,
organizationName: '一级系统:巴基斯坦公司',
remark: '123',
editorId: 1,
editorName: '管理员',
editTime: '2021-03-16 22:20:25',
},
{
regionId: 1403455136596000,
regionName: '地点测试',
organizationId: 212,
organizationName: '吴国',
remark: '123',
editorId: 1,
editorName: '管理员',
editTime: '2021-03-16 22:19:19',
},
{
regionId: 1400871246102560,
regionName: '陈浩玮测试',
organizationId: 212,
organizationName: '吴国',
remark: '321',
editorId: 1,
editorName: '管理员',
editTime: '2021-03-02 16:04:25',
},
{
regionId: 1396338094768160,
regionName: '文件地区',
organizationId: 208,
organizationName: '扩普发展',
remark: '123213123123213213223123123',
editorId: 1,
editorName: '管理员',
editTime: '2021-03-02 15:37:11',
},
],
total: 4,
size: 10,
current: 1,
pages: 1,
},
code: 'sys.success',
message: null,
traceId: '1805b654e82a4f2a8357d2f8922d9b19',
meta: null,
}));
<template> <template>
<a-dropdown class="lang header-item"> <a-dropdown class="lang header-item">
<div> <div><a-icon type="global" /> {{ langAlias }}</div>
<a-icon type="global" /> {{langAlias}}
</div>
<a-menu @click="val => setLang(val.key)" :selected-keys="[lang]" slot="overlay"> <a-menu @click="val => setLang(val.key)" :selected-keys="[lang]" slot="overlay">
<a-menu-item v-for=" lang in langList" :key="lang.key">{{lang.key.toLowerCase() + ' ' + lang.name}}</a-menu-item> <a-menu-item v-for="lang in langList" :key="lang.key">
{{ lang.key.toLowerCase() + ' ' + lang.name }}
</a-menu-item>
</a-menu> </a-menu>
</a-dropdown> </a-dropdown>
</template> </template>
<script> <script>
import { mapState, mapMutations } from 'vuex'; import { mapState, mapMutations } from 'vuex';
import {globalConfig} from '@/config'; import { globalConfig } from '@/config';
export default { export default {
name: "LayoutTopHeaderLang", name: 'LayoutTopHeaderLang',
data() { data() {
return { return {
langList: globalConfig.langs, langList: globalConfig.langs,
}; };
}, },
computed: { computed: {
...mapState("settingModule", ["lang"]), ...mapState('settingModule', ['lang']),
langAlias() { langAlias() {
let lang = this.langList.find((item) => item.key == this.lang); let lang = this.langList.find(item => item.key == this.lang);
return lang.alias; return lang.alias;
}, },
}, },
methods: { methods: {
...mapMutations("settingModule", ["setLang"]), ...mapMutations('settingModule', ['setLang']),
}, },
}; };
</script> </script>
<style lang="less">
</style>
<template> <template>
<a-layout :class="['admin-layout', 'beauty-scroll']"> <a-layout :class="['admin-layout', 'beauty-scroll']">
<drawer v-if="isMobile" v-model="drawerOpen"> <drawer v-if="isMobile" v-model="drawerOpen">
<side-menu :theme="theme.mode" :menuData="menuData" :collapsed="false" :collapsible="false" @menuSelect="onMenuSelect" /> <side-menu
:theme="theme.mode"
:menuData="menuData"
:collapsed="false"
:collapsible="false"
@menuSelect="onMenuSelect"
/>
</drawer> </drawer>
<side-menu :class="[fixedSideBar ? 'fixed-side' : '']" :theme="theme.mode" v-else-if="layout === 'side' || layout === 'mix'" :menuData="sideMenuData" :collapsed="collapsed" :collapsible="true" /> <side-menu
:class="[fixedSideBar ? 'fixed-side' : '']"
:theme="theme.mode"
v-else-if="layout === 'side' || layout === 'mix'"
:menuData="sideMenuData"
:collapsed="collapsed"
:collapsible="true"
/>
<div v-if="fixedSideBar && !isMobile" :style="`width: ${sideMenuWidth}; min-width: ${sideMenuWidth};max-width: ${sideMenuWidth};`" class="virtual-side"> <div
</div> v-if="fixedSideBar && !isMobile"
:style="`width: ${sideMenuWidth}; min-width: ${sideMenuWidth};max-width: ${sideMenuWidth};`"
class="virtual-side"
></div>
<drawer v-if="!hideSettingExtend" v-model="showSetting" placement="right"> <drawer v-if="!hideSettingExtend" v-model="showSetting" placement="right">
<div class="setting" slot="handler"> <div class="setting" slot="handler">
...@@ -17,8 +33,20 @@ ...@@ -17,8 +33,20 @@
</drawer> </drawer>
<a-layout class="admin-layout-main beauty-scroll"> <a-layout class="admin-layout-main beauty-scroll">
<layout-top-header :class="[{'fixed-tabs': fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage}]" :style="headerStyle" :menuData="headMenuData" :collapsed="collapsed" @toggleCollapse="toggleCollapse" /> <layout-top-header
<a-layout-header :class="['virtual-header', {'fixed-tabs' : fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage}]" v-show="fixedHeader"></a-layout-header> :class="[{ 'fixed-tabs': fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage }]"
:style="headerStyle"
:menuData="headMenuData"
:collapsed="collapsed"
@toggleCollapse="toggleCollapse"
/>
<a-layout-header
:class="[
'virtual-header',
{ 'fixed-tabs': fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage },
]"
v-show="fixedHeader"
></a-layout-header>
<a-layout-content class="admin-layout-content" :style="`min-height: ${minHeight}px;`"> <a-layout-content class="admin-layout-content" :style="`min-height: ${minHeight}px;`">
<div style="position: relative"> <div style="position: relative">
<slot></slot> <slot></slot>
...@@ -126,7 +154,7 @@ export default { ...@@ -126,7 +154,7 @@ export default {
matched = matched.slice(0, matched.length - 1); matched = matched.slice(0, matched.length - 1);
const { firstMenu } = this; const { firstMenu } = this;
for (let menu of firstMenu) { for (let menu of firstMenu) {
if (matched.findIndex((item) => item.path === menu.fullPath) !== -1) { if (matched.findIndex(item => item.path === menu.fullPath) !== -1) {
this.setActivatedFirst(menu.fullPath); this.setActivatedFirst(menu.fullPath);
break; break;
} }
......
<template> <template>
<div class="page-layout"> <div class="page-layout">
<page-header ref="pageHeader" :style="`margin-top: ${multiPage ? 0 : -24}px`" :breadcrumb="breadcrumb" :title="pageTitle" :logo="logo" :avatar="avatar"> <page-header
ref="pageHeader"
:style="`margin-top: ${multiPage ? 0 : -24}px`"
:breadcrumb="breadcrumb"
:title="pageTitle"
:logo="logo"
:avatar="avatar"
>
<slot name="action" slot="action"></slot> <slot name="action" slot="action"></slot>
<slot slot="content" name="headerContent"></slot> <slot slot="content" name="headerContent"></slot>
<div slot="content" v-if="!this.$slots.headerContent && desc"> <div slot="content" v-if="!this.$slots.headerContent && desc">
<p>{{ desc }}</p> <p>{{ desc }}</p>
<div v-if="this.linkList" class="link"> <div v-if="this.linkList" class="link">
<template v-for="(link, index) in linkList"> <template v-for="(link, index) in linkList">
<a :key="index" :href="link.href"> <a :key="index" :href="link.href"> <a-icon :type="link.icon" />{{ link.title }} </a>
<a-icon :type="link.icon" />{{ link.title }}
</a>
</template> </template>
</div> </div>
</div> </div>
...@@ -76,7 +81,7 @@ export default { ...@@ -76,7 +81,7 @@ export default {
let breadcrumb = page && page.breadcrumb; let breadcrumb = page && page.breadcrumb;
if (breadcrumb) { if (breadcrumb) {
let i18nBreadcrumb = []; let i18nBreadcrumb = [];
breadcrumb.forEach((item) => { breadcrumb.forEach(item => {
i18nBreadcrumb.push(this.$t(item)); i18nBreadcrumb.push(this.$t(item));
}); });
return i18nBreadcrumb; return i18nBreadcrumb;
...@@ -95,8 +100,8 @@ export default { ...@@ -95,8 +100,8 @@ export default {
const path = this.$route.path; const path = this.$route.path;
let breadcrumb = []; let breadcrumb = [];
routes routes
.filter((item) => path.includes(item.path)) .filter(item => path.includes(item.path))
.forEach((route) => { .forEach(route => {
const path = route.path.length === 0 ? '/home' : route.path; const path = route.path.length === 0 ? '/home' : route.path;
breadcrumb.push(this.$t(getI18nKey(path))); breadcrumb.push(this.$t(getI18nKey(path)));
}); });
...@@ -119,11 +124,7 @@ export default { ...@@ -119,11 +124,7 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
.page-header {
margin: 0 -24px 0;
}
.link { .link {
/*margin-top: 16px;*/
line-height: 24px; line-height: 24px;
a { a {
font-size: 14px; font-size: 14px;
...@@ -136,9 +137,7 @@ export default { ...@@ -136,9 +137,7 @@ export default {
} }
.page-content { .page-content {
position: relative; position: relative;
padding: 24px 0 0;
&.side {
}
&.head.fixed { &.head.fixed {
margin: 0 auto; margin: 0 auto;
max-width: 1400px; max-width: 1400px;
......
import { request, METHOD, removeAuthorization } from '@/utils/requestUtil'; import { request, METHOD } from '@/utils/requestUtil';
import BASE_URL from '@/utils/baseUrlUtil'; import md5 from 'crypto-js/md5';
/** /**
* 登录服务,登录成功后 根据用户ID 获取用户 角色 + 菜单 + 功能权限 + 用户基本信息 * 登录服务,登录成功后 根据用户ID 获取用户 角色 + 菜单 + 功能权限 + 用户基本信息
...@@ -7,27 +7,25 @@ import BASE_URL from '@/utils/baseUrlUtil'; ...@@ -7,27 +7,25 @@ import BASE_URL from '@/utils/baseUrlUtil';
* @param password 账户密码 * @param password 账户密码
* @returns {Promise<AxiosResponse<T>>} * @returns {Promise<AxiosResponse<T>>}
*/ */
export async function login(name, password) { export async function login(userName, password) {
let loginApi = `${BASE_URL}/login`; return request('/api/v1/login', METHOD.POST, {
return request(loginApi, METHOD.POST, { userName,
name: name, password: md5(password).toString(),
password: password });
});
} }
/** /**
* 退出登录 * 退出登录
*/ */
export function logout() { export function logout() {
//清除所有保存的信息 //清除所有保存的信息
localStorage.removeItem(process.env.VUE_APP_ROUTES_KEY); localStorage.removeItem(process.env.VUE_APP_ROUTES_KEY);
localStorage.removeItem(process.env.VUE_APP_PERMISSIONS_KEY); localStorage.removeItem(process.env.VUE_APP_PERMISSIONS_KEY);
localStorage.removeItem(process.env.VUE_APP_ROLES_KEY); localStorage.removeItem(process.env.VUE_APP_ROLES_KEY);
localStorage.removeItem(process.env.VUE_APP_USER_KEY); localStorage.removeItem(process.env.VUE_APP_USER_KEY);
removeAuthorization();
} }
export default { export default {
login, login,
logout, logout,
} };
import { globalConfig, settingConfig } from "@/config"; import { globalConfig, settingConfig } from '@/config';
import { formatFullPath } from "@/utils/i18nUtil"; import { formatFullPath } from '@/utils/i18nUtil';
import { filterMenu } from "@/utils/authorityUtil"; import { filterMenu } from '@/utils/authorityUtil';
import { getLocalSetting } from "@/utils/themeUtil"; import { getLocalSetting } from '@/utils/themeUtil';
import deepClone from "lodash.clonedeep"; import { langUtil } from '@/utils';
import deepClone from 'lodash.clonedeep';
const localSetting = getLocalSetting(true); const localSetting = getLocalSetting(true);
const customTitlesStr = sessionStorage.getItem(process.env.VUE_APP_TBAS_TITLES_KEY); const customTitlesStr = sessionStorage.getItem(process.env.VUE_APP_TBAS_TITLES_KEY);
...@@ -17,6 +18,7 @@ export default { ...@@ -17,6 +18,7 @@ export default {
pageMinHeight: 0, pageMinHeight: 0,
menuData: [], menuData: [],
activatedFirst: undefined, activatedFirst: undefined,
lang: '',
customTitles, customTitles,
...settingConfig, ...settingConfig,
...localSetting, ...localSetting,
...@@ -34,7 +36,7 @@ export default { ...@@ -34,7 +36,7 @@ export default {
if (menuData.length > 0 && !menuData[0].fullPath) { if (menuData.length > 0 && !menuData[0].fullPath) {
formatFullPath(menuData); formatFullPath(menuData);
} }
return menuData.map((item) => { return menuData.map(item => {
const menuItem = { ...item }; const menuItem = { ...item };
delete menuItem.children; delete menuItem.children;
return menuItem; return menuItem;
...@@ -45,7 +47,7 @@ export default { ...@@ -45,7 +47,7 @@ export default {
if (menuData.length > 0 && !menuData[0].fullPath) { if (menuData.length > 0 && !menuData[0].fullPath) {
formatFullPath(menuData); formatFullPath(menuData);
} }
const current = menuData.find((menu) => menu.fullPath === activatedFirst); const current = menuData.find(menu => menu.fullPath === activatedFirst);
return (current && current.children) || []; return (current && current.children) || [];
}, },
}, },
...@@ -73,6 +75,7 @@ export default { ...@@ -73,6 +75,7 @@ export default {
}, },
setLang(state, lang) { setLang(state, lang) {
state.lang = lang; state.lang = lang;
langUtil.set(lang);
}, },
setHideSetting(state, hideSetting) { setHideSetting(state, hideSetting) {
state.hideSetting = hideSetting; state.hideSetting = hideSetting;
...@@ -97,13 +100,16 @@ export default { ...@@ -97,13 +100,16 @@ export default {
}, },
setCustomTitle(state, { path, title }) { setCustomTitle(state, { path, title }) {
if (title) { if (title) {
const obj = state.customTitles.find((item) => item.path === path); const obj = state.customTitles.find(item => item.path === path);
if (obj) { if (obj) {
obj.title = title; obj.title = title;
} else { } else {
state.customTitles.push({ path, title }); state.customTitles.push({ path, title });
} }
sessionStorage.setItem(process.env.VUE_APP_TBAS_TITLES_KEY, JSON.stringify(state.customTitles)); sessionStorage.setItem(
process.env.VUE_APP_TBAS_TITLES_KEY,
JSON.stringify(state.customTitles),
);
} }
}, },
}, },
......
...@@ -12,8 +12,12 @@ ...@@ -12,8 +12,12 @@
<a-tabs size="large" :tabBarStyle="{ textAlign: 'center' }"> <a-tabs size="large" :tabBarStyle="{ textAlign: 'center' }">
<a-tab-pane :tab="$t('tabTitle')" key="1"> <a-tab-pane :tab="$t('tabTitle')" key="1">
<a-form-item> <a-form-item>
<a-input autocomplete="autocomplete" size="large" :placeholder="$t('idPlaceHolder')" v-decorator="[ <a-input
'name', autocomplete="autocomplete"
size="large"
:placeholder="$t('idPlaceHolder')"
v-decorator="[
'userName',
{ {
rules: [ rules: [
{ {
...@@ -23,12 +27,18 @@ ...@@ -23,12 +27,18 @@
}, },
], ],
}, },
]"> ]"
>
<a-icon slot="prefix" type="user" /> <a-icon slot="prefix" type="user" />
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-input size="large" :placeholder="$t('pwdPlaceHolder')" autocomplete="autocomplete" type="password" v-decorator="[ <a-input
size="large"
:placeholder="$t('pwdPlaceHolder')"
autocomplete="autocomplete"
type="password"
v-decorator="[
'password', 'password',
{ {
rules: [ rules: [
...@@ -39,7 +49,8 @@ ...@@ -39,7 +49,8 @@
}, },
], ],
}, },
]"> ]"
>
<a-icon slot="prefix" type="lock" /> <a-icon slot="prefix" type="lock" />
</a-input> </a-input>
</a-form-item> </a-form-item>
...@@ -50,7 +61,14 @@ ...@@ -50,7 +61,14 @@
<a style="float: right">{{ $t('forgetPwdLink') }}</a> <a style="float: right">{{ $t('forgetPwdLink') }}</a>
</div> </div>
<a-form-item> <a-form-item>
<a-button :loading="logging" class="logging-button" size="large" htmlType="submit" type="primary">{{ $t('loginButton') }}</a-button> <a-button
:loading="logging"
class="logging-button"
size="large"
htmlType="submit"
type="primary"
>{{ $t('loginButton') }}</a-button
>
</a-form-item> </a-form-item>
</a-form> </a-form>
</div> </div>
...@@ -60,13 +78,12 @@ ...@@ -60,13 +78,12 @@
<script> <script>
import CommonLayout from '@/pages/frame/layouts/CommonLayout'; import CommonLayout from '@/pages/frame/layouts/CommonLayout';
import { login } from '@/pages/frame/services/accountService'; import { login } from '@/pages/frame/services/accountService';
import { setAuthorization } from '@/utils/requestUtil'; import { setToken, clearToken, setUserId } from '@/utils';
import { loadRoutes } from '@/utils/routerUtil'; import { loadRoutes } from '@/utils/routerUtil';
import { mapMutations } from 'vuex'; import { mapMutations } from 'vuex';
import loginI18n from './i18n'; import loginI18n from './i18n';
export default { export default {
name: 'yarn',
components: { CommonLayout }, components: { CommonLayout },
i18n: loginI18n, i18n: loginI18n,
data() { data() {
...@@ -76,6 +93,9 @@ export default { ...@@ -76,6 +93,9 @@ export default {
form: this.$form.createForm(this), form: this.$form.createForm(this),
}; };
}, },
created() {
clearToken();
},
computed: { computed: {
applicationName() { applicationName() {
return this.$store.state.settingModule.systemName; return this.$store.state.settingModule.systemName;
...@@ -85,13 +105,12 @@ export default { ...@@ -85,13 +105,12 @@ export default {
...mapMutations('accountModule', ['setUser', 'setPermissions', 'setRoles']), ...mapMutations('accountModule', ['setUser', 'setPermissions', 'setRoles']),
onSubmit(e) { onSubmit(e) {
e.preventDefault(); e.preventDefault();
this.form.validateFields((err) => { this.form.validateFields(err => {
if (!err) { if (!err) {
this.logging = true; this.logging = true;
const name = this.form.getFieldValue('name'); const userName = this.form.getFieldValue('userName');
const password = this.form.getFieldValue('password'); const password = this.form.getFieldValue('password');
login(userName, password).then(this.afterLogin);
login(name, password).then(this.afterLogin);
} }
}); });
}, },
...@@ -100,24 +119,10 @@ export default { ...@@ -100,24 +119,10 @@ export default {
}, },
afterLogin(result) { afterLogin(result) {
this.logging = false; this.logging = false;
const loginResult = result.data; const { token, userId } = result;
setToken(token);
if (loginResult.code >= 0) { setUserId(userId);
const { user, permissions, roles, routers } = loginResult.data; this.$router.replace('/dashboard/workbench');
this.setUser(user);
this.setPermissions(permissions);
this.setRoles(roles);
setAuthorization({
token: loginResult.data.token,
expireAt: new Date(loginResult.data.expireAt),
});
// 获取路由配置
loadRoutes(routers);
this.$router.push('/dashboard/workbench');
this.$message.success(loginResult.message, 2);
} else {
this.$message.error(loginResult.message, 2);
}
}, },
}, },
}; };
......
export default { export default {
messages: { messages: {
zh_CN: { zh_CN: {
appName: '基于Antd Vue v1.73 的前端小框架', appName: '基于Antd Vue v1.73 的前端小框架',
tabTitle: '账号密码登录', tabTitle: '账号密码登录',
idPlaceHolder: '请输入登录账号', idPlaceHolder: '请输入登录账号',
pwdPlaceHolder: '请输入登录密码', pwdPlaceHolder: '请输入登录密码',
autoLoginLink: '自动登录', autoLoginLink: '自动登录',
forgetPwdLink: '忘记密码', forgetPwdLink: '忘记密码',
loginButton: '登录', loginButton: '登录',
idNotBlankValider: '登录Id不能为空', idNotBlankValider: '登录Id不能为空',
pwdNotBlankValider: '登录密码不能为空', pwdNotBlankValider: '登录密码不能为空',
},
en_US: {
appName: 'Akina Antd Vue Pro',
tabTitle: 'Login Account',
idPlaceHolder: 'Please input login Id',
pwdPlaceHolder: 'Please input password',
autoLoginLink: 'Auto Login',
forgetPwdLink: 'Forget Password',
loginButton: 'Login',
idNotBlankValider: 'Login Id is required',
pwdNotBlankValider: 'Login password is required',
},
}, },
en_US: { };
appName: 'Akina Antd Vue Pro',
tabTitle: 'Login Account',
idPlaceHolder: 'Please input login Id',
pwdPlaceHolder: 'Please input password',
autoLoginLink: 'Auto Login',
forgetPwdLink: 'Forget Password',
loginButton: 'Login',
idNotBlankValider: 'Login Id is required',
pwdNotBlankValider: 'Login password is required',
},
}
}
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
</template> </template>
<script> <script>
import PageLayout from '../../layouts/PageLayout'; import PageLayout from '../../layouts/PageLayout.vue';
import PageToggleTransition from '@/components/transition/PageToggleTransition'; import PageToggleTransition from '@/components/transition/PageToggleTransition';
import { mapState } from 'vuex'; import { mapState } from 'vuex';
......
<template>
<h1>Home</h1>
</template>
<template> <template>
<h1>Menu Management</h1> <my-table url="/api/v1/menus" :addBtn="addBtn" :formatData="formatData" rowKey="menuId">
<template #search="{query}">
<a-form-model-item label="菜单、目录名称">
<a-input v-model="query.name" />
</a-form-model-item>
</template>
<template #add>
<a-input v-model="add.name" />
</template>
<a-table-column title="名称" data-index="menuName" />
<a-table-column title="类型" data-index="menuTypeName" />
<a-table-column title="显示排序" data-index="viewIndex" :sorter="sorter" sortOrder="ascend" />
<a-table-column title="显示图标" data-index="menuIcon" />
<a-table-column title="模块URL" data-index="menuUrl" />
<a-table-column title="操作">
<template #default>
<span>
<a>编辑</a>
<a-divider type="vertical" />
<a>删除</a>
</span>
</template>
</a-table-column>
</my-table>
</template> </template>
\ No newline at end of file <script>
import { convertListToTree } from '@/utils';
export default {
data: () => ({
add: {},
addBtn: { text: '新建', title: '菜单配置' },
sortOrder: 'ascend',
}),
methods: {
formatData: convertListToTree,
sorter(pre, next) {
return pre.viewIndex - next.viewIndex;
},
},
};
</script>
<template> <template>
<h1>Menu Management</h1> <my-table url="table" addBtn="addBtn">
<template #search="{query}">
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
</template>
<template #add>
<a-input v-model="add.name" />
</template>
<a-table-column title="姓名" data-index="name" />
<a-table-column title="操作">
<template #default="row">
<span>
<a> {{ row }}</a>
<a-divider type="vertical" />
<a>Delete</a>
</span>
</template>
</a-table-column>
</my-table>
</template> </template>
\ No newline at end of file <script>
export default {
data: () => ({
add: {},
addBtn: {},
}),
};
</script>
<template>
<my-table url="table" :search="search" />
</template>
<script>
export default {
data: () => ({
search: [
{
type: 'input',
key: 'name',
label: '所属组织',
options: {
placeholder: '请选择所属组织',
},
},
{
type: 'input',
key: 'address',
label: '地区名称',
},
],
}),
};
</script>
import VueI18nPlugin from './vueI18nPlugin' import VueI18nPlugin from './vueI18nPlugin';
import AuthorityPlugin from './authorityPlugin' import AuthorityPlugin from './authorityPlugin';
import TabsPagePlugin from './tabsPagePlugin' import TabsPagePlugin from './tabsPagePlugin';
import { Table } from '@/components';
const Plugins = { const Plugins = {
install: function (Vue) { install: function(Vue) {
Vue.use(VueI18nPlugin) Vue.use(VueI18nPlugin);
Vue.use(AuthorityPlugin) Vue.use(AuthorityPlugin);
Vue.use(TabsPagePlugin) Vue.use(TabsPagePlugin);
} Vue.component('my-table', Table);
} },
};
export default Plugins; export default Plugins;
import {hasAuthority} from '@/utils/authorityUtil' import { hasAuthority } from '@/utils/authorityUtil';
import {loginIgnore} from '@/router/index' import { loginIgnore } from '@/router/index';
import {checkAuthorization} from '@/utils/requestUtil' import { checkAuthorization } from '@/utils/requestUtil';
import NProgress from 'nprogress' import NProgress from 'nprogress';
NProgress.configure({ showSpinner: false }) NProgress.configure({ showSpinner: false });
/** /**
* 进度条开始 * 进度条开始
...@@ -12,12 +12,12 @@ NProgress.configure({ showSpinner: false }) ...@@ -12,12 +12,12 @@ NProgress.configure({ showSpinner: false })
* @param next * @param next
*/ */
const progressStart = (to, from, next) => { const progressStart = (to, from, next) => {
// start progress bar // start progress bar
if (!NProgress.isStarted()) { if (!NProgress.isStarted()) {
NProgress.start() NProgress.start();
} }
next() next();
} };
/** /**
* 登录守卫 * 登录守卫
...@@ -27,14 +27,14 @@ const progressStart = (to, from, next) => { ...@@ -27,14 +27,14 @@ const progressStart = (to, from, next) => {
* @param options * @param options
*/ */
const loginGuard = (to, from, next, options) => { const loginGuard = (to, from, next, options) => {
const {message} = options const { message } = options;
if (!loginIgnore.includes(to) && !checkAuthorization()) { if (!loginIgnore.includes(to) && !checkAuthorization()) {
message.warning('登录已失效,请重新登录') message.warning('登录已失效,请重新登录');
next({path: '/login'}) next({ path: '/login' });
} else { } else {
next() next();
} }
} };
/** /**
* 权限守卫 * 权限守卫
...@@ -44,17 +44,17 @@ const loginGuard = (to, from, next, options) => { ...@@ -44,17 +44,17 @@ const loginGuard = (to, from, next, options) => {
* @param options * @param options
*/ */
const authorityGuard = (to, from, next, options) => { const authorityGuard = (to, from, next, options) => {
const {store, message} = options const { store, message } = options;
const permissions = store.getters['accountModule/permissions'] const permissions = store.getters['accountModule/permissions'];
const roles = store.getters['accountModule/roles'] const roles = store.getters['accountModule/roles'];
if (!hasAuthority(to, permissions, roles)) { if (!hasAuthority(to, permissions, roles)) {
message.warning(`对不起,您无权访问页面: ${to.fullPath},请联系管理员`) message.warning(`对不起,您无权访问页面: ${to.fullPath},请联系管理员`);
next({path: '/403'}) next({ path: '/403' });
// NProgress.done() // NProgress.done()
} else { } else {
next() next();
} }
} };
/** /**
* 混合导航模式下一级菜单跳转重定向 * 混合导航模式下一级菜单跳转重定向
...@@ -65,27 +65,27 @@ const authorityGuard = (to, from, next, options) => { ...@@ -65,27 +65,27 @@ const authorityGuard = (to, from, next, options) => {
* @returns {*} * @returns {*}
*/ */
const redirectGuard = (to, from, next, options) => { const redirectGuard = (to, from, next, options) => {
const {store} = options const { store } = options;
const getFirstChild = (routes) => { const getFirstChild = routes => {
const route = routes[0] const route = routes[0];
if (!route.children || route.children.length === 0) { if (!route.children || route.children.length === 0) {
return route return route;
} }
return getFirstChild(route.children) return getFirstChild(route.children);
} };
if (store.state.settingModule.layout === 'mix') { if (store.state.settingModule.layout === 'mix') {
const firstMenu = store.getters['settingModule/firstMenu'] const firstMenu = store.getters['settingModule/firstMenu'];
if (firstMenu.find(item => item.fullPath === to.fullPath)) { if (firstMenu.find(item => item.fullPath === to.fullPath)) {
store.commit('settingModule/setActivatedFirst', to.fullPath) store.commit('settingModule/setActivatedFirst', to.fullPath);
const subMenu = store.getters['settingModule/subMenu'] const subMenu = store.getters['settingModule/subMenu'];
if (subMenu.length > 0) { if (subMenu.length > 0) {
const redirect = getFirstChild(subMenu) const redirect = getFirstChild(subMenu);
return next({path: redirect.fullPath}) return next({ path: redirect.fullPath });
} }
}
} }
} next();
next() };
}
/** /**
* 进度条结束 * 进度条结束
...@@ -94,11 +94,11 @@ const redirectGuard = (to, from, next, options) => { ...@@ -94,11 +94,11 @@ const redirectGuard = (to, from, next, options) => {
* @param options * @param options
*/ */
const progressDone = () => { const progressDone = () => {
// finish progress bar // finish progress bar
NProgress.done() NProgress.done();
} };
export default { export default {
beforeEach: [progressStart, loginGuard, authorityGuard, redirectGuard], beforeEach: [progressStart, loginGuard, authorityGuard, redirectGuard],
afterEach: [progressDone] afterEach: [progressDone],
} };
import Cookie from 'js-cookie'
// 401拦截 // 401拦截
const resp401 = { const resp401 = {
/** /**
* 响应数据之前做点什么 * 响应数据之前做点什么
* @param response 响应对象 * @param response 响应对象
* @param options 应用配置 包含: {router, i18n, store, message} * @param options 应用配置 包含: {router, i18n, store, message}
* @returns {*} * @returns {*}
*/ */
onFulfilled(response, options) { onFulfilled(response, options) {
const {message} = options const { message } = options;
if (response.code === 401) { if (response.code === 401) {
message.error('无此权限') message.error('无此权限');
} }
return response return response;
}, },
/** /**
* 响应出错时执行 * 响应出错时执行
* @param error 错误对象 * @param error 错误对象
* @param options 应用配置 包含: {router, i18n, store, message} * @param options 应用配置 包含: {router, i18n, store, message}
* @returns {Promise<never>} * @returns {Promise<never>}
*/ */
onRejected(error, options) { onRejected(error, options) {
const {message} = options const { message } = options;
const {response} = error const { response } = error;
if (response.status === 401) { if (response.status === 401) {
message.error('无此权限') message.error('无此权限');
} }
return Promise.reject(error) return Promise.reject(error);
} },
} };
const resp403 = { const resp403 = {
onFulfilled(response, options) { onFulfilled(response, options) {
const {message} = options const { message } = options;
if (response.code === 403) { if (response.code === 403) {
message.error('请求被拒绝') message.error('请求被拒绝');
} }
return response return response;
}, },
onRejected(error, options) { onRejected(error, options) {
const {message} = options const { message } = options;
const {response} = error const { response } = error;
if (response.status === 403) { if (response.status === 403) {
message.error('请求被拒绝') message.error('请求被拒绝');
} }
return Promise.reject(error) return Promise.reject(error);
} },
} };
const reqCommon = { const reqCommon = {
/** /**
* 发送请求之前做些什么 * 发送请求之前做些什么
* @param config axios config * @param config axios config
* @param options 应用配置 包含: {router, i18n, store, message} * @param options 应用配置 包含: {router, i18n, store, message}
* @returns {*} * @returns {*}
*/ */
onFulfilled(config, options) { onFulfilled(config, options) {
const {message} = options const { message } = options;
const {url, xsrfCookieName} = config const { url, xsrfCookieName } = config;
if (url.indexOf('login') === -1 && xsrfCookieName && !Cookie.get(xsrfCookieName)) { if (url.indexOf('login') === -1 && xsrfCookieName) {
message.warning('认证 token 已过期,请重新登录') message.warning('认证 token 已过期,请重新登录');
} }
return config return config;
}, },
/** /**
* 请求出错时做点什么 * 请求出错时做点什么
* @param error 错误对象 * @param error 错误对象
* @param options 应用配置 包含: {router, i18n, store, message} * @param options 应用配置 包含: {router, i18n, store, message}
* @returns {Promise<never>} * @returns {Promise<never>}
*/ */
onRejected(error, options) { onRejected(error, options) {
const {message} = options const { message } = options;
message.error(error.message) message.error(error.message);
return Promise.reject(error) return Promise.reject(error);
} },
} };
export default { export default {
request: [reqCommon], // 请求拦截 request: [reqCommon], // 请求拦截
response: [resp401, resp403] // 响应拦截 response: [resp401, resp403], // 响应拦截
} };
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
//const API_PROXY_PREFIX='/api' //const API_PROXY_PREFIX='/api'
//const BASE_URL = process.env.NODE_ENV === 'production' ? process.env.VUE_APP_API_BASE_URL : API_PROXY_PREFIX //const BASE_URL = process.env.NODE_ENV === 'production' ? process.env.VUE_APP_API_BASE_URL : API_PROXY_PREFIX
const BASE_URL = process.env.VUE_APP_API_BASE_URL; const BASE_URL = process.env.VUE_APP_API_BASE_URL;
export default BASE_URL; export default BASE_URL;
\ No newline at end of file
import { notification } from 'antd';
import { getLocale, history } from 'umi';
import config from '@/config';
import { clearLoginStatus, getLocaleText } from '@/utils';
import HttpRequest from './kim-request';
import store from 'store';
// const codeMessage = {
// 200: getLocaleText('kim.system.request.200'),
// 201: getLocaleText('kim.system.request.201'),
// 202: getLocaleText('kim.system.request.202'),
// 204: getLocaleText('kim.system.request.204'),
// 400: getLocaleText('kim.system.request.400'),
// 401: getLocaleText('kim.system.request.401'),
// 403: getLocaleText('kim.system.request.403'),
// 404: getLocaleText('kim.system.request.404'),
// 406: getLocaleText('kim.system.request.406'),
// 410: getLocaleText('kim.system.request.410'),
// 422: getLocaleText('kim.system.request.422'),
// 500: getLocaleText('kim.system.request.500'),
// 502: getLocaleText('kim.system.request.502'),
// 503: getLocaleText('kim.system.request.503'),
// 504: getLocaleText('kim.system.request.504'),
// };
const {
request: {
baseUrl,
apiPrefix,
resCodeKey,
resMessageKey,
successCode,
isThrowError = true,
authCodes = ['error.system.authc'],
},
} = config;
/**
* 组装url
* @param {url} url
* @param {{}} more
*/
const merge = (url: string, more: any) => {
if (more && more.apiPrefix && typeof more.apiPrefix === 'string') {
return `${config.apiPrefix}${url}`;
}
if (apiPrefix && typeof apiPrefix === 'string') {
return `${config.apiPrefix}${url}`;
}
return url;
};
const headers = () => ({
Authorization: store.get('token'),
'X-Access-Lang': getLocale().replace(/-/, '_'),
});
const axios = new HttpRequest({
baseUrl,
headers: headers || {},
});
/**
* 正常返回结果处理
* @param {返回请求数据} response
* @param {配置项} more
*/
const handleResponse = (response) => {
const { data } = response;
console.log('response', response);
//
if (`${data[resCodeKey]}` !== `${successCode}`) {
if (isThrowError) {
const errorMessage =
data[resMessageKey] || getLocaleText('kim.system.request.default.message');
let error = new Error();
error = { ...error, ...data };
error.code = data[resCodeKey];
error.message = errorMessage;
return Promise.reject(error);
}
}
// success
return data;
};
const handleError = (error, more) => {
const codeMessage = {
200: getLocaleText('kim.system.request.200'),
201: getLocaleText('kim.system.request.201'),
202: getLocaleText('kim.system.request.202'),
204: getLocaleText('kim.system.request.204'),
400: getLocaleText('kim.system.request.400'),
401: getLocaleText('kim.system.request.401'),
403: getLocaleText('kim.system.request.403'),
404: getLocaleText('kim.system.request.404'),
406: getLocaleText('kim.system.request.406'),
410: getLocaleText('kim.system.request.410'),
422: getLocaleText('kim.system.request.422'),
500: getLocaleText('kim.system.request.500'),
502: getLocaleText('kim.system.request.502'),
503: getLocaleText('kim.system.request.503'),
504: getLocaleText('kim.system.request.504'),
};
const { response } = error;
const options = { isThrowError, ...more };
// status
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const {
status,
config: { url },
} = response;
notification.error({
message: `${getLocaleText('kim.system.request.dialog.title')} ${status}`,
description: `${url}${errorText}`,
});
// 跳转到404页面
if (status >= 404 && status < 422) {
history.push('/exception/404');
}
if (status <= 504 && status >= 500) {
history.push('/exception/500');
}
// 跳转到登录页面 可能原因:token 失效
if (status === 403 && status === 401) {
clearLoginStatus();
history.push('/user/login');
}
} else {
// code
const { code, message } = error;
notification.error({
key: `notification_${code ? message : code}`,
message: getLocaleText('kim.system.request.dialog.title'),
description: message,
});
// 跳转到登录页面 可能原因:token 失效
if (authCodes && authCodes.includes(code)) {
// 清空相关信息
clearLoginStatus();
history.push('/user/login');
}
}
if (options.isThrowError) {
throw error;
} else {
return error;
}
};
export default function request(url, options = {}, more = {}, handle = false) {
let newOptions = options;
newOptions.url = url;
if (more.headers) {
newOptions = { ...options, headers: more.headers };
}
if (handle) {
return axios.request(newOptions);
}
return axios
.request(newOptions)
.then((response) => handleResponse(response, more))
.catch((error) => handleError(error, more));
}
/**
* get请求
* @param url
* @param data
* @param more
* @returns {AxiosPromise<any>|Promise<Promise<never> | 返回请求数据.data | *>}
*/
const get = (url, data, more = {}) => {
if (data?.current && !data?.pageNum) {
// eslint-disable-next-line no-param-reassign
data.pageNum = data.current;
}
return request(
`${merge(url, more)}`,
{
method: 'get', // default
params: data,
},
more,
);
};
/**
* post请求
* @param url
* @param data
* @param more
* @returns {AxiosPromise<any>|Promise<Promise<never> | 返回请求数据.data | *>}
*/
const post = (url, data, more = {}) =>
request(
`${merge(url, more)}`,
{
method: 'post', // default
data,
},
more,
);
/**
* put请求
* @param url
* @param data
* @param more
* @returns {AxiosPromise<any>|Promise<Promise<never> | 返回请求数据.data | *>}
*/
const put = (url, data, more = {}) =>
request(
`${merge(url, more)}`,
{
method: 'put', // default
data,
},
more,
);
/**
* delete请求
* @param url
* @param data
* @param more
* @returns {AxiosPromise<any>|Promise<Promise<never> | 返回请求数据.data | *>}
*/
const del = (url, data, more = {}) =>
request(
`${merge(url, more)}`,
{
method: 'delete', // default
data,
},
more,
);
/**
* patch请求
* @param url
* @param data
* @param more
* @returns {AxiosPromise<any>|Promise<Promise<never> | 返回请求数据.data | *>}
*/
const patch = (url, data, more = {}) =>
request(
`${merge(url, more)}`,
{
method: 'patch', // default
data,
...more,
},
more,
);
const downloadFile = (url, data, more = {}) => {
console.log('xiazai');
return request(
`${merge(url, more)}`,
{
method: 'get', // default
params: data,
responseType: 'blob',
},
more,
true,
).then((response) => {
return new Blob([response.data]);
});
};
const formDataUpload = (url, options, more = {}) => {
const formData = new FormData();
if (options) {
Object.keys(options).forEach((key) => {
formData.append(key, options[key]);
});
}
return post(`${merge(url, more)}`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: store.get('token'),
},
...more,
}).then((res) => {
const { code } = res;
if (code === 'sys.success' && typeof options.onSuccess) {
return options.onSuccess(res);
}
return res;
});
};
/**
* 上传文件
* @param {url} url
* @param {data} data
* @param {type} type
* @param {more} more
*/
const uploadFile = (url, data, type = 'formData', more = {}) => {
if (type === 'formData') {
return formDataUpload(url, data, more);
}
return null;
};
export { request, get, post, put, del, patch, uploadFile, downloadFile };
import routesI18n from '@/router/i18n' import routesI18n from '@/router/i18n';
import {getI18nKey} from '@/utils/routerUtil' import { getI18nKey } from '@/utils/routerUtil';
/** /**
* 根据 router options 配置生成 国际化语言 * 根据 router options 配置生成 国际化语言
...@@ -9,23 +9,29 @@ import {getI18nKey} from '@/utils/routerUtil' ...@@ -9,23 +9,29 @@ import {getI18nKey} from '@/utils/routerUtil'
* @returns {*} * @returns {*}
*/ */
function generateI18n(lang, routes, valueKey) { function generateI18n(lang, routes, valueKey) {
routes.forEach(route => { routes.forEach(route => {
let keys = getI18nKey(route.fullPath).split('.') let keys = getI18nKey(route.fullPath).split('.');
let value = valueKey === 'path' ? route[valueKey].split('/').filter(item => !item.startsWith(':') && item != '').join('.') : route[valueKey] console.log(keys);
if (value.includes('_')) { let value =
value = value.replace('_', ' '); valueKey === 'path'
value = value.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()); ? route[valueKey]
} .split('/')
else { .filter(item => !item.startsWith(':') && item != '')
value = value.charAt(0).toUpperCase() + value.toLowerCase().substring(1) .join('.')
} : route[valueKey];
if (value.includes('_')) {
lang.assignProps(keys, value) value = value.replace('_', ' ');
if (route.children) { value = value.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase());
generateI18n(lang, route.children, valueKey) } else {
} value = value.charAt(0).toUpperCase() + value.toLowerCase().substring(1);
}) }
return lang
lang.assignProps(keys, value);
if (route.children) {
generateI18n(lang, route.children, valueKey);
}
});
return lang;
} }
/** /**
...@@ -34,13 +40,17 @@ function generateI18n(lang, routes, valueKey) { ...@@ -34,13 +40,17 @@ function generateI18n(lang, routes, valueKey) {
* @param parentPath * @param parentPath
*/ */
function formatFullPath(routes, parentPath = '') { function formatFullPath(routes, parentPath = '') {
routes.forEach(route => { routes.forEach(route => {
let isFullPath = route.path.substring(0, 1) === '/' let isFullPath = route.path.substring(0, 1) === '/';
route.fullPath = isFullPath ? route.path : (parentPath === '/' ? parentPath + route.path : parentPath + '/' + route.path) route.fullPath = isFullPath
if (route.children) { ? route.path
formatFullPath(route.children, route.fullPath) : parentPath === '/'
} ? parentPath + route.path
}) : parentPath + '/' + route.path;
if (route.children) {
formatFullPath(route.children, route.fullPath);
}
});
} }
/** /**
...@@ -49,16 +59,16 @@ function formatFullPath(routes, parentPath = '') { ...@@ -49,16 +59,16 @@ function formatFullPath(routes, parentPath = '') {
* @param routes * @param routes
*/ */
function mergeI18nFromRoutes(i18n, routes) { function mergeI18nFromRoutes(i18n, routes) {
formatFullPath(routes) formatFullPath(routes);
const zh_CN = generateI18n(new Object(), routes, 'name') const zh_CN = generateI18n(new Object(), routes, 'name');
const en_US = generateI18n(new Object(), routes, 'path') const en_US = generateI18n(new Object(), routes, 'path');
i18n.mergeLocaleMessage('zh_CN', zh_CN) i18n.mergeLocaleMessage('zh_CN', zh_CN);
i18n.mergeLocaleMessage('en_US', en_US) i18n.mergeLocaleMessage('en_US', en_US);
const messages = routesI18n.messages const messages = routesI18n.messages;
Object.keys(messages).forEach(lang => { Object.keys(messages).forEach(lang => {
i18n.mergeLocaleMessage(lang, messages[lang]) i18n.mergeLocaleMessage(lang, messages[lang]);
}) });
} }
/** /**
...@@ -68,25 +78,22 @@ function mergeI18nFromRoutes(i18n, routes) { ...@@ -68,25 +78,22 @@ function mergeI18nFromRoutes(i18n, routes) {
* @returns {Object} * @returns {Object}
*/ */
Object.defineProperty(Object.prototype, 'assignProps', { Object.defineProperty(Object.prototype, 'assignProps', {
writable: false, writable: false,
enumerable: false, enumerable: false,
configurable: true, configurable: true,
value: function (keys, value) { value: function(keys, value) {
let props = this let props = this;
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
let key = keys[i] let key = keys[i];
if (i == keys.length - 1) { if (i == keys.length - 1) {
props[key] = value props[key] = value;
} else { } else {
props[key] = props[key] == undefined ? {} : props[key] props[key] = props[key] == undefined ? {} : props[key];
props = props[key] props = props[key];
} }
} }
return this return this;
} },
}) });
export { export { mergeI18nFromRoutes, formatFullPath };
mergeI18nFromRoutes,
formatFullPath
}
export { default as langUtil } from './langUtils';
export * from './requestUtil';
const USERID_KEY = 'userId';
export function getUserId() {
return window.sessionStorage.getItem(USERID_KEY) || '';
}
export function setUserId(val) {
window.sessionStorage.setItem(USERID_KEY, val);
}
/**
* 转变菜单列表为tree结构
* @param {Array} menuList 菜单列表
*/
export function convertListToTree(menuList) {
const tempMenu = [...menuList];
for (const menu of menuList) {
if (menu.parentMenuId === 0) continue;
const parent = menuList.find(m => m.menuId === menu.parentMenuId);
parent.children ? parent.children.push(menu) : (parent.children = [menu]);
}
return tempMenu.filter(m => m.parentMenuId === 0);
}
const LANG_KEY = 'LANG_KEY';
function setLang(val) {
window.sessionStorage.setItem(LANG_KEY, val);
}
function getLang() {
return window.sessionStorage.getItem(LANG_KEY);
}
export default {
get: getLang,
set: setLang,
};
import axios from 'axios' import axios from 'axios';
import Cookie from 'js-cookie' import { notification } from 'ant-design-vue';
import md5 from 'crypto-js/md5';
import { langUtil } from '.';
// 跨域认证信息 header 名 // 跨域认证信息 header 名
const xsrfHeaderName = 'Authorization' const xsrfHeaderName = 'Authorization';
axios.defaults.timeout = 5000 axios.defaults.timeout = 5000;
axios.defaults.withCredentials= true axios.defaults.withCredentials = true;
axios.defaults.xsrfHeaderName= xsrfHeaderName axios.defaults.xsrfHeaderName = xsrfHeaderName;
axios.defaults.xsrfCookieName= xsrfHeaderName axios.defaults.xsrfCookieName = xsrfHeaderName;
// 认证类型 /**
const AUTH_TYPE = { * @param {*} info 提示函数
BEARER: 'Bearer', */
BASIC: 'basic', function loadResponseInterceptor({ router }) {
AUTH1: 'auth1', axios.interceptors.request.use(
AUTH2: 'auth2', function(config) {
// 在发送请求之前做些什么
config.headers = {
...config.headers,
Authorization: getToken(),
'X-Access-Lang': langUtil.get() || 'zh_CN',
};
return config;
},
function(error) {
// 对请求错误做些什么
return Promise.reject(error);
},
);
// 添加响应拦截器
axios.interceptors.response.use(
function(response) {
const { data } = response;
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
if (data.code === 'sys.success') {
return data.data;
}
if (data.code === 'error.system.authc') {
router.push('/login');
}
notification.error({
message: data.code,
description: h => h('pre', data.message),
});
return Promise.reject(data.message);
},
function(error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
notification.error({
message: `${error.response.status} ${error.response.statusText}`,
description: h => h('pre', error.message),
});
return Promise.reject(error);
},
);
} }
// http method // http method
const METHOD = { const METHOD = {
GET: 'get', GET: 'get',
POST: 'post', POST: 'post',
PUT: 'put', PUT: 'put',
DELETE: 'delete', DELETE: 'delete',
} };
/** /**
* axios请求 * axios请求
...@@ -34,142 +80,57 @@ const METHOD = { ...@@ -34,142 +80,57 @@ const METHOD = {
* @returns {Promise<AxiosResponse<T>>} * @returns {Promise<AxiosResponse<T>>}
*/ */
async function request(url, method, params, config) { async function request(url, method, params, config) {
switch (method) { switch (method) {
case METHOD.GET: case METHOD.GET:
return axios.get(url, {params, ...config}) return axios.get(url, { params, ...config });
case METHOD.POST: case METHOD.POST:
return axios.post(url, params, config) return axios.post(url, params, config);
case METHOD.PUT: case METHOD.PUT:
return axios.put(url, params, config) return axios.put(url, params, config);
case METHOD.DELETE: case METHOD.DELETE:
return axios.delete(url, {params, ...config}) return axios.delete(url, { params, ...config });
default: default:
return axios.get(url, {params, ...config}) return axios.get(url, { params, ...config });
} }
} }
/** /**
* 设置认证信息 * 解析 url 中的参数
* @param auth {Object} * @param url
* @param authType {AUTH_TYPE} 认证类型,默认:{AUTH_TYPE.BEARER} * @returns {Object}
*/ */
function setAuthorization(auth, authType = AUTH_TYPE.BEARER) { function parseUrlParams(url) {
switch (authType) { const params = {};
case AUTH_TYPE.BEARER: if (!url || url === '' || typeof url !== 'string') {
Cookie.set(xsrfHeaderName, 'Bearer ' + auth.token, {expires: auth.expireAt}) return params;
break }
case AUTH_TYPE.BASIC: const paramsStr = url.split('?')[1];
case AUTH_TYPE.AUTH1: if (!paramsStr) {
case AUTH_TYPE.AUTH2: return params;
default: }
break const paramsArr = paramsStr.replace(/&|=/g, ' ').split(' ');
} for (let i = 0; i < paramsArr.length / 2; i++) {
const value = paramsArr[i * 2 + 1];
params[paramsArr[i * 2]] = value === 'true' ? true : value === 'false' ? false : value;
}
return params;
} }
/** const TOKEN_KEY = md5('TOKEN').toString();
* 移出认证信息 function getToken() {
* @param authType {AUTH_TYPE} 认证类型 return window.sessionStorage.getItem(TOKEN_KEY) ?? '';
*/
function removeAuthorization(authType = AUTH_TYPE.BEARER) {
switch (authType) {
case AUTH_TYPE.BEARER:
Cookie.remove(xsrfHeaderName)
break
case AUTH_TYPE.BASIC:
case AUTH_TYPE.AUTH1:
case AUTH_TYPE.AUTH2:
default:
break
}
} }
/** function setToken(val) {
* 检查认证信息 window.sessionStorage.setItem(TOKEN_KEY, val);
* @param authType
* @returns {boolean}
*/
function checkAuthorization(authType = AUTH_TYPE.BEARER) {
switch (authType) {
case AUTH_TYPE.BEARER:
if (Cookie.get(xsrfHeaderName)) {
return true
}
break
case AUTH_TYPE.BASIC:
case AUTH_TYPE.AUTH1:
case AUTH_TYPE.AUTH2:
default:
break
}
return false
} }
/** function clearToken() {
* 加载 axios 拦截器 window.sessionStorage.removeItem(TOKEN_KEY);
* @param interceptors
* @param options
*/
function loadInterceptors(interceptors, options) {
const {request, response} = interceptors
// 加载请求拦截器
request.forEach(item => {
let {onFulfilled, onRejected} = item
if (!onFulfilled || typeof onFulfilled !== 'function') {
onFulfilled = config => config
}
if (!onRejected || typeof onRejected !== 'function') {
onRejected = error => Promise.reject(error)
}
axios.interceptors.request.use(
config => onFulfilled(config, options),
error => onRejected(error, options)
)
})
// 加载响应拦截器
response.forEach(item => {
let {onFulfilled, onRejected} = item
if (!onFulfilled || typeof onFulfilled !== 'function') {
onFulfilled = response => response
}
if (!onRejected || typeof onRejected !== 'function') {
onRejected = error => Promise.reject(error)
}
axios.interceptors.response.use(
response => onFulfilled(response, options),
error => onRejected(error, options)
)
})
} }
/** function checkAuthorization() {
* 解析 url 中的参数 return !!getToken();
* @param url
* @returns {Object}
*/
function parseUrlParams(url) {
const params = {}
if (!url || url === '' || typeof url !== 'string') {
return params
}
const paramsStr = url.split('?')[1]
if (!paramsStr) {
return params
}
const paramsArr = paramsStr.replace(/&|=/g, ' ').split(' ')
for (let i = 0; i < paramsArr.length / 2; i++) {
const value = paramsArr[i * 2 + 1]
params[paramsArr[i * 2]] = value === 'true' ? true : (value === 'false' ? false : value)
}
return params
} }
export { export { METHOD, request, parseUrlParams, loadResponseInterceptor, setToken, checkAuthorization, clearToken };
METHOD,
AUTH_TYPE,
request,
setAuthorization,
removeAuthorization,
checkAuthorization,
loadInterceptors,
parseUrlParams
}
...@@ -5,74 +5,74 @@ import { globalConfig, settingConfig } from '../config'; ...@@ -5,74 +5,74 @@ import { globalConfig, settingConfig } from '../config';
const theme = settingConfig.theme; const theme = settingConfig.theme;
function getThemeColors(color, $theme) { function getThemeColors(color, $theme) {
const _color = color || theme.color const _color = color || theme.color;
const mode = $theme || theme.mode const mode = $theme || theme.mode;
const replaceColors = getThemeToggleColors(_color, mode) const replaceColors = getThemeToggleColors(_color, mode);
const themeColors = [ const themeColors = [
...replaceColors.mainColors, ...replaceColors.mainColors,
...replaceColors.subColors, ...replaceColors.subColors,
...replaceColors.menuColors, ...replaceColors.menuColors,
...replaceColors.contentColors, ...replaceColors.contentColors,
...replaceColors.rgbColors, ...replaceColors.rgbColors,
...replaceColors.functionalColors.success, ...replaceColors.functionalColors.success,
...replaceColors.functionalColors.warning, ...replaceColors.functionalColors.warning,
...replaceColors.functionalColors.error, ...replaceColors.functionalColors.error,
] ];
return themeColors return themeColors;
} }
function changeThemeColor(newColor, $theme) { function changeThemeColor(newColor, $theme) {
let promise = client.changer.changeColor({newColors: getThemeColors(newColor, $theme)}) let promise = client.changer.changeColor({ newColors: getThemeColors(newColor, $theme) });
return promise return promise;
} }
function modifyVars(color) { function modifyVars(color) {
let _color = color || theme.color let _color = color || theme.color;
const palettes = getAntdColors(_color, theme.mode) const palettes = getAntdColors(_color, theme.mode);
const menuColors = getMenuColors(_color, theme.mode) const menuColors = getMenuColors(_color, theme.mode);
const {success, warning, error} = getFunctionalColors(theme.mode) const { success, warning, error } = getFunctionalColors(theme.mode);
const primary = palettes[5] const primary = palettes[5];
return { return {
'primary-color': primary, 'primary-color': primary,
'primary-1': palettes[0], 'primary-1': palettes[0],
'primary-2': palettes[1], 'primary-2': palettes[1],
'primary-3': palettes[2], 'primary-3': palettes[2],
'primary-4': palettes[3], 'primary-4': palettes[3],
'primary-5': palettes[4], 'primary-5': palettes[4],
'primary-6': palettes[5], 'primary-6': palettes[5],
'primary-7': palettes[6], 'primary-7': palettes[6],
'primary-8': palettes[7], 'primary-8': palettes[7],
'primary-9': palettes[8], 'primary-9': palettes[8],
'primary-10': palettes[9], 'primary-10': palettes[9],
'info-color': primary, 'info-color': primary,
'success-color': success[5], 'success-color': success[5],
'warning-color': warning[5], 'warning-color': warning[5],
'error-color': error[5], 'error-color': error[5],
'alert-info-bg-color': palettes[0], 'alert-info-bg-color': palettes[0],
'alert-info-border-color': palettes[2], 'alert-info-border-color': palettes[2],
'alert-success-bg-color': success[0], 'alert-success-bg-color': success[0],
'alert-success-border-color': success[2], 'alert-success-border-color': success[2],
'alert-warning-bg-color': warning[0], 'alert-warning-bg-color': warning[0],
'alert-warning-border-color': warning[2], 'alert-warning-border-color': warning[2],
'alert-error-bg-color': error[0], 'alert-error-bg-color': error[0],
'alert-error-border-color': error[2], 'alert-error-border-color': error[2],
'processing-color': primary, 'processing-color': primary,
'menu-dark-submenu-bg': menuColors[0], 'menu-dark-submenu-bg': menuColors[0],
'layout-header-background': menuColors[1], 'layout-header-background': menuColors[1],
'layout-trigger-background': menuColors[2], 'layout-trigger-background': menuColors[2],
'btn-danger-bg': error[4], 'btn-danger-bg': error[4],
'btn-danger-border': error[4], 'btn-danger-border': error[4],
...globalConfig.theme[theme.mode] ...globalConfig.theme[theme.mode],
} };
} }
function loadLocalTheme(localSetting) { function loadLocalTheme(localSetting) {
if (localSetting && localSetting.theme) { if (localSetting && localSetting.theme) {
let {color, mode} = localSetting.theme let { color, mode } = localSetting.theme;
color = color || theme.color color = color || theme.color;
mode = mode || theme.mode mode = mode || theme.mode;
changeThemeColor(color, mode) changeThemeColor(color, mode);
} }
} }
/** /**
...@@ -81,23 +81,17 @@ function loadLocalTheme(localSetting) { ...@@ -81,23 +81,17 @@ function loadLocalTheme(localSetting) {
* @returns {Object} * @returns {Object}
*/ */
function getLocalSetting(loadTheme) { function getLocalSetting(loadTheme) {
let localSetting = {} let localSetting = {};
try { try {
const localSettingStr = localStorage.getItem(process.env.VUE_APP_SETTING_KEY) const localSettingStr = localStorage.getItem(process.env.VUE_APP_SETTING_KEY) || '{}';
localSetting = JSON.parse(localSettingStr) localSetting = JSON.parse(localSettingStr);
} catch (e) { } catch (e) {
console.error(e) console.error(e);
} }
if (loadTheme) { if (loadTheme) {
loadLocalTheme(localSetting) loadLocalTheme(localSetting);
} }
return localSetting return localSetting;
} }
export { export { getThemeColors, changeThemeColor, modifyVars, loadLocalTheme, getLocalSetting };
getThemeColors,
changeThemeColor,
modifyVars,
loadLocalTheme,
getLocalSetting
}
module.exports = {
purge: ['./src/**/*.vue'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
prefix: 'tw-',
corePlugins: {
preflight: false,
fixed: false,
},
};
let path = require("path"); let path = require('path');
const webpack = require("webpack"); const webpack = require('webpack');
const ThemeColorReplacer = require("webpack-theme-color-replacer"); const ThemeColorReplacer = require('webpack-theme-color-replacer');
const globalConfig = require("./src/config/global.config"); const globalConfig = require('./src/config/global.config');
const settingConfig = require("./src/config/setting.config"); const settingConfig = require('./src/config/setting.config');
const cssResolverConfig = require("./src/config/cssResolver.config"); const cssResolverConfig = require('./src/config/cssResolver.config');
const { getMenuColors, getAntdColors, getThemeToggleColors, getFunctionalColors } = require('./src/utils/colorUtil'); const {
getMenuColors,
const CompressionWebpackPlugin = require("compression-webpack-plugin"); getAntdColors,
getThemeToggleColors,
getFunctionalColors,
} = require('./src/utils/colorUtil');
const productionGzipExtensions = ["js", "css"]; const CompressionWebpackPlugin = require('compression-webpack-plugin');
const isProd = process.env.NODE_ENV === "production";
const productionGzipExtensions = ['js', 'css'];
const isProd = process.env.NODE_ENV === 'production';
const theme = settingConfig.theme; const theme = settingConfig.theme;
function getThemeColors(color, $theme) { function getThemeColors(color, $theme) {
const _color = color || theme.color const _color = color || theme.color;
const mode = $theme || theme.mode const mode = $theme || theme.mode;
const replaceColors = getThemeToggleColors(_color, mode) const replaceColors = getThemeToggleColors(_color, mode);
const themeColors = [ const themeColors = [
...replaceColors.mainColors, ...replaceColors.mainColors,
...replaceColors.subColors, ...replaceColors.subColors,
...replaceColors.menuColors, ...replaceColors.menuColors,
...replaceColors.contentColors, ...replaceColors.contentColors,
...replaceColors.rgbColors, ...replaceColors.rgbColors,
...replaceColors.functionalColors.success, ...replaceColors.functionalColors.success,
...replaceColors.functionalColors.warning, ...replaceColors.functionalColors.warning,
...replaceColors.functionalColors.error, ...replaceColors.functionalColors.error,
] ];
return themeColors return themeColors;
} }
function modifyVars(color) { function modifyVars(color) {
let _color = color || theme.color let _color = color || theme.color;
const palettes = getAntdColors(_color, theme.mode) const palettes = getAntdColors(_color, theme.mode);
const menuColors = getMenuColors(_color, theme.mode) const menuColors = getMenuColors(_color, theme.mode);
const { success, warning, error } = getFunctionalColors(theme.mode) const { success, warning, error } = getFunctionalColors(theme.mode);
const primary = palettes[5] const primary = palettes[5];
return { return {
'primary-color': primary, 'primary-color': primary,
'primary-1': palettes[0], 'primary-1': palettes[0],
'primary-2': palettes[1], 'primary-2': palettes[1],
'primary-3': palettes[2], 'primary-3': palettes[2],
'primary-4': palettes[3], 'primary-4': palettes[3],
'primary-5': palettes[4], 'primary-5': palettes[4],
'primary-6': palettes[5], 'primary-6': palettes[5],
'primary-7': palettes[6], 'primary-7': palettes[6],
'primary-8': palettes[7], 'primary-8': palettes[7],
'primary-9': palettes[8], 'primary-9': palettes[8],
'primary-10': palettes[9], 'primary-10': palettes[9],
'info-color': primary, 'info-color': primary,
'success-color': success[5], 'success-color': success[5],
'warning-color': warning[5], 'warning-color': warning[5],
'error-color': error[5], 'error-color': error[5],
'alert-info-bg-color': palettes[0], 'alert-info-bg-color': palettes[0],
'alert-info-border-color': palettes[2], 'alert-info-border-color': palettes[2],
'alert-success-bg-color': success[0], 'alert-success-bg-color': success[0],
'alert-success-border-color': success[2], 'alert-success-border-color': success[2],
'alert-warning-bg-color': warning[0], 'alert-warning-bg-color': warning[0],
'alert-warning-border-color': warning[2], 'alert-warning-border-color': warning[2],
'alert-error-bg-color': error[0], 'alert-error-bg-color': error[0],
'alert-error-border-color': error[2], 'alert-error-border-color': error[2],
'processing-color': primary, 'processing-color': primary,
'menu-dark-submenu-bg': menuColors[0], 'menu-dark-submenu-bg': menuColors[0],
'layout-header-background': menuColors[1], 'layout-header-background': menuColors[1],
'layout-trigger-background': menuColors[2], 'layout-trigger-background': menuColors[2],
'btn-danger-bg': error[4], 'btn-danger-bg': error[4],
'btn-danger-border': error[4], 'btn-danger-border': error[4],
...globalConfig.theme[theme.mode] ...globalConfig.theme[theme.mode],
} };
} }
// 修正 webpack-theme-color-replacer 插件提取的 css 结果 // 修正 webpack-theme-color-replacer 插件提取的 css 结果
function resolveCss(output, srcArr) { function resolveCss(output, srcArr) {
let regExps = [] let regExps = [];
// 提取 resolve 配置中所有的正则配置 // 提取 resolve 配置中所有的正则配置
Object.keys(cssResolverConfig).forEach(key => { Object.keys(cssResolverConfig).forEach(key => {
let isRegExp = false let isRegExp = false;
let reg = {} let reg = {};
try { try {
reg = eval(key) reg = eval(key);
isRegExp = reg instanceof RegExp isRegExp = reg instanceof RegExp;
} catch (e) { } catch (e) {
isRegExp = false isRegExp = false;
} }
if (isRegExp) { if (isRegExp) {
regExps.push([reg, cssResolverConfig[key]]) regExps.push([reg, cssResolverConfig[key]]);
} }
}) });
// 去重 // 去重
srcArr = dropDuplicate(srcArr) srcArr = dropDuplicate(srcArr);
// 处理 css // 处理 css
let outArr = [] let outArr = [];
srcArr.forEach(text => { srcArr.forEach(text => {
// 转换为 css 对象 // 转换为 css 对象
let cssObj = parseCssObj(text) let cssObj = parseCssObj(text);
// 根据selector匹配配置,匹配成功,则按配置处理 css // 根据selector匹配配置,匹配成功,则按配置处理 css
if (cssResolverConfig[cssObj.selector] != undefined) { if (cssResolverConfig[cssObj.selector] != undefined) {
let cfg = cssResolverConfig[cssObj.selector] let cfg = cssResolverConfig[cssObj.selector];
if (cfg) { if (cfg) {
outArr.push(cfg.resolve(text, cssObj)) outArr.push(cfg.resolve(text, cssObj));
} }
} else { } else {
let cssText = '' let cssText = '';
// 匹配不成功,则测试是否有匹配的正则配置,有则按正则对应的配置处理 // 匹配不成功,则测试是否有匹配的正则配置,有则按正则对应的配置处理
for (let regExp of regExps) { for (let regExp of regExps) {
if (regExp[0].test(cssObj.selector)) { if (regExp[0].test(cssObj.selector)) {
let cssCfg = regExp[1] let cssCfg = regExp[1];
cssText = cssCfg ? cssCfg.resolve(text, cssObj) : '' cssText = cssCfg ? cssCfg.resolve(text, cssObj) : '';
break break;
}
// 未匹配到正则,则设置 cssText 为默认的 css(即不处理)
cssText = text;
}
if (cssText != '') {
outArr.push(cssText);
}
} }
// 未匹配到正则,则设置 cssText 为默认的 css(即不处理) });
cssText = text output = outArr.join('\n');
} return output;
if (cssText != '') {
outArr.push(cssText)
}
}
})
output = outArr.join('\n')
return output
} }
// 数组去重 // 数组去重
function dropDuplicate(arr) { function dropDuplicate(arr) {
let map = {} let map = {};
let r = [] let r = [];
for (let s of arr) { for (let s of arr) {
if (!map[s]) { if (!map[s]) {
r.push(s) r.push(s);
map[s] = 1 map[s] = 1;
}
} }
} return r;
return r
} }
/** /**
...@@ -146,137 +151,142 @@ function dropDuplicate(arr) { ...@@ -146,137 +151,142 @@ function dropDuplicate(arr) {
* }} * }}
*/ */
function parseCssObj(cssText) { function parseCssObj(cssText) {
let css = {} let css = {};
const ruleIndex = cssText.indexOf('{') const ruleIndex = cssText.indexOf('{');
css.selector = cssText.substring(0, ruleIndex) css.selector = cssText.substring(0, ruleIndex);
const ruleBody = cssText.substring(ruleIndex + 1, cssText.length - 1) const ruleBody = cssText.substring(ruleIndex + 1, cssText.length - 1);
const rules = ruleBody.split(';') const rules = ruleBody.split(';');
css.rules = rules css.rules = rules;
css.toText = function () { css.toText = function() {
let body = '' let body = '';
this.rules.forEach(item => { body += item + ';' }) this.rules.forEach(item => {
return `${this.selector}{${body}}` body += item + ';';
} });
return css return `${this.selector}{${body}}`;
};
return css;
} }
const assetsCDN = { const assetsCDN = {
// webpack build externals cdn预加载使用 // webpack build externals cdn预加载使用
externals: { externals: {
vue: "Vue", vue: 'Vue',
"vue-router": "VueRouter", 'vue-router': 'VueRouter',
vuex: "Vuex", vuex: 'Vuex',
axios: "axios", axios: 'axios',
nprogress: "NProgress", nprogress: 'NProgress',
clipboard: "ClipboardJS", clipboard: 'ClipboardJS',
"@antv/data-set": "DataSet", '@antv/data-set': 'DataSet',
"js-cookie": "Cookies", },
}, css: [],
css: [], js: [
js: [ '//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js',
"//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js", '//cdn.jsdelivr.net/npm/vue-router@3.3.4/dist/vue-router.min.js',
"//cdn.jsdelivr.net/npm/vue-router@3.3.4/dist/vue-router.min.js", '//cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js',
"//cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js", '//cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js',
"//cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js", '//cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js',
"//cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js", '//cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js',
"//cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js", '//cdn.jsdelivr.net/npm/@antv/data-set@0.11.4/build/data-set.min.js',
"//cdn.jsdelivr.net/npm/@antv/data-set@0.11.4/build/data-set.min.js", ],
"//cdn.jsdelivr.net/npm/js-cookie@2.2.1/src/js.cookie.min.js",
],
}; };
module.exports = { module.exports = {
devServer: { devServer: {
// proxy: { proxy: {
// '/api': { //此处要与 /services/api.js 中的 API_PROXY_PREFIX 值保持一致 '/api': {
// target: process.env.VUE_APP_API_BASE_URL, //此处要与 /services/api.js 中的 API_PROXY_PREFIX 值保持一致
// changeOrigin: true, // target: process.env.VUE_APP_API_BASE_URL,
// pathRewrite: { target: 'http://platform.kuopu.net:9300',
// '^/api': '' changeOrigin: true,
// } // pathRewrite: {
// } // '^/api': ''
// } // }
}, },
pluginOptions: { },
/**
* 导入css 预处理器的一些公共的样式文件变量,比如:variables , mixins , functions,
* 避免在每个样式文件中手动的@import导入,然后在各个css 文件中直接使用 变量。
*
* theme.less 定义的变量预先导入 直接使用
*/
"style-resources-loader": {
preProcessor: "less",
//这个是绝对路径,不能使用 alias中配置的别名路径,如@表示的src
patterns: [path.resolve(__dirname, "./src/theme/default.less")],
}, },
}, pluginOptions: {
configureWebpack: (config) => { /**
config.entry.app = ["babel-polyfill", "whatwg-fetch", "./src/main.js"]; * 导入css 预处理器的一些公共的样式文件变量,比如:variables , mixins , functions,
config.performance = { * 避免在每个样式文件中手动的@import导入,然后在各个css 文件中直接使用 变量。
hints: false, *
}; * theme.less 定义的变量预先导入 直接使用
config.plugins.push( */
new ThemeColorReplacer({ 'style-resources-loader': {
//Optional. output css file name, suport [contenthash] and [hash]. preProcessor: 'less',
fileName: "css/theme-colors-[contenthash:8].css", //这个是绝对路径,不能使用 alias中配置的别名路径,如@表示的src
//matchColors: Array<string> Colors array for extracting css file. patterns: [path.resolve(__dirname, './src/theme/default.less')],
//Css rules which have any one of these colors will be extracted out. },
//抽取所有相关颜色做替换 },
matchColors: getThemeColors(), configureWebpack: config => {
//injectCss: boolean Optional. Inject css text into js file, no need to download theme-colors-xxx.css any more. config.entry.app = ['babel-polyfill', 'whatwg-fetch', './src/main.js'];
injectCss: true, config.performance = {
//resolveCss: Function(resultCss : string) : string Optional. Resolve result css code as you wish. hints: false,
//转换原有CSS中的特定样式为自定义样式 };
resolveCss, config.plugins.push(
}) new ThemeColorReplacer({
); //Optional. output css file name, suport [contenthash] and [hash].
// Ignore all locale files of moment.js fileName: 'css/theme-colors-[contenthash:8].css',
config.plugins.push(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)); //matchColors: Array<string> Colors array for extracting css file.
// 生产环境下将资源压缩成gzip格式 //Css rules which have any one of these colors will be extracted out.
if (isProd) { //抽取所有相关颜色做替换
// add `CompressionWebpack` plugin to webpack plugins matchColors: getThemeColors(),
config.plugins.push( //injectCss: boolean Optional. Inject css text into js file, no need to download theme-colors-xxx.css any more.
new CompressionWebpackPlugin({ injectCss: true,
algorithm: "gzip", //resolveCss: Function(resultCss : string) : string Optional. Resolve result css code as you wish.
test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"), //转换原有CSS中的特定样式为自定义样式
threshold: 10240, resolveCss,
minRatio: 0.8, }),
}) );
); // Ignore all locale files of moment.js
} config.plugins.push(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/));
// if prod, add externals // 生产环境下将资源压缩成gzip格式
if (isProd) { if (isProd) {
config.externals = assetsCDN.externals; // add `CompressionWebpack` plugin to webpack plugins
} config.plugins.push(
}, new CompressionWebpackPlugin({
chainWebpack: (config) => { algorithm: 'gzip',
// 生产环境下关闭css压缩的 colormin 项,因为此项优化与主题色替换功能冲突 test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
if (isProd) { threshold: 10240,
config.plugin("optimize-css").tap((args) => { minRatio: 0.8,
args[0].cssnanoOptions.preset[1].colormin = false; }),
return args; );
}); }
} // if prod, add externals
// 生产环境下使用CDN if (isProd) {
if (isProd) { config.externals = assetsCDN.externals;
config.plugin("html").tap((args) => { }
args[0].cdn = assetsCDN; },
return args; chainWebpack: config => {
}); // 生产环境下关闭css压缩的 colormin 项,因为此项优化与主题色替换功能冲突
} if (isProd) {
}, config.plugin('optimize-css').tap(args => {
css: { args[0].cssnanoOptions.preset[1].colormin = false;
loaderOptions: { return args;
less: { });
lessOptions: { }
modifyVars: modifyVars(), // 生产环境下使用CDN
javascriptEnabled: true, if (isProd) {
config.plugin('html').tap(args => {
args[0].cdn = assetsCDN;
return args;
});
}
},
css: {
loaderOptions: {
less: {
lessOptions: {
modifyVars: modifyVars(),
javascriptEnabled: true,
},
},
postcss: {
plugins: [require('tailwindcss')],
},
}, },
},
}, },
}, publicPath: process.env.VUE_APP_PUBLIC_PATH,
publicPath: process.env.VUE_APP_PUBLIC_PATH, outputDir: 'dist',
outputDir: "dist", assetsDir: 'static',
assetsDir: "static", productionSourceMap: false,
productionSourceMap: false,
}; };
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment