Commit f73bbf52 authored by 陈浩玮's avatar 陈浩玮

Merge branch 'feature/shuiluo' into 'master'

feat: 完成菜单组件管理

See merge request product/kim3-web-vue/starter-web-vue!22
parents 9cc3fe98 e7671869
VUE_APP_PUBLIC_PATH=/
VUE_APP_NAME=Admin
VUE_APP_NAME=Karote
VUE_APP_ROUTES_KEY=admin.routes
VUE_APP_PERMISSIONS_KEY=admin.permissions
VUE_APP_ROLES_KEY=admin.roles
......
import { request, METHOD } from '@/utils';
export * from './menu';
export * from './system';
export * from './task';
export function getUserDetailInfoApi() {
return request('/api/v1/detail', METHOD.GET);
......
......@@ -35,3 +35,11 @@ export function getRoleApi(id) {
export function updateRoleApi(data) {
return putReq('/api/v1/roles', data);
}
export function getMenuComponentApi(id) {
return getReq(`/api/v1/menus/${id}/components`);
}
export function addMenuComponentApi(id, data) {
return postReq(`/api/v1/menus/${id}/components`, data);
}
import { postReq, putReq } from '@/utils';
export function addTaskApi(data) {
return postReq('/api/v1/schedules', data);
}
export function updateTaskApi(data) {
return putReq('/api/v1/schedules', data);
}
<template>
<div class="components-input-demo-presuffix">
<a-input :placeholder="placeholder" :value="cron" @input="handleinput">
<a-icon slot="prefix" type="schedule" title="corn控件" @click="openModal" />
<a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空" />
</a-input>
<AntCron ref="innerVueCron" :data="afterCron" @ok="handleOK"></AntCron>
</div>
<div class="components-input-demo-presuffix">
<a-input :placeholder="placeholder" :value="cron" @input="handleinput" :disabled="disabled">
<a-icon slot="prefix" type="schedule" title="corn控件" @click="openModal" />
<a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空" />
</a-input>
<AntCron ref="innerVueCron" :data="afterCron" @ok="handleOK"></AntCron>
</div>
</template>
<script>
import AntCron from "./AntCron";
import { replaceWeekName } from "./validator";
import AntCron from './AntCron';
import { replaceWeekName } from './validator';
export default {
name: "ACron",
components: {
AntCron
},
props: {
value: {
required: false,
type: String,
default: ""
name: 'ACron',
components: {
AntCron,
},
placeholder: {
required: false,
type: String,
default: ""
}
},
data() {
return {
cron: this.value,
afterCron: ""
};
},
watch: {
value(val) {
this.cron = val;
props: {
value: {
required: false,
type: String,
default: '',
},
placeholder: {
required: false,
type: String,
default: '',
},
disabled: Boolean,
},
cron(val) {
console.log(replaceWeekName(val));
this.afterCron = replaceWeekName(val);
console.log(val);
this.$emit("input", val);
}
},
methods: {
openModal() {
this.$refs.innerVueCron.show();
data() {
return {
cron: this.value,
afterCron: '',
};
},
handleOK(val) {
this.cron = val;
this.$emit("change", this.cron);
watch: {
value(val) {
this.cron = val;
},
cron(val) {
console.log(replaceWeekName(val));
this.afterCron = replaceWeekName(val);
console.log(val);
this.$emit('input', val);
},
},
handleinput(evt) {
this.cron = evt.target.value;
if (this.cron !== "") {
this.$emit("change", this.cron);
} else {
this.$emit("change", undefined);
}
methods: {
openModal() {
if (this.disabled) return;
this.$refs.innerVueCron.show();
},
handleOK(val) {
this.cron = val;
this.$emit('change', this.cron);
},
handleinput(evt) {
this.cron = evt.target.value;
if (this.cron !== '') {
this.$emit('change', this.cron);
} else {
this.$emit('change', undefined);
}
},
handleEmpty() {
if (this.disabled) return;
this.handleOK('');
},
},
model: {
prop: 'value',
event: 'change',
},
handleEmpty() {
this.handleOK("");
}
},
model: {
prop: "value",
event: "change"
}
};
</script>
<style scoped>
.components-input-demo-presuffix .anticon-close-circle {
cursor: pointer;
color: #ccc;
transition: color 0.3s;
font-size: 12px;
cursor: pointer;
color: #ccc;
transition: color 0.3s;
font-size: 12px;
}
.components-input-demo-presuffix .anticon-close-circle:hover {
color: #f5222d;
color: #f5222d;
}
.components-input-demo-presuffix .anticon-close-circle:active {
color: #666;
color: #666;
}
</style>
\ No newline at end of file
</style>
This diff is collapsed.
......@@ -20,15 +20,16 @@ export default {
async submit() {
await this.$refs.DrawerForm.validate();
if (this.isAdd) {
return this?.add();
return this.add();
}
if (this.isEdit) {
return this?.edit();
return this.edit();
}
},
setData(data, type) {
this.form = { ...data };
this.type = type;
console.log(data, type);
},
},
};
......@@ -9,7 +9,7 @@
:onOk="btn.onOk"
:label="btn.label"
/>
<a v-else @click="() => btn.click(row)">{{ btn.label }}</a>
<a v-else @click="() => btn.click(row)" v-bind="btn.option">{{ btn.label }}</a>
<a-divider type="vertical" v-if="moreBtns.length || index !== basicBtns.length - 1" />
</span>
......@@ -26,7 +26,7 @@
:onOk="btn.onOk"
:label="btn.label"
/>
<a v-else @click="() => btn.click(row)">{{ btn.label }}</a>
<a v-else @click="() => btn.click(row)" v-bind="btn.option">{{ btn.label }}</a>
</span>
</a-space>
</template>
......@@ -77,10 +77,10 @@ export default {
return url;
},
init() {
this.buttons.map((i) => {
this.buttons.map(i => {
i.__hidden__ = i.isHidden && i.isHidden(this.row);
});
this.buttonsArr = this.buttons.filter((i) => !i.__hidden__);
this.buttonsArr = this.buttons.filter(i => !i.__hidden__);
},
},
};
......
......@@ -180,6 +180,7 @@ export default {
this.addVisible = true;
this.type = 0;
this.noFooter = false;
this.title = '新增';
},
addDrawerClose() {
this.addVisible = false;
......
......@@ -15,10 +15,14 @@
</div>
</my-card>
<my-card class="tw-mt-3">
<a-space class="tw-mb-2.5">
<slot name="action" />
<my-card>
<a-space :class="{ 'tw-mb-2.5': !!$scopedSlots.search }">
<a-button type="primary" v-if="addBtn" @click="addBtnClick">
{{ typeof addBtn === 'object' ? addBtn.text : '新增' }}
</a-button>
<slot name="operation" />
</a-space>
<a-table
:data-source="data"
:loading="loading"
......@@ -27,8 +31,26 @@
@change="pageChange"
>
<slot />
<a-table-column title="操作" v-if="buttons">
<template #default="row">
<my-ac-btn :row="row" :buttons="buttons" />
</template>
</a-table-column>
</a-table>
</my-card>
<a-drawer
placement="right"
:visible="visible"
:drawerStyle="drawerStyle"
:bodyStyle="bodyStyle"
destroyOnClose
:width="600"
@close="hidden"
:maskClosable="false"
>
<slot name="drawer" :hidden="hidden" />
</a-drawer>
</div>
</template>
......@@ -43,6 +65,10 @@ const initQuery = {
export default {
props: {
url: String,
addBtn: [Object, Boolean],
buttons: Array,
noPage: Boolean,
formatData: Function,
},
data() {
......@@ -54,6 +80,17 @@ export default {
queryForm: {},
loading: false,
total: 0,
visible: false,
title: '新增',
drawerStyle: {
display: 'flex',
flexDirection: 'column',
overflowY: 'hidden',
},
bodyStyle: {
flex: 1,
overflow: 'hidden',
},
};
},
......@@ -63,12 +100,14 @@ export default {
computed: {
pagination() {
return {
current: this.initQuery.pageNum,
pageSize: this.initQuery.pageSize,
total: this.total,
showQuickJumper: true,
};
return this.noPage
? false
: {
current: this.initQuery.pageNum,
pageSize: this.initQuery.pageSize,
total: this.total,
showQuickJumper: true,
};
},
},
......@@ -76,7 +115,7 @@ export default {
async getData() {
this.loading = true;
try {
await this.getDataWithPage();
this.noPage ? await this.getDataNoPage() : await this.getDataWithPage();
} catch (error) {
// todo
}
......@@ -90,10 +129,34 @@ export default {
else this.data = res.records;
},
async getDataNoPage() {
const res = await request(this.url, METHOD.GET, this.queryForm);
if (this.formatData) this.data = this.formatData(res);
else this.data = res;
},
pageChange(page) {
this.initQuery.pageNum = page.current;
this.getData();
},
hidden() {
this.visible = false;
},
show({ title } = {}) {
this.visible = true;
if (title) this.title = title;
},
reset() {
this.queryForm = {};
this.initQuery = { ...initQuery };
this.getData();
},
addBtnClick() {
const { click } = typeof this.addBtn === 'object' ? this.addBtn : {};
click && click();
this.visible = true;
},
},
};
</script>
<template>
<div>
<Drawer />
<Table>
<template #search="query">
<slot name="search" :query="query" />
</template>
<slot name="table" />
</Table>
</div>
</template>
<script>
import Table from './table.vue';
import Drawer from './drawer.vue';
export default {
components: { Table, Drawer },
};
</script>
<template>
<div class="tw-flex tw-flex-col tw-h-full">
<div class="tw-overflow-y-hidden tw-flex-1">
<div class="tw-overflow-y-auto tw-h-full">
<slot />
</div>
</div>
<template>
<a-divider />
<a-space class="tw-justify-end">
<a-button @click="cancel">取消</a-button>
<slot name="footer">
<a-button type="primary" @click="ok" :loading="loading">确认</a-button>
</slot>
</a-space>
</template>
</div>
</template>
<script>
import { EMPTY_FUN } from '@/utils';
export default {
props: {
onOk: {
type: Function,
default: EMPTY_FUN,
},
onCancel: Function,
hidden: {
type: Function,
default: EMPTY_FUN,
},
},
data: () => ({
loading: false,
}),
methods: {
cancel() {
if (this.onCancel) this.onCancel();
this.hidden();
},
async ok() {
this.loading = true;
await this.onOk();
this.loading = false;
this.hidden();
},
},
};
</script>
// 全部的配置数据
const globalConfig = {
//支持语言
langs: [
{key: 'zh_CN', name: '简体中文', alias: '简体'},
{key: 'en_US', name: 'English', alias: 'English'}
],
//调色板 颜色
palettes: ["#f5222d", "#fa541c", "#fadb14", "#3eaf7c", "#13c2c2", "#1890ff", "#722ed1", "#eb2f96"],
palettes: ['#f5222d', '#fa541c', '#fadb14', '#3eaf7c', '#13c2c2', '#1890ff', '#722ed1', '#eb2f96'],
//tab出入动画效果
animates: {
preset: [
//参考Animate.css 各种特效的css命名,default对应没有 direction的情况
{ name: "back", alias: "渐近", directions: ["Left", "Right"] },
{ name: "bounce", alias: "弹跳", directions: ["Left", "Right", "Default"] },
{ name: 'back', alias: '渐近', directions: ['Left', 'Right'] },
{ name: 'bounce', alias: '弹跳', directions: ['Left', 'Right', 'Default'] },
{
name: "fade",
alias: "淡化",
directions: ["Left", "LeftBig", "Right", "RightBig", "Default"],
name: 'fade',
alias: '淡化',
directions: ['Left', 'LeftBig', 'Right', 'RightBig', 'Default'],
},
{ name: "lightSpeed", alias: "光速", directions: ["Left", "Right"] },
{ name: "slide", alias: "滑动", directions: ["Left", "Right"] },
{ name: 'lightSpeed', alias: '光速', directions: ['Left', 'Right'] },
{ name: 'slide', alias: '滑动', directions: ['Left', 'Right'] },
],
},
//当前主色调 + 3个功能颜色 + 3个模式菜单颜色
primary: {
color: "#3eaf7c",
warning: "#faad14",
success: "#52c41a",
error: "#f5222d",
color: '#3eaf7c',
warning: '#faad14',
success: '#52c41a',
error: '#f5222d',
light: {
menuColors: ["#000c17", "#001529", "#002140"],
menuColors: ['#000c17', '#001529', '#002140'],
},
dark: {
menuColors: ["#000c17", "#001529", "#002140"],
menuColors: ['#000c17', '#001529', '#002140'],
},
night: {
menuColors: ["#151515", "#1f1f1f", "#1e1e1e"],
menuColors: ['#151515', '#1f1f1f', '#1e1e1e'],
},
},
//主题
theme: {
mode: {
DARK: "dark",
LIGHT: "light",
NIGHT: "night",
DARK: 'dark',
LIGHT: 'light',
NIGHT: 'night',
},
dark: {
"layout-body-background": "#f0f2f5",
"body-background": "#fff",
"component-background": "#fff",
"heading-color": "rgba(0, 0, 0, 0.85)",
"text-color": "rgba(0, 0, 0, 0.65)",
"text-color-inverse": "#fff",
"text-color-secondary": "rgba(0, 0, 0, 0.45)",
"shadow-color": "rgba(0, 0, 0, 0.15)",
"border-color-split": "#f0f0f0",
"background-color-light": "#fafafa",
"background-color-base": "#f5f5f5",
"table-selected-row-bg": "#fafafa",
"table-expanded-row-bg": "#fbfbfb",
"checkbox-check-color": "#fff",
"disabled-color": "rgba(0, 0, 0, 0.25)",
"menu-dark-color": "rgba(254, 254, 254, 0.65)",
"menu-dark-highlight-color": "#fefefe",
"menu-dark-arrow-color": "#fefefe",
"btn-primary-color": "#fff",
'layout-body-background': '#f0f2f5',
'body-background': '#fff',
'component-background': '#fff',
'heading-color': 'rgba(0, 0, 0, 0.85)',
'text-color': 'rgba(0, 0, 0, 0.65)',
'text-color-inverse': '#fff',
'text-color-secondary': 'rgba(0, 0, 0, 0.45)',
'shadow-color': 'rgba(0, 0, 0, 0.15)',
'border-color-split': '#f0f0f0',
'background-color-light': '#fafafa',
'background-color-base': '#f5f5f5',
'table-selected-row-bg': '#fafafa',
'table-expanded-row-bg': '#fbfbfb',
'checkbox-check-color': '#fff',
'disabled-color': 'rgba(0, 0, 0, 0.25)',
'menu-dark-color': 'rgba(254, 254, 254, 0.65)',
'menu-dark-highlight-color': '#fefefe',
'menu-dark-arrow-color': '#fefefe',
'btn-primary-color': '#fff',
},
light: {
"layout-body-background": "#f0f2f5",
"body-background": "#fff",
"component-background": "#fff",
"heading-color": "rgba(0, 0, 0, 0.85)",
"text-color": "rgba(0, 0, 0, 0.65)",
"text-color-inverse": "#fff",
"text-color-secondary": "rgba(0, 0, 0, 0.45)",
"shadow-color": "rgba(0, 0, 0, 0.15)",
"border-color-split": "#f0f0f0",
"background-color-light": "#fafafa",
"background-color-base": "#f5f5f5",
"table-selected-row-bg": "#fafafa",
"table-expanded-row-bg": "#fbfbfb",
"checkbox-check-color": "#fff",
"disabled-color": "rgba(0, 0, 0, 0.25)",
"menu-dark-color": "rgba(1, 1, 1, 0.65)",
"menu-dark-highlight-color": "#fefefe",
"menu-dark-arrow-color": "#fefefe",
"btn-primary-color": "#fff",
'layout-body-background': '#f0f2f5',
'body-background': '#fff',
'component-background': '#fff',
'heading-color': 'rgba(0, 0, 0, 0.85)',
'text-color': 'rgba(0, 0, 0, 0.65)',
'text-color-inverse': '#fff',
'text-color-secondary': 'rgba(0, 0, 0, 0.45)',
'shadow-color': 'rgba(0, 0, 0, 0.15)',
'border-color-split': '#f0f0f0',
'background-color-light': '#fafafa',
'background-color-base': '#f5f5f5',
'table-selected-row-bg': '#fafafa',
'table-expanded-row-bg': '#fbfbfb',
'checkbox-check-color': '#fff',
'disabled-color': 'rgba(0, 0, 0, 0.25)',
'menu-dark-color': 'rgba(1, 1, 1, 0.65)',
'menu-dark-highlight-color': '#fefefe',
'menu-dark-arrow-color': '#fefefe',
'btn-primary-color': '#fff',
},
night: {
"layout-body-background": "#000",
"body-background": "#141414",
"component-background": "#141414",
"heading-color": "rgba(255, 255, 255, 0.85)",
"text-color": "rgba(255, 255, 255, 0.85)",
"text-color-inverse": "#141414",
"text-color-secondary": "rgba(255, 255, 255, 0.45)",
"shadow-color": "rgba(255, 255, 255, 0.15)",
"border-color-split": "#303030",
"background-color-light": "#ffffff0a",
"background-color-base": "#2a2a2a",
"table-selected-row-bg": "#ffffff0a",
"table-expanded-row-bg": "#ffffff0b",
"checkbox-check-color": "#141414",
"disabled-color": "rgba(255, 255, 255, 0.25)",
"menu-dark-color": "rgba(254, 254, 254, 0.65)",
"menu-dark-highlight-color": "#fefefe",
"menu-dark-arrow-color": "#fefefe",
"btn-primary-color": "#141414",
'layout-body-background': '#000',
'body-background': '#141414',
'component-background': '#141414',
'heading-color': 'rgba(255, 255, 255, 0.85)',
'text-color': 'rgba(255, 255, 255, 0.85)',
'text-color-inverse': '#141414',
'text-color-secondary': 'rgba(255, 255, 255, 0.45)',
'shadow-color': 'rgba(255, 255, 255, 0.15)',
'border-color-split': '#303030',
'background-color-light': '#ffffff0a',
'background-color-base': '#2a2a2a',
'table-selected-row-bg': '#ffffff0a',
'table-expanded-row-bg': '#ffffff0b',
'checkbox-check-color': '#141414',
'disabled-color': 'rgba(255, 255, 255, 0.25)',
'menu-dark-color': 'rgba(254, 254, 254, 0.65)',
'menu-dark-highlight-color': '#fefefe',
'menu-dark-arrow-color': '#fefefe',
'btn-primary-color': '#141414',
},
},
layout: {
SIDE: "side",
HEAD: "head",
SIDE: 'side',
HEAD: 'head',
},
};
module.exports = globalConfig;
\ No newline at end of file
......@@ -8,17 +8,16 @@ import VueI18n from 'vue-i18n';
import { accountModule, settingModule } from './pages/frame/store';
import globalStore from '@/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 guards from './router/guards';
import { loadResponseInterceptor } from './utils/requestUtil';
import langUtils from '@/utils/langUtils';
import 'tailwindcss/tailwind.css';
import 'animate.css/source/animate.css';
import './theme/index.less';
// import '@/mock';
import 'moment/locale/zh-cn';
//设置为非生产提示
......@@ -40,13 +39,12 @@ const router = new Router(options);
//装载vue-i18n控件 如果语言优先级 请直接修改这里localeLang和fallbackLang
Vue.use(VueI18n);
//defalt 'CN'
const localeLang = store.state.settingModule.lang;
//default'EN'
const fallbackLang = store.state.settingModule.fallbackLang;
const localeLang = langUtils.get();
const i18n = new VueI18n({
locale: localeLang,
fallbackLocale: fallbackLang,
fallbackLocale: langUtils.fallbackLocale,
silentFallbackWarn: true,
});
......
<template>
<a-dropdown class="lang header-item">
<div><a-icon type="global" /> {{ langAlias }}</div>
<a-menu @click="val => setLang(val.key)" :selected-keys="[lang]" slot="overlay">
<a-menu @click="changeLang" :selected-keys="[lang]" slot="overlay">
<a-menu-item v-for="lang in langList" :key="lang.key">
{{ lang.key.toLowerCase() + ' ' + lang.name }}
</a-menu-item>
......@@ -10,26 +10,28 @@
</template>
<script>
import { mapState, mapMutations } from 'vuex';
import { globalConfig } from '@/config';
import langUtils from '@/utils/langUtils';
export default {
name: 'LayoutTopHeaderLang',
data() {
return {
langList: globalConfig.langs,
langList: langUtils.langList,
lang: langUtils.get(),
};
},
computed: {
...mapState('settingModule', ['lang']),
langAlias() {
let lang = this.langList.find(item => item.key == this.lang);
const lang = this.langList.find(item => item.key == this.lang);
return lang.alias;
},
},
methods: {
...mapMutations('settingModule', ['setLang']),
change(langKey) {
this.$i18n.locale = langKey;
this.lang = langKey;
langUtils.set(langKey);
},
},
};
</script>
<template>
<page-toggle-transition
:disabled="animate.disabled"
:animate="animate.name"
:direction="animate.direction"
>
<router-view />
</page-toggle-transition>
<router-view />
</template>
<script>
import PageToggleTransition from '@/components/transition/PageToggleTransition.vue';
import { mapState } from 'vuex';
export default {
name: 'BlankTemplateView',
components: { PageToggleTransition },
computed: {
...mapState('settingModule', ['multiPage', 'animate']),
...mapState('settingModule', ['multiPage']),
},
};
</script>
<template>
<page-layout :desc="desc" :linkList="linkList">
<div v-if="this.extraImage && !isMobile" slot="extra" class="extraImg">
<div v-if="this.extraImage && !isMobile" slot="extra" :class="$style.extraImg">
<img :src="extraImage" />
</div>
<page-toggle-transition :disabled="animate.disabled" :animate="animate.name" :direction="animate.direction">
<router-view ref="page" />
</page-toggle-transition>
<router-view ref="page" />
</page-layout>
</template>
<script>
import PageLayout from '../../layouts/PageLayout.vue';
import PageToggleTransition from '@/components/transition/PageToggleTransition';
import { mapState } from 'vuex';
export default {
name: 'PageTemplateView',
components: { PageToggleTransition, PageLayout },
components: { PageLayout },
data() {
return {
page: {},
};
},
computed: {
...mapState('settingModule', ['isMobile', 'multiPage', 'animate']),
...mapState('settingModule', ['isMobile', 'multiPage']),
desc() {
return this.page.desc;
},
......@@ -43,7 +40,7 @@ export default {
};
</script>
<style lang="less" scoped>
<style lang="less" modle>
.extraImg {
margin-top: -60px;
text-align: center;
......
......@@ -14,16 +14,10 @@
:class="['tabs-view-content', layout, pageWidth]"
:style="`margin-top: ${multiPage ? -20 : 0}px`"
>
<page-toggle-transition
:disabled="animate.disabled"
:animate="animate.name"
:direction="animate.direction"
>
<a-keep-alive :exclude-keys="excludeKeys" v-if="multiPage && cachePage" v-model="clearCaches">
<router-view v-if="!refreshing" ref="tabContent" :key="$route.fullPath" />
</a-keep-alive>
<router-view ref="tabContent" v-else-if="!refreshing" />
</page-toggle-transition>
<a-keep-alive :exclude-keys="excludeKeys" v-if="multiPage && cachePage" v-model="clearCaches">
<router-view v-if="!refreshing" ref="tabContent" :key="$route.fullPath" />
</a-keep-alive>
<router-view ref="tabContent" v-else-if="!refreshing" />
</div>
</admin-layout>
</template>
......@@ -31,7 +25,6 @@
<script>
import AdminLayout from '../../layouts/AdminLayout';
import Contextmenu from '@/components/menu/Contextmenu.vue';
import PageToggleTransition from '@/components/transition/PageToggleTransition';
import { mapState, mapMutations } from 'vuex';
import { getI18nKey } from '@/utils/routerUtil';
import AKeepAlive from '@/components/cache/AKeepAlive';
......@@ -41,7 +34,7 @@ import templateI18n from './i18n';
export default {
name: 'TabsTemplateView',
i18n: templateI18n,
components: { LayoutTabsHeader, PageToggleTransition, Contextmenu, AdminLayout, AKeepAlive },
components: { LayoutTabsHeader, Contextmenu, AdminLayout, AKeepAlive },
data() {
return {
clearCaches: [],
......@@ -53,7 +46,7 @@ export default {
};
},
computed: {
...mapState('settingModule', ['multiPage', 'cachePage', 'animate', 'layout', 'pageWidth']),
...mapState('settingModule', ['multiPage', 'cachePage', 'layout', 'pageWidth']),
menuItemList() {
return [
......
......@@ -8,7 +8,7 @@
ref="table"
>
<template #drawer>
<AddCom v-if="addCom" />
<AddCom v-if="addCom" :row="currentClickRow" ref="addCom" />
<Form ref="addForm" v-else />
</template>
......@@ -33,24 +33,30 @@ import AddCom from './add_com.vue';
export default {
data: vm => ({
addCom: false,
currentClickRow: null,
addBtn: {
text: '新建',
onOk() {
return vm.$refs['addForm']?.submit();
return vm.addCom ? vm.$refs['addCom']?.submit() : vm.$refs['addForm']?.submit();
},
onCancel() {
vm.addCom = false;
vm.currentClickRow = null;
},
},
sortOrder: 'ascend',
buttons: [
{
label: '新增组件',
click() {
option: {
style: 'color: #ff4d4f',
},
click(row) {
vm.show();
vm.addCom = true;
vm.currentClickRow = row;
},
isHidden: row => row.menuType === 'MENU',
isHidden: row => row.menuType !== 'MENU',
},
{ label: '编辑', click: vm.edit },
{ type: 'confirm', url: row => `/api/v1/menus/${row.menuId}`, after: vm.refreshTable },
......@@ -74,7 +80,7 @@ export default {
});
},
show() {
this.$refs['table'].show();
this.$refs['table'].show({ title: '菜单组件' });
},
},
};
......
<template>
<h1>addCom</h1>
<div>
<a-button type="primary" class="tw-mb-2" @click="add">新增</a-button>
<a-table :dataSource="components" :rowKey="getRowKey">
<a-table-column title="组件编码">
<template #default="row">
<span v-if="row.componentId">{{ row.componentCode }}</span>
<a-input v-else v-model="row.componentCode" />
</template>
</a-table-column>
<a-table-column title="组件名称">
<template #default="row">
<span v-if="row.componentId">{{ row.componentName }}</span>
<a-input v-else v-model="row.componentName" />
</template>
</a-table-column>
<a-table-column title="操作">
<template #default="row">
<a @click="() => del(row)">删除</a>
</template>
</a-table-column>
</a-table>
</div>
</template>
<script>
import { getMenuComponentApi, addMenuComponentApi } from '@/api';
export default {
props: ['row'],
data: () => ({
components: [],
}),
mounted() {
this.getData();
},
methods: {
async getData() {
const { menuId } = this.row;
this.components = await getMenuComponentApi(menuId);
},
add() {
this.components.push({
componentCode: '',
componentName: '',
__key__: this.components.length,
});
},
del(row) {
this.components = this.components.filter(r => r !== row);
},
getRowKey(row) {
return row.__key__ ? row.__key__ : row.componentId;
},
submit() {
const { menuId } = this.row;
return addMenuComponentApi(
menuId,
this.components.map(i => {
delete i.__key__;
return i;
}),
);
},
},
};
</script>
<template>
<a-form-model layout="vertical" :model="form" :rules="rules" ref="DrawerForm">
<a-form-model-item label="任务名称" prop="jobName">
<a-input v-model="form.jobName" :disabled="isView" />
<a-form-model-item label="任务名称" prop="taskName">
<a-input v-model="form.taskName" :disabled="isView" />
</a-form-model-item>
<a-form-model-item label="调度规则" prop="jobDescription">
<a-textarea v-model="form.jobDescription" :disabled="isView" :rows="4" />
<a-form-model-item label="调度规则" prop="taskRule">
<ACron v-model="form.taskRule" :disabled="isView" />
</a-form-model-item>
<a-form-model-item label="实现类" prop="jobDescription">
<a-input v-model="form.jobDescription" :disabled="isView" />
<a-form-model-item label="实现类" prop="implementClass">
<a-input v-model="form.implementClass" :disabled="isView" />
</a-form-model-item>
<a-form-model-item label="说明" prop="jobDescription">
<a-textarea v-model="form.jobDescription" :disabled="isView" :rows="4" />
<a-form-model-item label="说明" prop="remark">
<a-textarea v-model="form.remark" :disabled="isView" :rows="4" />
</a-form-model-item>
</a-form-model>
</template>
<script>
import JobsApi from '@/api/organization';
import { addTaskApi, updateTaskApi } from '@/api';
import FormMixin from '@/components/FormMixin';
import ACron from '@/components/Acron/ACron.vue';
export default {
components: { ACron },
props: ['row'],
mixins: [FormMixin],
data() {
return {
rules: {
jobName: [{ required: true, message: 'Please select Activity zone', trigger: 'change' }],
jobDescription: [
{ required: true, message: 'Please select Activity zone', trigger: 'change' },
],
taskName: [{ required: true }],
taskRule: [{ required: true }],
},
};
},
methods: {
add() {
return JobsApi.add({ ...this.form });
return addTaskApi({ ...this.form });
},
edit() {
return JobsApi.update({ ...this.form });
return updateTaskApi({ ...this.form });
},
},
};
......
<template>
<my-table url="/api/v1/schedules" rowKey="taskId" :addBtn="addBtn" ref="table">
<template #drawer>
<Form ref="form" />
<Form ref="form" :row="currentClickRow" />
</template>
<a-table-column title="任务名称" data-index="taskName" />
<a-table-column title="调度规则" data-index="taskRule" />
......@@ -23,7 +23,12 @@ export default {
components: { ActionButton, Form },
data() {
return {
addBtn: {},
addBtn: {
onOk: () => {
return this.$refs.form.submit();
},
},
currentClickRow: null,
buttons: [
{
label: '查看',
......@@ -47,8 +52,9 @@ export default {
type: 'confirm',
label: '失效',
title: '确认是否失效?',
url: row => `/api/v1/schedules/${row.taskId}`,
url: row => ({ url: `/api/v1/schedules/${row.taskId}/pause`, method: 'del' }),
after: () => this.refreshTable(),
isHidden: row => row.isPaused === 1,
},
{
type: 'confirm',
......
<template>
<Wraper :onCancel="hidden" :hidden="hidden">
<h1>这是内容</h1>
</Wraper>
</template>
<script>
import Wraper from '@/components/table/wraper.vue';
export default {
components: { Wraper },
props: ['hidden'],
};
</script>
<template>
<h1>TODO</h1>
<Table url="/api/v1/logger/login" rowKey="logId" addBtn :actions="actions" ref="table">
<template #search="{query}">
<my-form-item label="开始时间">
<a-date-picker
class="tw-w-full"
show-time
v-model="query.startTime"
valueFormat="YYYY-MM-DD HH:mm:ss"
/>
</my-form-item>
</template>
<template #drawer="{hidden}">
<Form :hidden="hidden" />
</template>
<a-table-column title="用户Id" data-index="userId" />
<a-table-column title="登录用户" data-index="userName" />
<a-table-column title="登录IP" data-index="loginIp" />
<a-table-column title="事件类型" data-index="loginTypeName" />
<a-table-column title="事件状态" data-index="isSuccessName" />
<a-table-column title="登录时间" data-index="loginTime" />
<a-table-column title="说明" data-index="logResultContent" />
</Table>
</template>
<script>
import Table from '@/components/table/table.vue';
import Form from './form.vue';
export default {
components: { Table, Form },
data: () => ({
addBtn: {
text: '新增',
},
actions: [],
}),
};
</script>
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 };
const LANG_KEY = 'LANG_KEY';
// 假如 第一语言没有, 就使用这个配置的语言
const fallbackLocale = 'zh_CN';
// 支持的语言列表
const langList = [
{ key: 'zh_CN', name: '简体中文', alias: '简体' },
{ key: 'en_US', name: 'English', alias: 'English' },
];
function setLang(val) {
window.sessionStorage.setItem(LANG_KEY, val);
window.localStorage.setItem(LANG_KEY, val);
}
function getLang() {
return window.sessionStorage.getItem(LANG_KEY);
return window.localStorage.getItem(LANG_KEY) || 'zh_CN';
}
export default {
get: getLang,
set: setLang,
fallbackLocale,
langList,
};
......@@ -21,7 +21,7 @@ function loadResponseInterceptor({ router }) {
config.headers = {
...config.headers,
Authorization: getToken(),
'X-Access-Lang': langUtil.get() || 'zh_CN',
'X-Access-Lang': langUtil.get(),
};
return config;
},
......
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