This commit is contained in:
ky_sunl
2021-04-21 13:02:38 +00:00
parent 30897c47df
commit cf657fb7ed
17 changed files with 1805 additions and 13 deletions

View File

@@ -4,6 +4,13 @@
width: 660px;
margin: 0 auto;
}
h1,
h2,
h3,
h4,
h5 {
color: darken(@white, 40%);
}
.yo-form-group {
margin-bottom: @padding-md;
}
@@ -146,7 +153,11 @@
padding: 0;
}
.yo-form {
>h3 {
h1,
h2,
h3,
h4,
h5 {
margin-top: @padding-sm;
padding: 0 @padding-md;
}
@@ -162,3 +173,33 @@
}
}
}
.yo-drawer-form {
.ant-drawer-header {
position: absolute;
top: 0;
left: 0;
z-index: 7;
width: 100%;
}
.ant-drawer-body {
padding: @padding-lg + 56px @padding-lg;
}
.ant-drawer-footer {
position: absolute;
left: 0;
bottom: 0;
z-index: 7;
width: 100%;
padding: 10px @padding-md;
text-align: right;
border-top: @border-width-base @border-style-base @border-color-split;
background: @white;
button+button {
margin-left: @padding-xs;
}
}
}

View File

@@ -0,0 +1 @@
在此文件夹中添加控制主题颜色的less文件

View File

@@ -25,6 +25,8 @@ export default {
return {
loading: false,
type: '',
data: [],
pagination: {
@@ -101,14 +103,23 @@ export default {
pageSize: this.pagination.pageSize,
...this.sorter
}).then((res) => {
this.data = res.rows
this.pagination.total = res.totalRows
if (res.rows) {
// 普通表格
this.type = 'table'
this.data = res.rows
this.pagination.total = res.totalRows
} else if (res) {
// 树形表格
this.type = 'tree'
this.data = this.onClearChildren(res)
this.pagination = false
}
this.onLoaded()
})
},
onReloadData(refresh = false) {
if (refresh && refresh.constructor === Boolean) {
if (refresh && refresh.constructor === Boolean && this.pagination.constructor === Object) {
this.pagination.current = this.pageNo
this.pagination.pageSize = this.pageSize
}
@@ -119,7 +130,23 @@ export default {
this.pagination = pagination
this.sorter = sorter
this.onLoadData()
}
},
/**
* 清除没有子节点内容的子节点位置
*/
onClearChildren(data) {
data.forEach(p => {
if (p.children) {
if (p.children.length) {
p.children = this.onClearChildren(p.children)
} else {
delete p.children
}
}
})
return data
},
},
render() {
@@ -154,7 +181,9 @@ export default {
<div class="yo-action-bar--actions">
<a-button-group>
<a-button onClick={this.onReloadData}>刷新</a-button>
<ColumnSetting {...{ props: { columns: this.columns } }} />
{
this.type === 'table' && <ColumnSetting {...{ props: { columns: this.columns } }} />
}
</a-button-group>
</div>
</div>

View File

@@ -0,0 +1,3 @@
基本上所有列表页都可以通过拷贝此处的种子文件实现增删改查的功能
所有带 ... 的注释是可以依据当前业务添加内容的地方
其他所有尽量不要修改

View File

@@ -0,0 +1,78 @@
<template>
<a-modal
:confirmLoading="confirmLoading"
:visible="visible"
@cancel="onCancel"
@ok="onOk"
class="yo-modal-form"
title="新增XX"
>
<FormBody ref="form-body" />
</a-modal>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
visible: false,
confirmLoading: false,
};
},
computed: {
formBody() {
return this.$refs['form-body'];
},
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口
*/
async onOpen() {
this.visible = true;
this.$nextTick(() => {
this.formBody.onInit();
});
},
/**
* 必要的方法
* 点击保存时的操作
*/
onOk() {
this.formBody.onGetData().then((data) => {
this.confirmLoading = true;
this.$api
/** !!此处必须修改调用的接口方法 */
.testAddApi(data)
.then(({ success }) => {
if (success) {
this.$message.success('新增成功');
this.onCancel();
this.$emit('ok');
}
})
.finally(() => {
this.confirmLoading = false;
});
});
},
/**
* 必要的方法
* 关闭窗口时的操作
*/
onCancel() {
this.formBody.onResetFields();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,78 @@
<template>
<a-modal
:confirmLoading="confirmLoading"
:visible="visible"
@close="onCancel"
class="yo-modal-form"
title="编辑XX"
>
<FormBody ref="form-body" />
</a-modal>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
visible: false,
confirmLoading: false,
};
},
computed: {
formBody() {
return this.$refs['form-body'];
},
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口,并填充外部传入的数据
*/
onOpen(record) {
this.visible = true;
this.$nextTick(async () => {
await this.formBody.onInit();
this.formBody.onFillData(record);
});
},
/**
* 必要的方法
* 点击保存时的操作
*/
onOk() {
this.formBody.onGetData().then((data) => {
this.confirmLoading = true;
this.$api
/** !!此处必须修改调用的接口方法 */
.testEditApi(data)
.then(({ success }) => {
if (success) {
this.$message.success('编辑成功');
this.onCancel();
this.$emit('ok');
}
})
.finally(() => {
this.confirmLoading = false;
});
});
},
/**
* 必要的方法
* 关闭窗口时的操作
*/
onCancel() {
this.formBody.onResetFields();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,100 @@
<template>
<a-form-model :model="form" :rules="rules" class="yo-form" ref="form">
<a-spin :spinning="loading">
<a-icon slot="indicator" spin type="loading" />
<div class="yo-form-group">
<!-- 表单控件 -->
</div>
</a-spin>
</a-form-model>
</template>
<script>
export default {
data() {
return {
/** 表单数据 */
form: {},
/** 验证格式 */
rules: {},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/** ... */
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(record) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...record,
/** 在此处添加默认数据转换 */
/** ... */
});
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
onGetData() {
return new Promise((reslove, reject) => {
this.$refs.form.validate((valid) => {
if (valid) {
const record = this.$_.cloneDeep(this.form);
/** 验证通过后可以对数据进行转换得到想要提交的格式 */
/** ... */
reslove(record);
} else {
reject();
}
});
});
},
/**
* 必要的方法
* 在外部窗口进行保存时调用表单验证
*/
onValidate(callback) {
this.$refs.form.validate(callback);
},
/**
* 必要的方法
* 在外部窗口关闭或重置时对表单验证进行初始化
*/
onResetFields() {
setTimeout(() => {
this.$refs.form.resetFields();
/** 在这里可以初始化当前组件中其他属性 */
/** ... */
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit() {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/** ...BEGIN */
/** ...END */
this.loading = false;
},
/** 当前组件的其他方法 */
/** ... */
},
};
</script>

View File

@@ -0,0 +1,187 @@
<template>
<container>
<br />
<a-card :bordered="false">
<Auth auth="authCode:page">
<div class="yo-query-bar">
<a-form-model :model="query" layout="inline">
<!-- 此处添加查询表单控件 -->
<a-form-model-item>
<a-button-group>
<a-button @click="onQuery" type="primary">查询</a-button>
<a-button @click="onResetQuery">重置</a-button>
</a-button-group>
</a-form-model-item>
</a-form-model>
</div>
</Auth>
<yo-table :columns="columns" :load-data="loadData" ref="table">
<Auth auth="authCode:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增XX</a-button>
</Auth>
<!-- 格式化字段内容 -->
<!-- 添加操作控件 -->
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="authCode:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="authCode:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
<!-- 可在此处添加其他操作控件 -->
</yo-table-actions>
</span>
</yo-table>
</a-card>
<br />
<add-form @ok="onReloadData" ref="add-form" />
<edit-form @ok="onReloadData" ref="edit-form" />
</container>
</template>
<script>
import AddForm from './addForm';
import EditForm from './editForm';
export default {
components: {
AddForm,
EditForm,
},
data() {
return {
query: {},
columns: [],
codes: {
code1: [],
code2: [],
},
};
},
created() {
this.onLoadCodes();
/** 根据权限添加操作列 */
const flag = this.$auth(/** ... */);
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return (
this.$api
/** !!此处必须修改调用的接口方法 */
.testGetApi({
...params,
...this.query,
})
.then((res) => {
return res.data;
})
);
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 有查询功能时的必要方法
* 重置查询条件
*/
onResetQuery() {
/** 在这里重置查询条件时,可对特殊的字段做保留处理 */
this.query = {};
this.onQuery();
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 必要方法
* 加载字典数据
* 如果不需要获取相应的字典数据,此方法内容可空
*/
onLoadCodes() {
this.$api
.$queue([
this.$api.sysDictTypeDropDownWait({ code: 'code1' }),
this.$api.sysDictTypeDropDownWait({ code: 'code2' }),
])
.then(([code1, code2]) => {
this.codes.code1 = code1.data;
this.codes.code2 = code2.data;
});
},
/**
* 必要方法
* 绑定数据字典值
*/
bindCodeValue(code, name) {
const c = this.codes[name].find((p) => p.code == code);
if (c) {
return c.value;
}
return null;
},
/**
* 必要方法
* 从列表页调用窗口的打开方法
*/
onOpen(formName, record) {
this.$refs[formName].onOpen(record);
},
/**
* 必要方法
* 可以用做一系列操作的公共回调,此方法中会重新加载当前列表
*/
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
this.$refs.table.onLoaded();
},
/**
* 必要方法
* 删除时调用
*/
onDelete(record) {
this.$refs.table.onLoading();
this.$api
/** !!此处必须修改调用的接口方法 */
.testDeleteApi(record)
.then(({ success }) => {
this.onResult(success, '删除成功');
});
},
},
};
</script>

View File

@@ -0,0 +1,84 @@
<template>
<a-drawer
:confirmLoading="confirmLoading"
:visible="visible"
:width="800"
@close="onClose"
@ok="onOk"
class="yo-drawer-form"
title="新增菜单"
>
<FormBody ref="form-body" />
<div class="ant-drawer-footer">
<a-button @click="onClose">取消</a-button>
<a-button :loading="confirmLoading" @click="onOk" type="primary">确定</a-button>
</div>
</a-drawer>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
visible: false,
confirmLoading: false,
};
},
computed: {
formBody() {
return this.$refs['form-body'];
},
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口
*/
async onOpen() {
this.visible = true;
this.$nextTick(() => {
this.formBody.onInit();
});
},
/**
* 必要的方法
* 点击保存时的操作
*/
onOk() {
this.formBody.onGetData().then((data) => {
this.confirmLoading = true;
this.$api
.sysMenuAdd(data)
.then(({ success }) => {
if (success) {
this.$message.success('新增成功');
this.onClose();
this.$emit('ok');
}
})
.finally(() => {
this.confirmLoading = false;
});
});
},
/**
* 必要的方法
* 关闭窗口时的操作
*/
onClose() {
this.formBody.onResetFields();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,83 @@
<template>
<a-drawer
:confirmLoading="confirmLoading"
:visible="visible"
:width="800"
@close="onClose"
class="yo-drawer-form"
title="编辑应用"
>
<FormBody ref="form-body" />
<div class="ant-drawer-footer">
<a-button @click="onClose">取消</a-button>
<a-button :loading="confirmLoading" @click="onOk" type="primary">确定</a-button>
</div>
</a-drawer>
</template>
<script>
import FormBody from './form';
export default {
components: {
FormBody,
},
data() {
return {
visible: false,
confirmLoading: false,
};
},
computed: {
formBody() {
return this.$refs['form-body'];
},
},
methods: {
/**
* 必要的方法
* 从外部调用打开本窗口,并填充外部传入的数据
*/
onOpen(record) {
this.visible = true;
this.$nextTick(async () => {
await this.formBody.onInit();
this.formBody.onFillData(record);
});
},
/**
* 必要的方法
* 点击保存时的操作
*/
onOk() {
this.formBody.onGetData().then((data) => {
this.confirmLoading = true;
this.$api
.sysMenuEdit(data)
.then(({ success }) => {
if (success) {
this.$message.success('编辑成功');
this.onClose();
this.$emit('ok');
}
})
.finally(() => {
this.confirmLoading = false;
});
});
},
/**
* 必要的方法
* 关闭窗口时的操作
*/
onClose() {
this.formBody.onResetFields();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,297 @@
<template>
<a-form-model :model="form" :rules="rules" class="yo-form" ref="form">
<a-spin :spinning="loading">
<a-icon slot="indicator" spin type="loading" />
<a-alert type="warning">
<template slot="message">当前没有写入字段[重定向]</template>
</a-alert>
<br />
<div class="yo-form-group">
<!-- 表单控件 -->
<h3>基本信息</h3>
<div class="yo-form-group">
<a-form-model-item label="菜单类型" prop="type">
<template slot="help">
目录默认添加在顶级
<br />菜单
<br />按钮
</template>
<a-radio-group @change="onTypeChange" v-model="form.type">
<a-radio-button
:key="type.code"
:value="type.code"
v-for="type in codes.menuType"
>{{type.value}}</a-radio-button>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="名称" prop="name">
<a-input placeholder="请输入名称" v-model="form.name" />
</a-form-model-item>
<a-form-model-item label="唯一编码" prop="code">
<a-input placeholder="请输入唯一编码" v-model="form.code" />
</a-form-model-item>
<a-form-model-item label="所属应用" prop="application">
<a-select
@change="onChangeApplication"
placeholder="请选择所属应用"
v-model="form.application"
>
<a-select-option
:key="item.code"
:value="item.code"
v-for="item in appList"
>{{item.name}}</a-select-option>
</a-select>
</a-form-model-item>
<!-- 父级菜单只有[目录]不可用 -->
<a-form-model-item label="父级菜单" prop="pid" v-if="form.type != 0">
<a-tree-select
:dropdown-style="{ maxHeight: '300px', overflow: 'auto' }"
:tree-data="parentTreeData"
placeholder="请选择父级菜单"
v-model="form.pid"
/>
</a-form-model-item>
</div>
<h3>扩展信息</h3>
<div class="yo-form-group">
<a-form-model-item label="打开方式" prop="openType" v-if="form.type == 1">
<a-radio-group @change="onOpenTypeChange" v-model="form.openType">
<a-radio-button
:key="type.code"
:value="type.code"
v-for="type in codes.openType"
>{{type.value}}</a-radio-button>
</a-radio-group>
</a-form-model-item>
<!-- 前端组件只有[菜单][组件]可用 -->
<a-form-model-item
label="前端组件"
prop="component"
v-if="form.type == 1 && form.openType == 1"
v-show="form.type == 1 && form.openType == 1"
>
<a-input placeholder="请输入前端组件" v-model="form.component" />
</a-form-model-item>
<!-- 内链地址只有[菜单][内链]可用 -->
<a-form-model-item label="内链地址" prop="router" v-if="form.type == 1 && form.openType == 2">
<a-input placeholder="请输入内链地址" v-model="form.router" />
</a-form-model-item>
<!-- 外链地址只有[菜单][外链]可用 -->
<a-form-model-item label="内外链地址" prop="link" v-if="form.type == 1 && form.openType == 3">
<a-input placeholder="请输入内外链地址" v-model="form.link" />
</a-form-model-item>
<a-form-model-item label="权重" prop="weight">
<template slot="help">
系统权重菜单可分配给任何角色
<br />业务权重菜单对超级管理员不可见
</template>
<a-radio-group v-model="form.weight">
<a-radio-button
:key="type.code"
:value="type.code"
v-for="type in codes.menuWerght"
>{{type.value}}</a-radio-button>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="可见性">
<a-switch checked-children="可见" un-checked-children="隐藏" v-model="form.visible" />
</a-form-model-item>
<!-- 图标只有[按钮]不可用 -->
<a-form-model-item label="图标" v-if="form.type != 2"></a-form-model-item>
<a-form-model-item label="排序">
<a-input-number
:max="1000"
:min="0"
class="w-100-p"
placeholder="请输入排序"
v-model="form.sort"
/>
</a-form-model-item>
<a-form-model-item label="备注信息">
<a-textarea placeholder="请输入备注信息" v-model="form.remark" />
</a-form-model-item>
</div>
</div>
</a-spin>
</a-form-model>
</template>
<script>
export default {
data() {
return {
/** 表单数据 */
form: {
type: '1',
openType: '1',
weight: '2',
visible: true,
sort: 100,
},
/** 验证格式 */
rules: {
type: [{ required: true, message: '请选择菜单类型' }],
name: [{ required: true, message: '请输入名称' }],
code: [{ required: true, message: '请输入唯一编码' }],
application: [{ required: true, message: '请选择所属应用' }],
pid: [{ required: true, message: '请选择父级' }],
component: [{ required: true, message: '请输入前端组件' }],
router: [{ required: true, message: '请输入内链地址' }],
link: [{ required: true, message: '请输入外链地址' }],
},
/** 加载异步数据状态 */
loading: false,
/** 其他成员属性 */
/** ... */
appList: [],
codes: {
menuType: [],
menuWerght: [],
openType: [],
},
parentTreeData: [],
};
},
methods: {
/**
* 必要的方法
* 在打开编辑页时允许填充数据
*/
onFillData(record) {
/** 将默认数据覆盖到form */
this.form = this.$_.cloneDeep({
...record,
/** 在此处添加默认数据转换 */
/** ... */
visible: record.visible == 'Y',
});
},
/**
* 必要方法
* 验证表单并获取表单数据
*/
onGetData() {
return new Promise((reslove, reject) => {
this.$refs.form.validate((valid) => {
if (valid) {
const record = this.$_.cloneDeep(this.form);
/** 验证通过后可以对数据进行转换得到想要提交的格式 */
/** ... */
record.visible = record.visible ? 'Y' : 'N';
reslove(record);
} else {
reject();
}
});
});
},
/**
* 必要的方法
* 在外部窗口进行保存时调用表单验证
*/
onValidate(callback) {
this.$refs.form.validate(callback);
},
/**
* 必要的方法
* 在外部窗口关闭或重置时对表单验证进行初始化
*/
onResetFields() {
setTimeout(() => {
this.$refs.form.resetFields();
/** 在这里可以初始化当前组件中其他属性 */
/** ... */
this.parentTreeData = [];
}, 300);
},
/**
* 必要方法
* 加载当前表单中所需要的异步数据
*/
async onInit() {
this.loading = true;
/** 可以在这里await获取一些异步数据 */
/** ...BEGIN */
this.codes = await this.onLoadCodes();
this.appList = await this.onLoadSysApplist();
/** ...END */
this.loading = false;
},
/** 当前组件的其他方法 */
/** ... */
onLoadCodes() {
return this.$api
.$queue([
this.$api.sysDictTypeDropDownWait({ code: 'menu_type' }),
this.$api.sysDictTypeDropDownWait({ code: 'menu_weight' }),
this.$api.sysDictTypeDropDownWait({ code: 'open_type' }),
])
.then(([menuType, menuWerght, openType]) => {
return {
menuType: menuType.data,
menuWerght: menuWerght.data,
openType: openType.data,
};
});
},
onLoadSysApplist() {
return this.$api.getAppList().then(({ data }) => {
return data;
});
},
onTypeChangeGroup() {
const { type, openType } = this.form;
if (type == 1 && openType == 2) {
this.$set(this.form, 'component', 'iframe');
} else {
this.$set(this.form, 'component', '');
}
},
onTypeChange() {
this.onTypeChangeGroup();
if (this.form.type == 0 || this.form.type == 2) {
this.form.openType = '0';
} else {
this.form.openType = '1';
}
},
onOpenTypeChange() {
this.onTypeChangeGroup();
},
onChangeApplication(value) {
this.$api.getMenuTree({ application: value }).then(({ data }) => {
this.parentTreeData = [
{
id: -1,
parentId: 0,
title: '顶级',
value: 0,
pid: 0,
children: data,
},
];
});
},
},
};
</script>

View File

@@ -0,0 +1,194 @@
<template>
<container>
<br />
<a-card :bordered="false">
<yo-table :columns="columns" :load-data="loadData" ref="table">
<Auth auth="sysMenu:add" slot="operator">
<a-button @click="onOpen('add-form')" icon="plus">新增菜单</a-button>
</Auth>
<span slot="type" slot-scope="text">{{ bindCodeValue(text, 'menu_type') }}</span>
<span slot="icon" slot-scope="text">
<div v-if="text">
<a-icon :type="text" />
</div>
</span>
<span slot="action" slot-scope="text, record">
<yo-table-actions>
<Auth auth="sysMenu:edit">
<a @click="onOpen('edit-form', record)">编辑</a>
</Auth>
<Auth auth="sysMenu:delete">
<a-popconfirm @confirm="onDelete(record)" placement="topRight" title="是否确认删除">
<a>删除</a>
</a-popconfirm>
</Auth>
</yo-table-actions>
</span>
</yo-table>
</a-card>
<br />
<add-form @ok="onReloadData" ref="add-form" />
<edit-form @ok="onReloadData" ref="edit-form" />
</container>
</template>
<script>
import AddForm from './addForm';
import EditForm from './editForm';
export default {
components: {
AddForm,
EditForm,
},
data() {
return {
query: {},
columns: [
{
title: '菜单名称',
width: '220px',
dataIndex: 'name',
},
{
title: '菜单类型',
width: '100px',
dataIndex: 'type',
scopedSlots: { customRender: 'type' },
},
{
title: '图标',
width: '100px',
dataIndex: 'icon',
scopedSlots: { customRender: 'icon' },
},
{
title: '前端组件',
width: '220px',
dataIndex: 'component',
ellipsis: true,
},
{
title: '权限标识',
width: '220px',
dataIndex: 'permission',
ellipsis: true,
},
{
title: '排序',
width: '100px',
dataIndex: 'sort',
},
],
codes: [
{
code: 'menu_type',
values: [],
},
{
code: 'menu_weight',
values: [],
},
{
code: 'open_type',
values: [],
},
],
};
},
created() {
this.onLoadCodes();
const flag = this.$auth({
sysApp: [['edit'], ['delete']],
});
if (flag) {
this.columns.push({
title: '操作',
width: '150px',
dataIndex: 'action',
scopedSlots: { customRender: 'action' },
});
}
},
methods: {
/**
* 必要的方法
* 传给yo-table以示意数据接口及其参数和返回的数据结构
*/
loadData(params) {
return this.$api
.getMenuList({
...params,
...this.query,
})
.then((res) => {
return res.data;
});
},
/**
* 有查询功能时的必要方法
* 加载数据时初始化分页信息
*/
onQuery() {
this.$refs.table.onReloadData(true);
},
/**
* 必要方法
* 重新列表数据
*/
onReloadData() {
this.$refs.table.onReloadData();
},
/**
* 加载字典数据时的必要方法
*/
onLoadCodes() {
this.$api
.$queue([
this.$api.sysDictTypeDropDownWait({ code: 'menu_type' }),
this.$api.sysDictTypeDropDownWait({ code: 'menu_weight' }),
this.$api.sysDictTypeDropDownWait({ code: 'open_type' }),
])
.then(([menuType, menuWerght, openType]) => {
this.codes.find((p) => p.code === 'menu_type').values = menuType.data;
this.codes.find((p) => p.code === 'menu_weight').values = menuWerght.data;
this.codes.find((p) => p.code === 'open_type').values = openType.data;
});
},
bindCodeValue(code, name) {
const c = this.codes.find((p) => p.code == name).values.find((p) => p.code == code);
if (c) {
return c.value;
}
return null;
},
/**
* 有编辑新增功能的必要方法
* 从列表页调用窗口的打开方法
*/
onOpen(formName, record) {
this.$refs[formName].onOpen(record);
},
onResult(success, successMessage) {
if (success) {
this.$message.success(successMessage);
this.onReloadData();
}
this.$refs.table.onLoaded();
},
onDelete(record) {
this.$refs.table.onLoading();
this.$api.sysMenuDelete(record).then(({ success }) => {
this.onResult(success, '删除成功');
});
},
},
};
</script>

View File

@@ -0,0 +1,296 @@
@-webkit-keyframes noise-anim {
0% {
clip: rect(32px, 9999px, 16px, 0);
}
5% {
clip: rect(5px, 9999px, 24px, 0);
}
10% {
clip: rect(77px, 9999px, 87px, 0);
}
15% {
clip: rect(91px, 9999px, 95px, 0);
}
20% {
clip: rect(74px, 9999px, 9px, 0);
}
25% {
clip: rect(37px, 9999px, 32px, 0);
}
30% {
clip: rect(56px, 9999px, 27px, 0);
}
35% {
clip: rect(35px, 9999px, 33px, 0);
}
40% {
clip: rect(89px, 9999px, 6px, 0);
}
45% {
clip: rect(81px, 9999px, 77px, 0);
}
50% {
clip: rect(64px, 9999px, 69px, 0);
}
55% {
clip: rect(12px, 9999px, 11px, 0);
}
60% {
clip: rect(59px, 9999px, 11px, 0);
}
65% {
clip: rect(69px, 9999px, 59px, 0);
}
70% {
clip: rect(74px, 9999px, 65px, 0);
}
75% {
clip: rect(56px, 9999px, 79px, 0);
}
80% {
clip: rect(80px, 9999px, 64px, 0);
}
85% {
clip: rect(87px, 9999px, 29px, 0);
}
90% {
clip: rect(16px, 9999px, 21px, 0);
}
95% {
clip: rect(69px, 9999px, 43px, 0);
}
100% {
clip: rect(75px, 9999px, 63px, 0);
}
}
@keyframes noise-anim {
0% {
clip: rect(32px, 9999px, 16px, 0);
}
5% {
clip: rect(5px, 9999px, 24px, 0);
}
10% {
clip: rect(77px, 9999px, 87px, 0);
}
15% {
clip: rect(91px, 9999px, 95px, 0);
}
20% {
clip: rect(74px, 9999px, 9px, 0);
}
25% {
clip: rect(37px, 9999px, 32px, 0);
}
30% {
clip: rect(56px, 9999px, 27px, 0);
}
35% {
clip: rect(35px, 9999px, 33px, 0);
}
40% {
clip: rect(89px, 9999px, 6px, 0);
}
45% {
clip: rect(81px, 9999px, 77px, 0);
}
50% {
clip: rect(64px, 9999px, 69px, 0);
}
55% {
clip: rect(12px, 9999px, 11px, 0);
}
60% {
clip: rect(59px, 9999px, 11px, 0);
}
65% {
clip: rect(69px, 9999px, 59px, 0);
}
70% {
clip: rect(74px, 9999px, 65px, 0);
}
75% {
clip: rect(56px, 9999px, 79px, 0);
}
80% {
clip: rect(80px, 9999px, 64px, 0);
}
85% {
clip: rect(87px, 9999px, 29px, 0);
}
90% {
clip: rect(16px, 9999px, 21px, 0);
}
95% {
clip: rect(69px, 9999px, 43px, 0);
}
100% {
clip: rect(75px, 9999px, 63px, 0);
}
}
@-webkit-keyframes noise-anim-2 {
0% {
clip: rect(12px, 9999px, 52px, 0);
}
5% {
clip: rect(42px, 9999px, 39px, 0);
}
10% {
clip: rect(64px, 9999px, 36px, 0);
}
15% {
clip: rect(52px, 9999px, 15px, 0);
}
20% {
clip: rect(79px, 9999px, 7px, 0);
}
25% {
clip: rect(17px, 9999px, 41px, 0);
}
30% {
clip: rect(15px, 9999px, 20px, 0);
}
35% {
clip: rect(62px, 9999px, 87px, 0);
}
40% {
clip: rect(94px, 9999px, 11px, 0);
}
45% {
clip: rect(49px, 9999px, 10px, 0);
}
50% {
clip: rect(82px, 9999px, 4px, 0);
}
55% {
clip: rect(70px, 9999px, 100px, 0);
}
60% {
clip: rect(62px, 9999px, 23px, 0);
}
65% {
clip: rect(51px, 9999px, 56px, 0);
}
70% {
clip: rect(41px, 9999px, 24px, 0);
}
75% {
clip: rect(6px, 9999px, 85px, 0);
}
80% {
clip: rect(96px, 9999px, 58px, 0);
}
85% {
clip: rect(16px, 9999px, 24px, 0);
}
90% {
clip: rect(40px, 9999px, 31px, 0);
}
95% {
clip: rect(91px, 9999px, 34px, 0);
}
100% {
clip: rect(87px, 9999px, 26px, 0);
}
}
@keyframes noise-anim-2 {
0% {
clip: rect(12px, 9999px, 52px, 0);
}
5% {
clip: rect(42px, 9999px, 39px, 0);
}
10% {
clip: rect(64px, 9999px, 36px, 0);
}
15% {
clip: rect(52px, 9999px, 15px, 0);
}
20% {
clip: rect(79px, 9999px, 7px, 0);
}
25% {
clip: rect(17px, 9999px, 41px, 0);
}
30% {
clip: rect(15px, 9999px, 20px, 0);
}
35% {
clip: rect(62px, 9999px, 87px, 0);
}
40% {
clip: rect(94px, 9999px, 11px, 0);
}
45% {
clip: rect(49px, 9999px, 10px, 0);
}
50% {
clip: rect(82px, 9999px, 4px, 0);
}
55% {
clip: rect(70px, 9999px, 100px, 0);
}
60% {
clip: rect(62px, 9999px, 23px, 0);
}
65% {
clip: rect(51px, 9999px, 56px, 0);
}
70% {
clip: rect(41px, 9999px, 24px, 0);
}
75% {
clip: rect(6px, 9999px, 85px, 0);
}
80% {
clip: rect(96px, 9999px, 58px, 0);
}
85% {
clip: rect(16px, 9999px, 24px, 0);
}
90% {
clip: rect(40px, 9999px, 31px, 0);
}
95% {
clip: rect(91px, 9999px, 34px, 0);
}
100% {
clip: rect(87px, 9999px, 26px, 0);
}
}
.error-result {
padding: 100px;
text-transform: uppercase;
}
.error-result--text {
font-size: 7rem;
line-height: 1;
position: relative;
display: block;
width: 12.5rem;
color: #5a5c69;
}
.error-result--text::after {
position: absolute;
top: 0;
left: 2px;
overflow: hidden;
clip: rect(0, 900px, 0, 0);
content: attr(data-text);
animation: noise-anim 2s infinite linear alternate-reverse;
color: #5a5c69;
background: #f8f9fc;
text-shadow: -1px 0 #e74a3b;
}
.error-result--text::before {
position: absolute;
top: 0;
left: -2px;
overflow: hidden;
clip: rect(0, 900px, 0, 0);
content: attr(data-text);
animation: noise-anim-2 3s infinite linear alternate-reverse;
color: #5a5c69;
background: #f8f9fc;
text-shadow: 1px 0 #4e73df;
}

View File

@@ -0,0 +1,308 @@
@import (reference) '~ant-design-vue/dist/antd.less';
@-webkit-keyframes noise-anim-after {
0% {
clip: rect(32px, 9999px, 16px, 0);
}
5% {
clip: rect(5px, 9999px, 24px, 0);
}
10% {
clip: rect(77px, 9999px, 87px, 0);
}
15% {
clip: rect(91px, 9999px, 95px, 0);
}
20% {
clip: rect(74px, 9999px, 9px, 0);
}
25% {
clip: rect(37px, 9999px, 32px, 0);
}
30% {
clip: rect(56px, 9999px, 27px, 0);
}
35% {
clip: rect(35px, 9999px, 33px, 0);
}
40% {
clip: rect(89px, 9999px, 6px, 0);
}
45% {
clip: rect(81px, 9999px, 77px, 0);
}
50% {
clip: rect(64px, 9999px, 69px, 0);
}
55% {
clip: rect(12px, 9999px, 11px, 0);
}
60% {
clip: rect(59px, 9999px, 11px, 0);
}
65% {
clip: rect(69px, 9999px, 59px, 0);
}
70% {
clip: rect(74px, 9999px, 65px, 0);
}
75% {
clip: rect(56px, 9999px, 79px, 0);
}
80% {
clip: rect(80px, 9999px, 64px, 0);
}
85% {
clip: rect(87px, 9999px, 29px, 0);
}
90% {
clip: rect(16px, 9999px, 21px, 0);
}
95% {
clip: rect(69px, 9999px, 43px, 0);
}
100% {
clip: rect(75px, 9999px, 63px, 0);
}
}
@keyframes noise-anim-after {
0% {
clip: rect(32px, 9999px, 16px, 0);
}
5% {
clip: rect(5px, 9999px, 24px, 0);
}
10% {
clip: rect(77px, 9999px, 87px, 0);
}
15% {
clip: rect(91px, 9999px, 95px, 0);
}
20% {
clip: rect(74px, 9999px, 9px, 0);
}
25% {
clip: rect(37px, 9999px, 32px, 0);
}
30% {
clip: rect(56px, 9999px, 27px, 0);
}
35% {
clip: rect(35px, 9999px, 33px, 0);
}
40% {
clip: rect(89px, 9999px, 6px, 0);
}
45% {
clip: rect(81px, 9999px, 77px, 0);
}
50% {
clip: rect(64px, 9999px, 69px, 0);
}
55% {
clip: rect(12px, 9999px, 11px, 0);
}
60% {
clip: rect(59px, 9999px, 11px, 0);
}
65% {
clip: rect(69px, 9999px, 59px, 0);
}
70% {
clip: rect(74px, 9999px, 65px, 0);
}
75% {
clip: rect(56px, 9999px, 79px, 0);
}
80% {
clip: rect(80px, 9999px, 64px, 0);
}
85% {
clip: rect(87px, 9999px, 29px, 0);
}
90% {
clip: rect(16px, 9999px, 21px, 0);
}
95% {
clip: rect(69px, 9999px, 43px, 0);
}
100% {
clip: rect(75px, 9999px, 63px, 0);
}
}
@-webkit-keyframes noise-anim-before {
0% {
clip: rect(12px, 9999px, 52px, 0);
}
5% {
clip: rect(42px, 9999px, 39px, 0);
}
10% {
clip: rect(64px, 9999px, 36px, 0);
}
15% {
clip: rect(52px, 9999px, 15px, 0);
}
20% {
clip: rect(79px, 9999px, 7px, 0);
}
25% {
clip: rect(17px, 9999px, 41px, 0);
}
30% {
clip: rect(15px, 9999px, 20px, 0);
}
35% {
clip: rect(62px, 9999px, 87px, 0);
}
40% {
clip: rect(94px, 9999px, 11px, 0);
}
45% {
clip: rect(49px, 9999px, 10px, 0);
}
50% {
clip: rect(82px, 9999px, 4px, 0);
}
55% {
clip: rect(70px, 9999px, 100px, 0);
}
60% {
clip: rect(62px, 9999px, 23px, 0);
}
65% {
clip: rect(51px, 9999px, 56px, 0);
}
70% {
clip: rect(41px, 9999px, 24px, 0);
}
75% {
clip: rect(6px, 9999px, 85px, 0);
}
80% {
clip: rect(96px, 9999px, 58px, 0);
}
85% {
clip: rect(16px, 9999px, 24px, 0);
}
90% {
clip: rect(40px, 9999px, 31px, 0);
}
95% {
clip: rect(91px, 9999px, 34px, 0);
}
100% {
clip: rect(87px, 9999px, 26px, 0);
}
}
@keyframes noise-anim-before {
0% {
clip: rect(12px, 9999px, 52px, 0);
}
5% {
clip: rect(42px, 9999px, 39px, 0);
}
10% {
clip: rect(64px, 9999px, 36px, 0);
}
15% {
clip: rect(52px, 9999px, 15px, 0);
}
20% {
clip: rect(79px, 9999px, 7px, 0);
}
25% {
clip: rect(17px, 9999px, 41px, 0);
}
30% {
clip: rect(15px, 9999px, 20px, 0);
}
35% {
clip: rect(62px, 9999px, 87px, 0);
}
40% {
clip: rect(94px, 9999px, 11px, 0);
}
45% {
clip: rect(49px, 9999px, 10px, 0);
}
50% {
clip: rect(82px, 9999px, 4px, 0);
}
55% {
clip: rect(70px, 9999px, 100px, 0);
}
60% {
clip: rect(62px, 9999px, 23px, 0);
}
65% {
clip: rect(51px, 9999px, 56px, 0);
}
70% {
clip: rect(41px, 9999px, 24px, 0);
}
75% {
clip: rect(6px, 9999px, 85px, 0);
}
80% {
clip: rect(96px, 9999px, 58px, 0);
}
85% {
clip: rect(16px, 9999px, 24px, 0);
}
90% {
clip: rect(40px, 9999px, 31px, 0);
}
95% {
clip: rect(91px, 9999px, 34px, 0);
}
100% {
clip: rect(87px, 9999px, 26px, 0);
}
}
.error-result {
padding: 100px;
text-transform: uppercase;
&--text {
font-size: @font-size-base * 8;
line-height: 1;
position: relative;
display: block;
width: 12.5rem;
color: #5a5c69;
&::after {
position: absolute;
top: 0;
left: 2px;
overflow: hidden;
clip: rect(0, 900px, 0, 0);
content: attr(data-text);
animation: noise-anim-after 2s infinite linear alternate-reverse;
color: #5a5c69;
background: @layout-body-background;
text-shadow: -1px 0 #e74a3b;
}
&::before {
position: absolute;
top: 0;
left: -2px;
overflow: hidden;
clip: rect(0, 900px, 0, 0);
content: attr(data-text);
animation: noise-anim-before 3s infinite linear alternate-reverse;
color: #5a5c69;
background: @layout-body-background;
text-shadow: 1px 0 #4e73df;
}
}
}

View File

@@ -0,0 +1,14 @@
<template>
<container>
<div class="error-result">
<div class="error-result--code">
<span class="error-result--text" data-text="404">404</span>
</div>
<p>not found</p>
</div>
</container>
</template>
<style lang="less" scope>
@import './index.less';
</style>

View File

@@ -74,7 +74,7 @@ export default {
pane.loaded = true;
NProgress.done();
}).catch(() => {
pane.component = () => import('@/views/404');
pane.component = () => import('@/views/error/404');
pane.loaded = true;
NProgress.done();
});

View File

@@ -35,6 +35,8 @@
import Logo from '../logo';
import Menu from './menu';
let swiper;
export default {
components: {
Logo,
@@ -65,16 +67,13 @@ export default {
},
};
},
computed: {
swiper() {
return this.$refs['sider-swiper'].$swiper;
},
},
mounted() {
swiper = this.$refs['sider-swiper'].$swiper;
window.addEventListener('resize', () => {
if (this.$root.global.settings.layout === 'left-menu' || this.$root.global.settings.layout === 'right-menu') {
setTimeout(() => {
this.swiper.update();
swiper.update();
}, 300);
}
});