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
### 说明
用户名/密码: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 {
created() {
this.setHtmlTitle();
this.setLanguage(this.lang);
enquireScreen((isMobile) => this.setDevice(isMobile));
enquireScreen(isMobile => this.setDevice(isMobile));
},
mounted() {},
watch: {
lang(val) {
this.setLanguage(val);
this.setHtmlTitle();
},
$route() {
this.setHtmlTitle();
},
'theme.mode': function (val) {
'theme.mode': function(val) {
let closeMessage = this.$message.loading(`您选择了主题模式 ${val}, 正在切换...`);
changeThemeColor(this.theme.color, val).then(closeMessage);
},
'theme.color': function (val) {
'theme.color': function(val) {
let closeMessage = this.$message.loading(`您选择了主题色 ${val}, 正在切换...`);
changeThemeColor(val, this.theme.mode).then(closeMessage);
},
layout: function () {
layout: function() {
window.dispatchEvent(new Event('resize'));
},
},
......@@ -77,6 +76,3 @@ export default {
},
};
</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>
<div :class="['page-header', layout, pageWidth]">
<div class="page-header-wide">
<div class="breadcrumb">
<a-breadcrumb>
<a-breadcrumb-item :key="index" v-for="(item, index) in breadcrumb">
<span>{{item}}</span>
<span>{{ item }}</span>
</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>
</template>
<script>
......@@ -63,43 +38,11 @@ export default {
<style lang="less" scoped>
.page-header {
background: @base-bg-color;
padding: 16px 24px;
&.head.fixed {
margin: auto;
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>
<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';
import Router from 'vue-router';
import { initRouter } from './router';
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 'tailwindcss/tailwind.css';
import 'animate.css/source/animate.css';
import './theme/index.less';
import Plugins from './plugins';
import { loadRoutes, loadGuards, setAppOptions } from './utils/routerUtil';
import { loadInterceptors } from './utils/requestUtil';
import guards from './router/guards';
import interceptors from './router/interceptors';
import { loadResponseInterceptor } from './utils/requestUtil';
import '@/mock';
// import '@/mock';
import 'moment/locale/zh-cn';
//设置为非生产提示
Vue.config.productionTip = false;
//装载Vuex控件
Vue.use(Vuex)
Vue.use(Vuex);
//加载 框架的module包括 命名空间accountModule,settingModule
const store = new Vuex.Store({
modules: {accountModule, settingModule}
modules: { accountModule, settingModule },
});
//装载vue-router控件 如果开发时 不用动态可直接修改这里 isAsynRount=false
......@@ -48,7 +48,6 @@ const i18n = new VueI18n({
silentFallbackWarn: true,
});
//装载antd控件
Vue.use(Antd);
//装载Viser;
......@@ -59,13 +58,22 @@ Vue.use(Plugins);
//启动引导方法应用启动时需要执行的操作放在这里
//设置应用配置
setAppOptions({ router, store, i18n });
// 加载 axios 拦截器
loadInterceptors(interceptors, { router, store, i18n, message: Vue.prototype.$message });
// 加载路由
loadRoutes();
// 加载路由守卫
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({
router,
......
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>
<a-dropdown class="lang header-item">
<div>
<a-icon type="global" /> {{langAlias}}
</div>
<div><a-icon type="global" /> {{ langAlias }}</div>
<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-dropdown>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
import {globalConfig} from '@/config';
import { globalConfig } from '@/config';
export default {
name: "LayoutTopHeaderLang",
name: 'LayoutTopHeaderLang',
data() {
return {
langList: globalConfig.langs,
};
},
computed: {
...mapState("settingModule", ["lang"]),
...mapState('settingModule', ['lang']),
langAlias() {
let lang = this.langList.find((item) => item.key == this.lang);
let lang = this.langList.find(item => item.key == this.lang);
return lang.alias;
},
},
methods: {
...mapMutations("settingModule", ["setLang"]),
...mapMutations('settingModule', ['setLang']),
},
};
</script>
<style lang="less">
</style>
<template>
<a-layout :class="['admin-layout', 'beauty-scroll']">
<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>
<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">
<div class="setting" slot="handler">
......@@ -17,8 +33,20 @@
</drawer>
<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" />
<a-layout-header :class="['virtual-header', {'fixed-tabs' : fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage}]" v-show="fixedHeader"></a-layout-header>
<layout-top-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;`">
<div style="position: relative">
<slot></slot>
......@@ -126,7 +154,7 @@ export default {
matched = matched.slice(0, matched.length - 1);
const { firstMenu } = this;
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);
break;
}
......
<template>
<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 slot="content" name="headerContent"></slot>
<div slot="content" v-if="!this.$slots.headerContent && desc">
<p>{{ desc }}</p>
<div v-if="this.linkList" class="link">
<template v-for="(link, index) in linkList">
<a :key="index" :href="link.href">
<a-icon :type="link.icon" />{{ link.title }}
</a>
<a :key="index" :href="link.href"> <a-icon :type="link.icon" />{{ link.title }} </a>
</template>
</div>
</div>
......@@ -76,7 +81,7 @@ export default {
let breadcrumb = page && page.breadcrumb;
if (breadcrumb) {
let i18nBreadcrumb = [];
breadcrumb.forEach((item) => {
breadcrumb.forEach(item => {
i18nBreadcrumb.push(this.$t(item));
});
return i18nBreadcrumb;
......@@ -95,8 +100,8 @@ export default {
const path = this.$route.path;
let breadcrumb = [];
routes
.filter((item) => path.includes(item.path))
.forEach((route) => {
.filter(item => path.includes(item.path))
.forEach(route => {
const path = route.path.length === 0 ? '/home' : route.path;
breadcrumb.push(this.$t(getI18nKey(path)));
});
......@@ -119,11 +124,7 @@ export default {
</script>
<style lang="less">
.page-header {
margin: 0 -24px 0;
}
.link {
/*margin-top: 16px;*/
line-height: 24px;
a {
font-size: 14px;
......@@ -136,9 +137,7 @@ export default {
}
.page-content {
position: relative;
padding: 24px 0 0;
&.side {
}
&.head.fixed {
margin: 0 auto;
max-width: 1400px;
......
import { request, METHOD, removeAuthorization } from '@/utils/requestUtil';
import BASE_URL from '@/utils/baseUrlUtil';
import { request, METHOD } from '@/utils/requestUtil';
import md5 from 'crypto-js/md5';
/**
* 登录服务,登录成功后 根据用户ID 获取用户 角色 + 菜单 + 功能权限 + 用户基本信息
......@@ -7,11 +7,10 @@ import BASE_URL from '@/utils/baseUrlUtil';
* @param password 账户密码
* @returns {Promise<AxiosResponse<T>>}
*/
export async function login(name, password) {
let loginApi = `${BASE_URL}/login`;
return request(loginApi, METHOD.POST, {
name: name,
password: password
export async function login(userName, password) {
return request('/api/v1/login', METHOD.POST, {
userName,
password: md5(password).toString(),
});
}
......@@ -24,10 +23,9 @@ export function logout() {
localStorage.removeItem(process.env.VUE_APP_PERMISSIONS_KEY);
localStorage.removeItem(process.env.VUE_APP_ROLES_KEY);
localStorage.removeItem(process.env.VUE_APP_USER_KEY);
removeAuthorization();
}
export default {
login,
logout,
}
};
import { globalConfig, settingConfig } from "@/config";
import { formatFullPath } from "@/utils/i18nUtil";
import { filterMenu } from "@/utils/authorityUtil";
import { getLocalSetting } from "@/utils/themeUtil";
import deepClone from "lodash.clonedeep";
import { globalConfig, settingConfig } from '@/config';
import { formatFullPath } from '@/utils/i18nUtil';
import { filterMenu } from '@/utils/authorityUtil';
import { getLocalSetting } from '@/utils/themeUtil';
import { langUtil } from '@/utils';
import deepClone from 'lodash.clonedeep';
const localSetting = getLocalSetting(true);
const customTitlesStr = sessionStorage.getItem(process.env.VUE_APP_TBAS_TITLES_KEY);
......@@ -17,6 +18,7 @@ export default {
pageMinHeight: 0,
menuData: [],
activatedFirst: undefined,
lang: '',
customTitles,
...settingConfig,
...localSetting,
......@@ -34,7 +36,7 @@ export default {
if (menuData.length > 0 && !menuData[0].fullPath) {
formatFullPath(menuData);
}
return menuData.map((item) => {
return menuData.map(item => {
const menuItem = { ...item };
delete menuItem.children;
return menuItem;
......@@ -45,7 +47,7 @@ export default {
if (menuData.length > 0 && !menuData[0].fullPath) {
formatFullPath(menuData);
}
const current = menuData.find((menu) => menu.fullPath === activatedFirst);
const current = menuData.find(menu => menu.fullPath === activatedFirst);
return (current && current.children) || [];
},
},
......@@ -73,6 +75,7 @@ export default {
},
setLang(state, lang) {
state.lang = lang;
langUtil.set(lang);
},
setHideSetting(state, hideSetting) {
state.hideSetting = hideSetting;
......@@ -97,13 +100,16 @@ export default {
},
setCustomTitle(state, { path, title }) {
if (title) {
const obj = state.customTitles.find((item) => item.path === path);
const obj = state.customTitles.find(item => item.path === path);
if (obj) {
obj.title = title;
} else {
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 @@
<a-tabs size="large" :tabBarStyle="{ textAlign: 'center' }">
<a-tab-pane :tab="$t('tabTitle')" key="1">
<a-form-item>
<a-input autocomplete="autocomplete" size="large" :placeholder="$t('idPlaceHolder')" v-decorator="[
'name',
<a-input
autocomplete="autocomplete"
size="large"
:placeholder="$t('idPlaceHolder')"
v-decorator="[
'userName',
{
rules: [
{
......@@ -23,12 +27,18 @@
},
],
},
]">
]"
>
<a-icon slot="prefix" type="user" />
</a-input>
</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',
{
rules: [
......@@ -39,7 +49,8 @@
},
],
},
]">
]"
>
<a-icon slot="prefix" type="lock" />
</a-input>
</a-form-item>
......@@ -50,7 +61,14 @@
<a style="float: right">{{ $t('forgetPwdLink') }}</a>
</div>
<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>
</div>
......@@ -60,13 +78,12 @@
<script>
import CommonLayout from '@/pages/frame/layouts/CommonLayout';
import { login } from '@/pages/frame/services/accountService';
import { setAuthorization } from '@/utils/requestUtil';
import { setToken, clearToken, setUserId } from '@/utils';
import { loadRoutes } from '@/utils/routerUtil';
import { mapMutations } from 'vuex';
import loginI18n from './i18n';
export default {
name: 'yarn',
components: { CommonLayout },
i18n: loginI18n,
data() {
......@@ -76,6 +93,9 @@ export default {
form: this.$form.createForm(this),
};
},
created() {
clearToken();
},
computed: {
applicationName() {
return this.$store.state.settingModule.systemName;
......@@ -85,13 +105,12 @@ export default {
...mapMutations('accountModule', ['setUser', 'setPermissions', 'setRoles']),
onSubmit(e) {
e.preventDefault();
this.form.validateFields((err) => {
this.form.validateFields(err => {
if (!err) {
this.logging = true;
const name = this.form.getFieldValue('name');
const userName = this.form.getFieldValue('userName');
const password = this.form.getFieldValue('password');
login(name, password).then(this.afterLogin);
login(userName, password).then(this.afterLogin);
}
});
},
......@@ -100,24 +119,10 @@ export default {
},
afterLogin(result) {
this.logging = false;
const loginResult = result.data;
if (loginResult.code >= 0) {
const { user, permissions, roles, routers } = loginResult.data;
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);
}
const { token, userId } = result;
setToken(token);
setUserId(userId);
this.$router.replace('/dashboard/workbench');
},
},
};
......
......@@ -22,5 +22,5 @@ export default {
idNotBlankValider: 'Login Id is required',
pwdNotBlankValider: 'Login password is required',
},
}
}
},
};
......@@ -10,7 +10,7 @@
</template>
<script>
import PageLayout from '../../layouts/PageLayout';
import PageLayout from '../../layouts/PageLayout.vue';
import PageToggleTransition from '@/components/transition/PageToggleTransition';
import { mapState } from 'vuex';
......
<template>
<h1>Home</h1>
</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>
<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>
<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>
<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 AuthorityPlugin from './authorityPlugin'
import TabsPagePlugin from './tabsPagePlugin'
import VueI18nPlugin from './vueI18nPlugin';
import AuthorityPlugin from './authorityPlugin';
import TabsPagePlugin from './tabsPagePlugin';
import { Table } from '@/components';
const Plugins = {
install: function (Vue) {
Vue.use(VueI18nPlugin)
Vue.use(AuthorityPlugin)
Vue.use(TabsPagePlugin)
}
}
install: function(Vue) {
Vue.use(VueI18nPlugin);
Vue.use(AuthorityPlugin);
Vue.use(TabsPagePlugin);
Vue.component('my-table', Table);
},
};
export default Plugins;
import {hasAuthority} from '@/utils/authorityUtil'
import {loginIgnore} from '@/router/index'
import {checkAuthorization} from '@/utils/requestUtil'
import NProgress from 'nprogress'
import { hasAuthority } from '@/utils/authorityUtil';
import { loginIgnore } from '@/router/index';
import { checkAuthorization } from '@/utils/requestUtil';
import NProgress from 'nprogress';
NProgress.configure({ showSpinner: false })
NProgress.configure({ showSpinner: false });
/**
* 进度条开始
......@@ -14,10 +14,10 @@ NProgress.configure({ showSpinner: false })
const progressStart = (to, from, next) => {
// start progress bar
if (!NProgress.isStarted()) {
NProgress.start()
NProgress.start();
}
next()
}
next();
};
/**
* 登录守卫
......@@ -27,14 +27,14 @@ const progressStart = (to, from, next) => {
* @param options
*/
const loginGuard = (to, from, next, options) => {
const {message} = options
const { message } = options;
if (!loginIgnore.includes(to) && !checkAuthorization()) {
message.warning('登录已失效,请重新登录')
next({path: '/login'})
message.warning('登录已失效,请重新登录');
next({ path: '/login' });
} else {
next()
next();
}
}
};
/**
* 权限守卫
......@@ -44,17 +44,17 @@ const loginGuard = (to, from, next, options) => {
* @param options
*/
const authorityGuard = (to, from, next, options) => {
const {store, message} = options
const permissions = store.getters['accountModule/permissions']
const roles = store.getters['accountModule/roles']
const { store, message } = options;
const permissions = store.getters['accountModule/permissions'];
const roles = store.getters['accountModule/roles'];
if (!hasAuthority(to, permissions, roles)) {
message.warning(`对不起,您无权访问页面: ${to.fullPath},请联系管理员`)
next({path: '/403'})
message.warning(`对不起,您无权访问页面: ${to.fullPath},请联系管理员`);
next({ path: '/403' });
// NProgress.done()
} else {
next()
next();
}
}
};
/**
* 混合导航模式下一级菜单跳转重定向
......@@ -65,27 +65,27 @@ const authorityGuard = (to, from, next, options) => {
* @returns {*}
*/
const redirectGuard = (to, from, next, options) => {
const {store} = options
const getFirstChild = (routes) => {
const route = routes[0]
const { store } = options;
const getFirstChild = routes => {
const route = routes[0];
if (!route.children || route.children.length === 0) {
return route
}
return getFirstChild(route.children)
return route;
}
return getFirstChild(route.children);
};
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)) {
store.commit('settingModule/setActivatedFirst', to.fullPath)
const subMenu = store.getters['settingModule/subMenu']
store.commit('settingModule/setActivatedFirst', to.fullPath);
const subMenu = store.getters['settingModule/subMenu'];
if (subMenu.length > 0) {
const redirect = getFirstChild(subMenu)
return next({path: redirect.fullPath})
const redirect = getFirstChild(subMenu);
return next({ path: redirect.fullPath });
}
}
}
next()
}
next();
};
/**
* 进度条结束
......@@ -95,10 +95,10 @@ const redirectGuard = (to, from, next, options) => {
*/
const progressDone = () => {
// finish progress bar
NProgress.done()
}
NProgress.done();
};
export default {
beforeEach: [progressStart, loginGuard, authorityGuard, redirectGuard],
afterEach: [progressDone]
}
afterEach: [progressDone],
};
import Cookie from 'js-cookie'
// 401拦截
const resp401 = {
/**
......@@ -8,11 +7,11 @@ const resp401 = {
* @returns {*}
*/
onFulfilled(response, options) {
const {message} = options
const { message } = options;
if (response.code === 401) {
message.error('无此权限')
message.error('无此权限');
}
return response
return response;
},
/**
* 响应出错时执行
......@@ -21,32 +20,32 @@ const resp401 = {
* @returns {Promise<never>}
*/
onRejected(error, options) {
const {message} = options
const {response} = error
const { message } = options;
const { response } = error;
if (response.status === 401) {
message.error('无此权限')
message.error('无此权限');
}
return Promise.reject(error)
}
}
return Promise.reject(error);
},
};
const resp403 = {
onFulfilled(response, options) {
const {message} = options
const { message } = options;
if (response.code === 403) {
message.error('请求被拒绝')
message.error('请求被拒绝');
}
return response
return response;
},
onRejected(error, options) {
const {message} = options
const {response} = error
const { message } = options;
const { response } = error;
if (response.status === 403) {
message.error('请求被拒绝')
}
return Promise.reject(error)
message.error('请求被拒绝');
}
}
return Promise.reject(error);
},
};
const reqCommon = {
/**
......@@ -56,12 +55,12 @@ const reqCommon = {
* @returns {*}
*/
onFulfilled(config, options) {
const {message} = options
const {url, xsrfCookieName} = config
if (url.indexOf('login') === -1 && xsrfCookieName && !Cookie.get(xsrfCookieName)) {
message.warning('认证 token 已过期,请重新登录')
const { message } = options;
const { url, xsrfCookieName } = config;
if (url.indexOf('login') === -1 && xsrfCookieName) {
message.warning('认证 token 已过期,请重新登录');
}
return config
return config;
},
/**
* 请求出错时做点什么
......@@ -70,13 +69,13 @@ const reqCommon = {
* @returns {Promise<never>}
*/
onRejected(error, options) {
const {message} = options
message.error(error.message)
return Promise.reject(error)
}
}
const { message } = options;
message.error(error.message);
return Promise.reject(error);
},
};
export default {
request: [reqCommon], // 请求拦截
response: [resp401, resp403] // 响应拦截
}
response: [resp401, resp403], // 响应拦截
};
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 {getI18nKey} from '@/utils/routerUtil'
import routesI18n from '@/router/i18n';
import { getI18nKey } from '@/utils/routerUtil';
/**
* 根据 router options 配置生成 国际化语言
......@@ -10,22 +10,28 @@ import {getI18nKey} from '@/utils/routerUtil'
*/
function generateI18n(lang, routes, valueKey) {
routes.forEach(route => {
let keys = getI18nKey(route.fullPath).split('.')
let value = valueKey === 'path' ? route[valueKey].split('/').filter(item => !item.startsWith(':') && item != '').join('.') : route[valueKey]
let keys = getI18nKey(route.fullPath).split('.');
console.log(keys);
let value =
valueKey === 'path'
? route[valueKey]
.split('/')
.filter(item => !item.startsWith(':') && item != '')
.join('.')
: route[valueKey];
if (value.includes('_')) {
value = value.replace('_', ' ');
value = value.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
}
else {
value = value.charAt(0).toUpperCase() + value.toLowerCase().substring(1)
value = value.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase());
} else {
value = value.charAt(0).toUpperCase() + value.toLowerCase().substring(1);
}
lang.assignProps(keys, value)
lang.assignProps(keys, value);
if (route.children) {
generateI18n(lang, route.children, valueKey)
generateI18n(lang, route.children, valueKey);
}
})
return lang
});
return lang;
}
/**
......@@ -35,12 +41,16 @@ function generateI18n(lang, routes, valueKey) {
*/
function formatFullPath(routes, parentPath = '') {
routes.forEach(route => {
let isFullPath = route.path.substring(0, 1) === '/'
route.fullPath = isFullPath ? route.path : (parentPath === '/' ? parentPath + route.path : parentPath + '/' + route.path)
let isFullPath = route.path.substring(0, 1) === '/';
route.fullPath = isFullPath
? route.path
: parentPath === '/'
? parentPath + route.path
: parentPath + '/' + route.path;
if (route.children) {
formatFullPath(route.children, route.fullPath)
formatFullPath(route.children, route.fullPath);
}
})
});
}
/**
......@@ -49,16 +59,16 @@ function formatFullPath(routes, parentPath = '') {
* @param routes
*/
function mergeI18nFromRoutes(i18n, routes) {
formatFullPath(routes)
const zh_CN = generateI18n(new Object(), routes, 'name')
const en_US = generateI18n(new Object(), routes, 'path')
formatFullPath(routes);
const zh_CN = generateI18n(new Object(), routes, 'name');
const en_US = generateI18n(new Object(), routes, 'path');
i18n.mergeLocaleMessage('zh_CN', zh_CN)
i18n.mergeLocaleMessage('en_US', en_US)
const messages = routesI18n.messages
i18n.mergeLocaleMessage('zh_CN', zh_CN);
i18n.mergeLocaleMessage('en_US', en_US);
const messages = routesI18n.messages;
Object.keys(messages).forEach(lang => {
i18n.mergeLocaleMessage(lang, messages[lang])
})
i18n.mergeLocaleMessage(lang, messages[lang]);
});
}
/**
......@@ -71,22 +81,19 @@ Object.defineProperty(Object.prototype, 'assignProps', {
writable: false,
enumerable: false,
configurable: true,
value: function (keys, value) {
let props = this
value: function(keys, value) {
let props = this;
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
let key = keys[i];
if (i == keys.length - 1) {
props[key] = value
props[key] = value;
} else {
props[key] = props[key] == undefined ? {} : props[key]
props = props[key]
props[key] = props[key] == undefined ? {} : props[key];
props = props[key];
}
}
return this
}
})
return this;
},
});
export {
mergeI18nFromRoutes,
formatFullPath
}
export { 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 Cookie from 'js-cookie'
import axios from 'axios';
import { notification } from 'ant-design-vue';
import md5 from 'crypto-js/md5';
import { langUtil } from '.';
// 跨域认证信息 header 名
const xsrfHeaderName = 'Authorization'
const xsrfHeaderName = 'Authorization';
axios.defaults.timeout = 5000
axios.defaults.withCredentials= true
axios.defaults.xsrfHeaderName= xsrfHeaderName
axios.defaults.xsrfCookieName= xsrfHeaderName
axios.defaults.timeout = 5000;
axios.defaults.withCredentials = true;
axios.defaults.xsrfHeaderName = xsrfHeaderName;
axios.defaults.xsrfCookieName = xsrfHeaderName;
// 认证类型
const AUTH_TYPE = {
BEARER: 'Bearer',
BASIC: 'basic',
AUTH1: 'auth1',
AUTH2: 'auth2',
/**
* @param {*} info 提示函数
*/
function loadResponseInterceptor({ router }) {
axios.interceptors.request.use(
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
......@@ -23,7 +69,7 @@ const METHOD = {
POST: 'post',
PUT: 'put',
DELETE: 'delete',
}
};
/**
* axios请求
......@@ -36,109 +82,16 @@ const METHOD = {
async function request(url, method, params, config) {
switch (method) {
case METHOD.GET:
return axios.get(url, {params, ...config})
return axios.get(url, { params, ...config });
case METHOD.POST:
return axios.post(url, params, config)
return axios.post(url, params, config);
case METHOD.PUT:
return axios.put(url, params, config)
return axios.put(url, params, config);
case METHOD.DELETE:
return axios.delete(url, {params, ...config})
default:
return axios.get(url, {params, ...config})
}
}
/**
* 设置认证信息
* @param auth {Object}
* @param authType {AUTH_TYPE} 认证类型,默认:{AUTH_TYPE.BEARER}
*/
function setAuthorization(auth, authType = AUTH_TYPE.BEARER) {
switch (authType) {
case AUTH_TYPE.BEARER:
Cookie.set(xsrfHeaderName, 'Bearer ' + auth.token, {expires: auth.expireAt})
break
case AUTH_TYPE.BASIC:
case AUTH_TYPE.AUTH1:
case AUTH_TYPE.AUTH2:
default:
break
}
}
/**
* 移出认证信息
* @param authType {AUTH_TYPE} 认证类型
*/
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
}
}
/**
* 检查认证信息
* @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:
return axios.delete(url, { params, ...config });
default:
break
return axios.get(url, { params, ...config });
}
return false
}
/**
* 加载 axios 拦截器
* @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)
)
})
}
/**
......@@ -147,29 +100,37 @@ function loadInterceptors(interceptors, options) {
* @returns {Object}
*/
function parseUrlParams(url) {
const params = {}
const params = {};
if (!url || url === '' || typeof url !== 'string') {
return params
return params;
}
const paramsStr = url.split('?')[1]
const paramsStr = url.split('?')[1];
if (!paramsStr) {
return params
return params;
}
const paramsArr = paramsStr.replace(/&|=/g, ' ').split(' ')
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)
const value = paramsArr[i * 2 + 1];
params[paramsArr[i * 2]] = value === 'true' ? true : value === 'false' ? false : value;
}
return params
return params;
}
const TOKEN_KEY = md5('TOKEN').toString();
function getToken() {
return window.sessionStorage.getItem(TOKEN_KEY) ?? '';
}
export {
METHOD,
AUTH_TYPE,
request,
setAuthorization,
removeAuthorization,
checkAuthorization,
loadInterceptors,
parseUrlParams
function setToken(val) {
window.sessionStorage.setItem(TOKEN_KEY, val);
}
function clearToken() {
window.sessionStorage.removeItem(TOKEN_KEY);
}
function checkAuthorization() {
return !!getToken();
}
export { METHOD, request, parseUrlParams, loadResponseInterceptor, setToken, checkAuthorization, clearToken };
......@@ -5,9 +5,9 @@ import { globalConfig, settingConfig } from '../config';
const theme = settingConfig.theme;
function getThemeColors(color, $theme) {
const _color = color || theme.color
const mode = $theme || theme.mode
const replaceColors = getThemeToggleColors(_color, mode)
const _color = color || theme.color;
const mode = $theme || theme.mode;
const replaceColors = getThemeToggleColors(_color, mode);
const themeColors = [
...replaceColors.mainColors,
...replaceColors.subColors,
......@@ -17,21 +17,21 @@ function getThemeColors(color, $theme) {
...replaceColors.functionalColors.success,
...replaceColors.functionalColors.warning,
...replaceColors.functionalColors.error,
]
return themeColors
];
return themeColors;
}
function changeThemeColor(newColor, $theme) {
let promise = client.changer.changeColor({newColors: getThemeColors(newColor, $theme)})
return promise
let promise = client.changer.changeColor({ newColors: getThemeColors(newColor, $theme) });
return promise;
}
function modifyVars(color) {
let _color = color || theme.color
const palettes = getAntdColors(_color, theme.mode)
const menuColors = getMenuColors(_color, theme.mode)
const {success, warning, error} = getFunctionalColors(theme.mode)
const primary = palettes[5]
let _color = color || theme.color;
const palettes = getAntdColors(_color, theme.mode);
const menuColors = getMenuColors(_color, theme.mode);
const { success, warning, error } = getFunctionalColors(theme.mode);
const primary = palettes[5];
return {
'primary-color': primary,
'primary-1': palettes[0],
......@@ -62,16 +62,16 @@ function modifyVars(color) {
'layout-trigger-background': menuColors[2],
'btn-danger-bg': error[4],
'btn-danger-border': error[4],
...globalConfig.theme[theme.mode]
}
...globalConfig.theme[theme.mode],
};
}
function loadLocalTheme(localSetting) {
if (localSetting && localSetting.theme) {
let {color, mode} = localSetting.theme
color = color || theme.color
mode = mode || theme.mode
changeThemeColor(color, mode)
let { color, mode } = localSetting.theme;
color = color || theme.color;
mode = mode || theme.mode;
changeThemeColor(color, mode);
}
}
......@@ -81,23 +81,17 @@ function loadLocalTheme(localSetting) {
* @returns {Object}
*/
function getLocalSetting(loadTheme) {
let localSetting = {}
let localSetting = {};
try {
const localSettingStr = localStorage.getItem(process.env.VUE_APP_SETTING_KEY)
localSetting = JSON.parse(localSettingStr)
const localSettingStr = localStorage.getItem(process.env.VUE_APP_SETTING_KEY) || '{}';
localSetting = JSON.parse(localSettingStr);
} catch (e) {
console.error(e)
console.error(e);
}
if (loadTheme) {
loadLocalTheme(localSetting)
loadLocalTheme(localSetting);
}
return localSetting
return localSetting;
}
export {
getThemeColors,
changeThemeColor,
modifyVars,
loadLocalTheme,
getLocalSetting
}
export { 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");
const webpack = require("webpack");
const ThemeColorReplacer = require("webpack-theme-color-replacer");
const globalConfig = require("./src/config/global.config");
const settingConfig = require("./src/config/setting.config");
const cssResolverConfig = require("./src/config/cssResolver.config");
const { getMenuColors, getAntdColors, getThemeToggleColors, getFunctionalColors } = require('./src/utils/colorUtil');
let path = require('path');
const webpack = require('webpack');
const ThemeColorReplacer = require('webpack-theme-color-replacer');
const globalConfig = require('./src/config/global.config');
const settingConfig = require('./src/config/setting.config');
const cssResolverConfig = require('./src/config/cssResolver.config');
const {
getMenuColors,
getAntdColors,
getThemeToggleColors,
getFunctionalColors,
} = require('./src/utils/colorUtil');
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const productionGzipExtensions = ["js", "css"];
const isProd = process.env.NODE_ENV === "production";
const productionGzipExtensions = ['js', 'css'];
const isProd = process.env.NODE_ENV === 'production';
const theme = settingConfig.theme;
function getThemeColors(color, $theme) {
const _color = color || theme.color
const mode = $theme || theme.mode
const replaceColors = getThemeToggleColors(_color, mode)
const _color = color || theme.color;
const mode = $theme || theme.mode;
const replaceColors = getThemeToggleColors(_color, mode);
const themeColors = [
...replaceColors.mainColors,
...replaceColors.subColors,
......@@ -26,16 +31,16 @@ function getThemeColors(color, $theme) {
...replaceColors.functionalColors.success,
...replaceColors.functionalColors.warning,
...replaceColors.functionalColors.error,
]
return themeColors
];
return themeColors;
}
function modifyVars(color) {
let _color = color || theme.color
const palettes = getAntdColors(_color, theme.mode)
const menuColors = getMenuColors(_color, theme.mode)
const { success, warning, error } = getFunctionalColors(theme.mode)
const primary = palettes[5]
let _color = color || theme.color;
const palettes = getAntdColors(_color, theme.mode);
const menuColors = getMenuColors(_color, theme.mode);
const { success, warning, error } = getFunctionalColors(theme.mode);
const primary = palettes[5];
return {
'primary-color': primary,
'primary-1': palettes[0],
......@@ -66,74 +71,74 @@ function modifyVars(color) {
'layout-trigger-background': menuColors[2],
'btn-danger-bg': error[4],
'btn-danger-border': error[4],
...globalConfig.theme[theme.mode]
}
...globalConfig.theme[theme.mode],
};
}
// 修正 webpack-theme-color-replacer 插件提取的 css 结果
function resolveCss(output, srcArr) {
let regExps = []
let regExps = [];
// 提取 resolve 配置中所有的正则配置
Object.keys(cssResolverConfig).forEach(key => {
let isRegExp = false
let reg = {}
let isRegExp = false;
let reg = {};
try {
reg = eval(key)
isRegExp = reg instanceof RegExp
reg = eval(key);
isRegExp = reg instanceof RegExp;
} catch (e) {
isRegExp = false
isRegExp = false;
}
if (isRegExp) {
regExps.push([reg, cssResolverConfig[key]])
regExps.push([reg, cssResolverConfig[key]]);
}
})
});
// 去重
srcArr = dropDuplicate(srcArr)
srcArr = dropDuplicate(srcArr);
// 处理 css
let outArr = []
let outArr = [];
srcArr.forEach(text => {
// 转换为 css 对象
let cssObj = parseCssObj(text)
let cssObj = parseCssObj(text);
// 根据selector匹配配置,匹配成功,则按配置处理 css
if (cssResolverConfig[cssObj.selector] != undefined) {
let cfg = cssResolverConfig[cssObj.selector]
let cfg = cssResolverConfig[cssObj.selector];
if (cfg) {
outArr.push(cfg.resolve(text, cssObj))
outArr.push(cfg.resolve(text, cssObj));
}
} else {
let cssText = ''
let cssText = '';
// 匹配不成功,则测试是否有匹配的正则配置,有则按正则对应的配置处理
for (let regExp of regExps) {
if (regExp[0].test(cssObj.selector)) {
let cssCfg = regExp[1]
cssText = cssCfg ? cssCfg.resolve(text, cssObj) : ''
break
let cssCfg = regExp[1];
cssText = cssCfg ? cssCfg.resolve(text, cssObj) : '';
break;
}
// 未匹配到正则,则设置 cssText 为默认的 css(即不处理)
cssText = text
cssText = text;
}
if (cssText != '') {
outArr.push(cssText)
outArr.push(cssText);
}
}
})
output = outArr.join('\n')
return output
});
output = outArr.join('\n');
return output;
}
// 数组去重
function dropDuplicate(arr) {
let map = {}
let r = []
let map = {};
let r = [];
for (let s of arr) {
if (!map[s]) {
r.push(s)
map[s] = 1
r.push(s);
map[s] = 1;
}
}
return r
return r;
}
/**
......@@ -146,56 +151,58 @@ function dropDuplicate(arr) {
* }}
*/
function parseCssObj(cssText) {
let css = {}
const ruleIndex = cssText.indexOf('{')
css.selector = cssText.substring(0, ruleIndex)
const ruleBody = cssText.substring(ruleIndex + 1, cssText.length - 1)
const rules = ruleBody.split(';')
css.rules = rules
css.toText = function () {
let body = ''
this.rules.forEach(item => { body += item + ';' })
return `${this.selector}{${body}}`
}
return css
let css = {};
const ruleIndex = cssText.indexOf('{');
css.selector = cssText.substring(0, ruleIndex);
const ruleBody = cssText.substring(ruleIndex + 1, cssText.length - 1);
const rules = ruleBody.split(';');
css.rules = rules;
css.toText = function() {
let body = '';
this.rules.forEach(item => {
body += item + ';';
});
return `${this.selector}{${body}}`;
};
return css;
}
const assetsCDN = {
// webpack build externals cdn预加载使用
externals: {
vue: "Vue",
"vue-router": "VueRouter",
vuex: "Vuex",
axios: "axios",
nprogress: "NProgress",
clipboard: "ClipboardJS",
"@antv/data-set": "DataSet",
"js-cookie": "Cookies",
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
nprogress: 'NProgress',
clipboard: 'ClipboardJS',
'@antv/data-set': 'DataSet',
},
css: [],
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/vuex@3.4.0/dist/vuex.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/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/js-cookie@2.2.1/src/js.cookie.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/vuex@3.4.0/dist/vuex.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/clipboard@2.0.6/dist/clipboard.min.js',
'//cdn.jsdelivr.net/npm/@antv/data-set@0.11.4/build/data-set.min.js',
],
};
module.exports = {
devServer: {
// proxy: {
// '/api': { //此处要与 /services/api.js 中的 API_PROXY_PREFIX 值保持一致
proxy: {
'/api': {
//此处要与 /services/api.js 中的 API_PROXY_PREFIX 值保持一致
// target: process.env.VUE_APP_API_BASE_URL,
// changeOrigin: true,
target: 'http://platform.kuopu.net:9300',
changeOrigin: true,
// pathRewrite: {
// '^/api': ''
// }
// }
// }
},
},
},
pluginOptions: {
/**
......@@ -204,21 +211,21 @@ module.exports = {
*
* theme.less 定义的变量预先导入 直接使用
*/
"style-resources-loader": {
preProcessor: "less",
'style-resources-loader': {
preProcessor: 'less',
//这个是绝对路径,不能使用 alias中配置的别名路径,如@表示的src
patterns: [path.resolve(__dirname, "./src/theme/default.less")],
patterns: [path.resolve(__dirname, './src/theme/default.less')],
},
},
configureWebpack: (config) => {
config.entry.app = ["babel-polyfill", "whatwg-fetch", "./src/main.js"];
configureWebpack: config => {
config.entry.app = ['babel-polyfill', 'whatwg-fetch', './src/main.js'];
config.performance = {
hints: false,
};
config.plugins.push(
new ThemeColorReplacer({
//Optional. output css file name, suport [contenthash] and [hash].
fileName: "css/theme-colors-[contenthash:8].css",
fileName: 'css/theme-colors-[contenthash:8].css',
//matchColors: Array<string> Colors array for extracting css file.
//Css rules which have any one of these colors will be extracted out.
//抽取所有相关颜色做替换
......@@ -228,7 +235,7 @@ module.exports = {
//resolveCss: Function(resultCss : string) : string Optional. Resolve result css code as you wish.
//转换原有CSS中的特定样式为自定义样式
resolveCss,
})
}),
);
// Ignore all locale files of moment.js
config.plugins.push(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/));
......@@ -237,11 +244,11 @@ module.exports = {
// add `CompressionWebpack` plugin to webpack plugins
config.plugins.push(
new CompressionWebpackPlugin({
algorithm: "gzip",
test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"),
algorithm: 'gzip',
test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
threshold: 10240,
minRatio: 0.8,
})
}),
);
}
// if prod, add externals
......@@ -249,17 +256,17 @@ module.exports = {
config.externals = assetsCDN.externals;
}
},
chainWebpack: (config) => {
chainWebpack: config => {
// 生产环境下关闭css压缩的 colormin 项,因为此项优化与主题色替换功能冲突
if (isProd) {
config.plugin("optimize-css").tap((args) => {
config.plugin('optimize-css').tap(args => {
args[0].cssnanoOptions.preset[1].colormin = false;
return args;
});
}
// 生产环境下使用CDN
if (isProd) {
config.plugin("html").tap((args) => {
config.plugin('html').tap(args => {
args[0].cdn = assetsCDN;
return args;
});
......@@ -273,10 +280,13 @@ module.exports = {
javascriptEnabled: true,
},
},
postcss: {
plugins: [require('tailwindcss')],
},
},
},
publicPath: process.env.VUE_APP_PUBLIC_PATH,
outputDir: "dist",
assetsDir: "static",
outputDir: 'dist',
assetsDir: 'static',
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