first commit
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<el-config-provider :locale="locale" :size="config.size" :zIndex="config.zIndex" :button="config.button">
|
||||
<router-view></router-view>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import colorTool from '@/utils/color'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
data() {
|
||||
return {
|
||||
config: {
|
||||
size: "small",
|
||||
zIndex: 2000,
|
||||
button: {
|
||||
autoInsertSpace: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
locale(){
|
||||
return this.$i18n.messages[this.$i18n.locale].el
|
||||
},
|
||||
},
|
||||
created() {
|
||||
//设置主题颜色
|
||||
const app_color = this.$CONFIG.COLOR || this.$TOOL.data.get('APP_COLOR')
|
||||
if(app_color){
|
||||
document.documentElement.style.setProperty('--el-color-primary', app_color);
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, colorTool.lighten(app_color,i/10));
|
||||
}
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, colorTool.darken(app_color,i/10));
|
||||
}
|
||||
}
|
||||
const debounce = (fn, delay) => {
|
||||
let timer = null;
|
||||
return function () {
|
||||
let context = this;
|
||||
let args = arguments;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
fn.apply(context, args);
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
const _ResizeObserver = window.ResizeObserver;
|
||||
window.ResizeObserver = class ResizeObserver extends _ResizeObserver{
|
||||
constructor(callback) {
|
||||
callback = debounce(callback, 16);
|
||||
super(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/assets/style/style.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @description 自动import导入所有 api 模块
|
||||
*/
|
||||
|
||||
const files = import.meta.glob('./module/*.js', { eager: true })
|
||||
const modules = {}
|
||||
Object.keys(files).forEach(key => {
|
||||
modules[key.replace(/^\.\/module\/(.*)\.js$/g, '$1')] = files[key].default
|
||||
})
|
||||
|
||||
export default modules
|
||||
@@ -0,0 +1,153 @@
|
||||
import config from "@/config"
|
||||
import http from "@/utils/request"
|
||||
|
||||
export default {
|
||||
family: {
|
||||
list: {
|
||||
url: `${config.API_URL}account/families/index`,
|
||||
name: "家庭列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
url: `${config.API_URL}account/families/detail`,
|
||||
name: "家庭详情",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}account/families/add`,
|
||||
name: "家庭添加",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}account/families/edit`,
|
||||
name: "家庭编辑",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}account/families/delete`,
|
||||
name: "家庭删除",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
accounts: {
|
||||
list: {
|
||||
url: `${config.API_URL}account/accounts/index`,
|
||||
name: "账户列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
url: `${config.API_URL}account/accounts/detail`,
|
||||
name: "账户详情",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}account/accounts/add`,
|
||||
name: "账户添加",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}account/accounts/edit`,
|
||||
name: "账户编辑",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}account/accounts/delete`,
|
||||
name: "账户删除",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
members: {
|
||||
list: {
|
||||
url: `${config.API_URL}account/members/index`,
|
||||
name: "成员列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
url: `${config.API_URL}account/members/detail`,
|
||||
name: "成员详情",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}account/members/add`,
|
||||
name: "成员添加",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}account/members/edit`,
|
||||
name: "成员编辑",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}account/members/delete`,
|
||||
name: "成员删除",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
records: {
|
||||
list: {
|
||||
url: `${config.API_URL}account/records/index`,
|
||||
name: "记录列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
url: `${config.API_URL}account/records/detail`,
|
||||
name: "记录详情",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}account/records/add`,
|
||||
name: "记录添加",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}account/records/edit`,
|
||||
name: "记录编辑",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}account/records/delete`,
|
||||
name: "记录删除",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import config from "@/config";
|
||||
import http from "@/utils/request";
|
||||
|
||||
export default {
|
||||
list: {
|
||||
url: `${config.API_URL}ads/index`,
|
||||
name: "获得广告列表",
|
||||
get: async function (params) {
|
||||
return await http.get(this.url, params);
|
||||
},
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}ads/add`,
|
||||
name: "添加广告",
|
||||
post: async function (params) {
|
||||
return await http.post(this.url, params);
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}ads/edit`,
|
||||
name: "编辑广告",
|
||||
post: async function (params) {
|
||||
return await http.put(this.url, params);
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}ads/delete`,
|
||||
name: "删除广告",
|
||||
post: async function (params) {
|
||||
return await http.delete(this.url, params);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,174 @@
|
||||
import config from "@/config"
|
||||
import http from "@/utils/request"
|
||||
|
||||
export default {
|
||||
login: {
|
||||
url: `${config.API_URL}auth/login`,
|
||||
name: "登录获取TOKEN",
|
||||
post: async function(data={}){
|
||||
return await http.post(this.url, data);
|
||||
}
|
||||
},
|
||||
logout:{
|
||||
url: `${config.API_URL}auth/logout`,
|
||||
name: "登出",
|
||||
get: async function(data={}){
|
||||
return await http.get(this.url, data);
|
||||
}
|
||||
},
|
||||
user: {
|
||||
url: `${config.API_URL}auth/user`,
|
||||
name: "获取当前登录用户",
|
||||
get: async function(data={}){
|
||||
return await http.get(this.url, data);
|
||||
}
|
||||
},
|
||||
users: {
|
||||
list: {
|
||||
url: `${config.API_URL}auth/users/index`,
|
||||
name: "获得用户列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}auth/users/add`,
|
||||
name: "添加用户",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}auth/users/edit`,
|
||||
name: "编辑用户",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
uppasswd:{
|
||||
url: `${config.API_URL}auth/users/passwd`,
|
||||
name: "修改密码",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
uprole: {
|
||||
url: `${config.API_URL}auth/users/uprole`,
|
||||
name: "设置角色",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}auth/users/delete`,
|
||||
name: "删除用户",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
role: {
|
||||
list: {
|
||||
url: `${config.API_URL}auth/role/index`,
|
||||
name: "获得角色列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}auth/role/add`,
|
||||
name: "添加角色",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}auth/role/edit`,
|
||||
name: "编辑角色",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
auth: {
|
||||
url: `${config.API_URL}auth/role/auth`,
|
||||
name: "角色授权",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}auth/role/delete`,
|
||||
name: "删除角色",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
department: {
|
||||
list: {
|
||||
url: `${config.API_URL}auth/department/index`,
|
||||
name: "获得部门列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}auth/department/add`,
|
||||
name: "添加部门",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}auth/department/edit`,
|
||||
name: "编辑部门",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}auth/department/delete`,
|
||||
name: "删除部门",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
menu: {
|
||||
myMenus: {
|
||||
url: `${config.API_URL}auth/menu/my`,
|
||||
name: "获取我的菜单",
|
||||
get: async function(){
|
||||
return await http.get(this.url);
|
||||
}
|
||||
},
|
||||
list: {
|
||||
url: `${config.API_URL}auth/menu/index`,
|
||||
name: "获取菜单",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}auth/menu/add`,
|
||||
name: "添加菜单",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}auth/menu/edit`,
|
||||
name: "编辑菜单",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}auth/menu/delete`,
|
||||
name: "删除菜单",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import config from "@/config"
|
||||
import http from "@/utils/request"
|
||||
|
||||
export default {
|
||||
upload: {
|
||||
url: `${config.API_URL}system/file/upload`,
|
||||
name: "文件上传",
|
||||
post: async function(data, config={}){
|
||||
return await http.post(this.url, data, config);
|
||||
}
|
||||
},
|
||||
ckeditor: `${config.API_URL}system/file/ckeditor`,
|
||||
file: {
|
||||
menu: {
|
||||
url: `${config.API_URL}system/file/menu`,
|
||||
name: "获取文件分类",
|
||||
get: async function(){
|
||||
return await http.get(this.url);
|
||||
}
|
||||
},
|
||||
list: {
|
||||
url: `${config.API_URL}system/file/list`,
|
||||
name: "获取文件列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import config from "@/config";
|
||||
import http from "@/utils/request";
|
||||
|
||||
export default {
|
||||
lists: {
|
||||
list: {
|
||||
url: `${config.API_URL}member/index/index`,
|
||||
name: "获得会员列表",
|
||||
get: async function (params) {
|
||||
return await http.get(this.url, params);
|
||||
},
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}member/index/add`,
|
||||
name: "添加会员",
|
||||
post: async function (params) {
|
||||
return await http.post(this.url, params);
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}member/index/edit`,
|
||||
name: "编辑会员",
|
||||
post: async function (params) {
|
||||
return await http.put(this.url, params);
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}member/index/delete`,
|
||||
name: "删除会员",
|
||||
post: async function (params) {
|
||||
return await http.delete(this.url, params);
|
||||
},
|
||||
},
|
||||
import: {
|
||||
url: `${config.API_URL}member/index/import`,
|
||||
name: "导入会员",
|
||||
post: async function (params) {
|
||||
return await http.post(this.url, params);
|
||||
},
|
||||
},
|
||||
export: {
|
||||
url: `${config.API_URL}member/index/export`,
|
||||
name: "导出会员",
|
||||
get: async function (params) {
|
||||
return await http.get(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
level: {
|
||||
list: {
|
||||
url: `${config.API_URL}member/level/index`,
|
||||
name: "获得会员等级列表",
|
||||
get: async function (params) {
|
||||
return await http.get(this.url, params);
|
||||
},
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}member/level/add`,
|
||||
name: "添加会员等级",
|
||||
post: async function (params) {
|
||||
return await http.post(this.url, params);
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}member/level/edit`,
|
||||
name: "编辑会员等级",
|
||||
post: async function (params) {
|
||||
return await http.put(this.url, params);
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}member/level/delete`,
|
||||
name: "删除会员等级",
|
||||
post: async function (params) {
|
||||
return await http.delete(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
field: {
|
||||
list: {
|
||||
url: `${config.API_URL}member/field/index`,
|
||||
name: "获得会员字段列表",
|
||||
get: async function (params) {
|
||||
return await http.get(this.url, params);
|
||||
},
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}member/field/add`,
|
||||
name: "添加会员字段",
|
||||
post: async function (params) {
|
||||
return await http.post(this.url, params);
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}member/field/edit`,
|
||||
name: "编辑会员字段",
|
||||
post: async function (params) {
|
||||
return await http.put(this.url, params);
|
||||
},
|
||||
},
|
||||
field: {
|
||||
url: `${config.API_URL}member/field/fields`,
|
||||
name: "获得会员字段",
|
||||
get: async function (params) {
|
||||
return await http.get(this.url, params);
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}member/field/delete`,
|
||||
name: "删除会员字段",
|
||||
post: async function (params) {
|
||||
return await http.delete(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
list: {
|
||||
url: `${config.API_URL}member/extend/index`,
|
||||
name: "获得会员扩展列表",
|
||||
get: async function (params) {
|
||||
return await http.get(this.url, params);
|
||||
},
|
||||
},
|
||||
audit: {
|
||||
url: `${config.API_URL}member/extend/audit`,
|
||||
name: "申请审核",
|
||||
post: async function (params) {
|
||||
return await http.put(this.url, params);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,342 @@
|
||||
import config from "@/config"
|
||||
import http from "@/utils/request"
|
||||
|
||||
export default {
|
||||
version:{
|
||||
url: `${config.API_URL}system/index/version`,
|
||||
name: "获取最新版本号",
|
||||
get: async function(){
|
||||
return await http.get(this.url);
|
||||
}
|
||||
},
|
||||
clearcache: {
|
||||
url: `${config.API_URL}system/index/clearcache`,
|
||||
name: "清除缓存",
|
||||
post: async function(){
|
||||
return await http.post(this.url);
|
||||
}
|
||||
},
|
||||
info: {
|
||||
url: `${config.API_URL}system/index/info`,
|
||||
name: "系统信息",
|
||||
get: function(data){
|
||||
return http.get(this.url, data);
|
||||
}
|
||||
},
|
||||
setting:{
|
||||
list: {
|
||||
url: `${config.API_URL}system/setting/index`,
|
||||
name: "获取配置信息",
|
||||
get: function(params){
|
||||
return http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
fields: {
|
||||
url: `${config.API_URL}system/setting/fields`,
|
||||
name: "获取配置字段",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}system/setting/add`,
|
||||
name: "保存配置信息",
|
||||
post: function(data){
|
||||
return http.post(this.url, data);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}system/setting/edit`,
|
||||
name: "编辑配置信息",
|
||||
post: function(data){
|
||||
return http.put(this.url, data);
|
||||
}
|
||||
},
|
||||
save: {
|
||||
url: `${config.API_URL}system/setting/save`,
|
||||
name: "保存配置信息",
|
||||
post: function(data){
|
||||
return http.put(this.url, data);
|
||||
}
|
||||
}
|
||||
},
|
||||
dictionary: {
|
||||
category: {
|
||||
url: `${config.API_URL}system/dict/category`,
|
||||
name: "获取字典树",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
editcate:{
|
||||
url: `${config.API_URL}system/dict/editcate`,
|
||||
name: "编辑字典树",
|
||||
post: async function(data = {}){
|
||||
return await http.put(this.url, data);
|
||||
}
|
||||
},
|
||||
addcate:{
|
||||
url: `${config.API_URL}system/dict/addcate`,
|
||||
name: "添加字典树",
|
||||
post: async function(data = {}){
|
||||
return await http.post(this.url, data);
|
||||
}
|
||||
},
|
||||
delCate:{
|
||||
url: `${config.API_URL}system/dict/deletecate`,
|
||||
name: "删除字典树",
|
||||
post: async function(data = {}){
|
||||
return await http.delete(this.url, data);
|
||||
}
|
||||
},
|
||||
list: {
|
||||
url: `${config.API_URL}system/dict/lists`,
|
||||
name: "字典明细",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
get: {
|
||||
url: `${config.API_URL}system/dict/detail`,
|
||||
name: "获取字典数据",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
edit:{
|
||||
url: `${config.API_URL}system/dict/edit`,
|
||||
name: "编辑字典明细",
|
||||
post: async function(data = {}){
|
||||
return await http.put(this.url, data);
|
||||
}
|
||||
},
|
||||
add:{
|
||||
url: `${config.API_URL}system/dict/add`,
|
||||
name: "添加字典明细",
|
||||
post: async function(data = {}){
|
||||
return await http.post(this.url, data);
|
||||
}
|
||||
},
|
||||
delete:{
|
||||
url: `${config.API_URL}system/dict/delete`,
|
||||
name: "删除字典明细",
|
||||
post: async function(data = {}){
|
||||
return await http.delete(this.url, data);
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
url: `${config.API_URL}system/dict/detail`,
|
||||
name: "字典明细",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
alldic: {
|
||||
url: `${config.API_URL}system/dict/all`,
|
||||
name: "全部字典",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
area: {
|
||||
list: {
|
||||
url: `${config.API_URL}system/area/index`,
|
||||
name: "地区列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}system/area/add`,
|
||||
name: "地区添加",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}system/area/edit`,
|
||||
name: "地区编辑",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
},
|
||||
app: {
|
||||
list: {
|
||||
url: `${config.API_URL}system/app/list`,
|
||||
name: "应用列表",
|
||||
get: async function(){
|
||||
return await http.get(this.url);
|
||||
}
|
||||
}
|
||||
},
|
||||
client: {
|
||||
list: {
|
||||
url: `${config.API_URL}system/client/index`,
|
||||
name: "客户端列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}system/client/add`,
|
||||
name: "客户端添加",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}system/client/edit`,
|
||||
name: "客户端编辑",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}system/client/delete`,
|
||||
name: "客户端删除",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
},
|
||||
menu: {
|
||||
list: {
|
||||
url: `${config.API_URL}system/menu/index`,
|
||||
name: "客户端菜单列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}system/menu/add`,
|
||||
name: "客户端菜单添加",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}system/menu/edit`,
|
||||
name: "客户端菜单编辑",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}system/menu/delete`,
|
||||
name: "客户端菜单删除",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
log: {
|
||||
list: {
|
||||
url: `${config.API_URL}system/log/index`,
|
||||
name: "日志列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
my: {
|
||||
url: `${config.API_URL}system/log/my`,
|
||||
name: "我的日志",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}system/log/delete`,
|
||||
name: "日志删除",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
tasks: {
|
||||
list: {
|
||||
url: `${config.API_URL}system/tasks/index`,
|
||||
name: "任务列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}system/tasks/delete`,
|
||||
name: "任务删除",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
},
|
||||
},
|
||||
crontab: {
|
||||
list: {
|
||||
url: `${config.API_URL}system/crontab/index`,
|
||||
name: "定时任务列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
add: {
|
||||
url: `${config.API_URL}system/crontab/add`,
|
||||
name: "定时任务添加",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
url: `${config.API_URL}system/crontab/edit`,
|
||||
name: "定时任务编辑",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
},
|
||||
delete: {
|
||||
url: `${config.API_URL}system/crontab/delete`,
|
||||
name: "定时任务删除",
|
||||
post: async function(params){
|
||||
return await http.delete(this.url, params);
|
||||
}
|
||||
},
|
||||
log: {
|
||||
url: `${config.API_URL}system/crontab/log`,
|
||||
name: "定时任务日志",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
reload: {
|
||||
url: `${config.API_URL}system/crontab/reload`,
|
||||
name: "定时任务重载",
|
||||
post: async function(params){
|
||||
return await http.put(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
modules:{
|
||||
list: {
|
||||
url: `${config.API_URL}system/modules/index`,
|
||||
name: "模块列表",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
},
|
||||
update: {
|
||||
url: `${config.API_URL}system/modules/update`,
|
||||
name: "更新模块",
|
||||
post: async function(params){
|
||||
return await http.post(this.url, params);
|
||||
}
|
||||
}
|
||||
},
|
||||
sms: {
|
||||
count: {
|
||||
url: `${config.API_URL}system/sms/count`,
|
||||
name: "短信发送统计",
|
||||
get: async function(params){
|
||||
return await http.get(this.url, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M468.1 320.3V158.7c0.9-8.1-1.3-16.4-7.5-22.7-10.9-10.8-28.4-10.8-39.3 0L147.6 436.1c-5.8 5.7-8.3 13.4-7.9 20.9-0.4 7.5 2.1 15.1 7.9 20.9l272.2 298.4c10.2 8.7 28.8 13.6 40.8 1.6 6.2-6.2 8.9-11.4 8-19.5V594c180.8 0 344.4 130.2 376.8 301.6 21.9-50.2 34.1-105.6 34.1-163.9 0.1-227.2-184.1-411.4-411.4-411.4z" p-id="1450"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M258.389333 354.133333a299.093333 299.093333 0 0 1 8.490667-12.8h490.24c2.944 4.181333 5.76 8.490667 8.490667 12.8l86.186666-49.749333 42.666667 73.898667-94.421333 54.528c6.912 25.173333 10.624 51.754667 10.624 79.189333v42.666667h128v85.333333h-128a296.96 296.96 0 0 1-22.869334 114.773333l106.666667 61.610667-42.666667 73.898667-107.776-62.208A298.325333 298.325333 0 0 1 554.666667 935.637333V597.333333h-85.333334v338.346667a298.325333 298.325333 0 0 1-189.354666-107.605333l-107.776 62.208-42.666667-73.898667 106.666667-61.568A297.770667 297.770667 0 0 1 213.333333 640H85.333333v-85.333333h128v-42.666667c0-27.434667 3.712-53.973333 10.624-79.189333L129.536 378.282667l42.666667-73.898667L258.389333 354.133333zM341.333333 256a170.666667 170.666667 0 1 1 341.333334 0H341.333333z" p-id="26147"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M554.666667 849.066667a213.418667 213.418667 0 0 0 170.666666-209.066667v-128a212.48 212.48 0 0 0-17.706666-85.333333h-391.253334A212.48 212.48 0 0 0 298.666667 512v128a213.418667 213.418667 0 0 0 170.666666 209.066667V597.333333h85.333334v251.733334z m-318.464-94.293334A297.770667 297.770667 0 0 1 213.333333 640H85.333333v-85.333333h128v-42.666667c0-27.434667 3.712-53.973333 10.624-79.189333L129.536 378.282667l42.666667-73.898667L258.389333 354.133333a299.093333 299.093333 0 0 1 8.490667-12.8h490.24c2.944 4.181333 5.76 8.490667 8.490667 12.8l86.186666-49.749333 42.666667 73.898667-94.421333 54.528c6.912 25.173333 10.624 51.754667 10.624 79.189333v42.666667h128v85.333333h-128a296.96 296.96 0 0 1-22.869334 114.773333l106.666667 61.610667-42.666667 73.898667-107.776-62.208A298.069333 298.069333 0 0 1 512 938.666667a298.069333 298.069333 0 0 1-232.021333-110.592l-107.776 62.208-42.666667-73.898667 106.666667-61.568zM341.333333 256a170.666667 170.666667 0 1 1 341.333334 0H341.333333z" p-id="25873"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M981.333333 512l-301.696 301.696-60.330666-60.330667L860.672 512l-241.365333-241.365333 60.330666-60.330667L981.333333 512zM163.328 512l241.365333 241.365333-60.330666 60.330667L42.666667 512l301.696-301.696 60.330666 60.330667L163.328 512z" p-id="4503"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M554.666667 426.666667h213.333333l-256 256-256-256h213.333333V128h85.333334v298.666667z m-384 384h682.666666v-298.666667h85.333334v341.333333a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667v-341.333333h85.333334v298.666667z" p-id="26056"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M121.984 122.752l536.32-76.586667a21.333333 21.333333 0 0 1 24.362667 21.12v889.429334a21.333333 21.333333 0 0 1-24.32 21.12L121.941333 901.248a42.666667 42.666667 0 0 1-36.650666-42.24V164.992a42.666667 42.666667 0 0 1 36.650666-42.24zM725.333333 128h170.666667a42.666667 42.666667 0 0 1 42.666667 42.666667v682.666666a42.666667 42.666667 0 0 1-42.666667 42.666667h-170.666667V128z m-290.133333 384L554.666667 341.333333h-102.4L384 438.869333 315.733333 341.333333H213.333333l119.466667 170.666667L213.333333 682.666667h102.4L384 585.130667 452.266667 682.666667H554.666667l-119.466667-170.666667z" p-id="3794"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M725.333333 128h170.666667a42.666667 42.666667 0 0 1 42.666667 42.666667v682.666666a42.666667 42.666667 0 0 1-42.666667 42.666667h-170.666667V128zM121.984 122.752l536.32-76.586667a21.333333 21.333333 0 0 1 24.362667 21.12v889.429334a21.333333 21.333333 0 0 1-24.32 21.12L121.941333 901.248a42.666667 42.666667 0 0 1-36.650666-42.24V164.992a42.666667 42.666667 0 0 1 36.650666-42.24zM213.333333 341.333333v341.333334h85.333334v-85.333334h256V341.333333H213.333333z m85.333334 85.333334h170.666666v85.333333H298.666667v-85.333333z" p-id="3925"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M725.333333 128h170.666667a42.666667 42.666667 0 0 1 42.666667 42.666667v682.666666a42.666667 42.666667 0 0 1-42.666667 42.666667h-170.666667V128zM121.984 122.752l536.32-76.586667a21.333333 21.333333 0 0 1 24.362667 21.12v889.429334a21.333333 21.333333 0 0 1-24.32 21.12L121.941333 901.248a42.666667 42.666667 0 0 1-36.650666-42.24V164.992a42.666667 42.666667 0 0 1 36.650666-42.24zM469.333333 341.333333v212.864L384 469.333333l-84.906667 85.333334L298.666667 341.333333H213.333333v341.333334h85.333334l85.333333-85.333334 85.333333 85.333334h85.333334V341.333333h-85.333334z" p-id="3663"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M640 128a42.666667 42.666667 0 0 1 42.666667 42.666667v170.666666a42.666667 42.666667 0 0 1-42.666667 42.666667h-85.333333v85.333333h170.666666a42.666667 42.666667 0 0 1 42.666667 42.666667v128h85.333333a42.666667 42.666667 0 0 1 42.666667 42.666667v170.666666a42.666667 42.666667 0 0 1-42.666667 42.666667h-256a42.666667 42.666667 0 0 1-42.666666-42.666667v-170.666666a42.666667 42.666667 0 0 1 42.666666-42.666667h85.333334v-85.333333H341.333333v85.333333h85.333334a42.666667 42.666667 0 0 1 42.666666 42.666667v170.666666a42.666667 42.666667 0 0 1-42.666666 42.666667H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666667v-170.666666a42.666667 42.666667 0 0 1 42.666667-42.666667h85.333333v-128a42.666667 42.666667 0 0 1 42.666667-42.666667h170.666666V384H384a42.666667 42.666667 0 0 1-42.666667-42.666667V170.666667a42.666667 42.666667 0 0 1 42.666667-42.666667h256zM384 725.333333H213.333333v85.333334h170.666667v-85.333334z m426.666667 0h-170.666667v85.333334h170.666667v-85.333334zM597.333333 213.333333h-170.666666v85.333334h170.666666V213.333333z" p-id="51975"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M170.666667 810.666667h682.666666v-298.666667h85.333334v341.333333a42.666667 42.666667 0 0 1-42.666667 42.666667H128a42.666667 42.666667 0 0 1-42.666667-42.666667v-341.333333h85.333334v298.666667z m384-426.666667v298.666667h-85.333334V384H256l256-256 256 256h-213.333333z" p-id="25917"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M42.666667 128h170.666666l298.666667 512 298.666667-512h170.666666L512 938.666667 42.666667 128z m369.792 0L512 298.666667l99.541333-170.666667h172.16L512 597.333333 240.298667 128h172.16z" p-id="4634"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M792.490667 585.002667a38.826667 38.826667 0 0 0 38.314666-38.314667c0-21.248-17.024-38.314667-38.314666-38.314667s-38.314667 17.066667-38.314667 38.314667c0 21.333333 17.066667 38.314667 38.314667 38.314667z m-188.8 0a38.826667 38.826667 0 0 0 38.314666-38.314667c0-21.248-17.066667-38.314667-38.314666-38.314667-21.333333 0-38.314667 17.066667-38.314667 38.314667 0 21.333333 17.024 38.314667 38.314667 38.314667z m280.192 215.04a14.805333 14.805333 0 0 0-7.338667 15.786666c0 2.048 0 4.138667 1.066667 6.272 4.181333 17.792 12.544 46.122667 12.544 47.189334 0 3.114667 1.066667 5.205333 1.066666 7.338666a9.386667 9.386667 0 0 1-9.429333 9.386667c-2.133333 0-3.157333-1.024-5.248-2.048l-61.824-35.669333a34.090667 34.090667 0 0 0-14.677333-4.181334c-3.114667 0-6.272 0-8.362667 1.024-29.354667 8.405333-59.733333 12.586667-92.202667 12.586667-156.16 0-281.898667-104.832-281.898666-234.88 0-130.005333 125.738667-234.88 281.898666-234.88 156.117333 0 281.856 104.874667 281.856 234.88 0 70.272-37.717333 134.229333-97.450666 177.237333zM711.381333 345.557333a388.48 388.48 0 0 0-11.946666-0.213333c-178.090667 0-324.522667 122.026667-324.522667 277.546667 0 23.637333 3.413333 46.506667 9.728 68.266666h-3.797333a425.088 425.088 0 0 1-110.250667-15.701333c-3.157333-1.066667-6.314667-1.066667-9.472-1.066667a35.498667 35.498667 0 0 0-17.834667 5.248l-74.581333 42.88c-2.133333 1.066667-4.224 2.133333-6.314667 2.133334a11.648 11.648 0 0 1-11.52-11.52c0-3.157333 1.024-5.248 2.090667-8.405334 1.024-1.024 10.496-35.584 15.744-56.490666 0-2.133333 1.024-5.248 1.024-7.338667a23.722667 23.722667 0 0 0-9.429333-18.858667C87.808 570.709333 42.666667 494.336 42.666667 409.514667 42.666667 253.653333 194.986667 128 381.866667 128c160.64 0 295.68 92.544 329.514666 217.514667z m-219.904 17.834667c24.448 0 43.776-20.352 43.776-43.776 0-24.448-19.328-43.776-43.776-43.776s-43.776 19.328-43.776 43.776 19.328 43.776 43.776 43.776z m-224.426666 0c24.448 0 43.818667-20.352 43.818666-43.776 0-24.448-19.370667-43.776-43.818666-43.776-24.405333 0-43.776 19.328-43.776 43.776s19.370667 43.776 43.776 43.776z" p-id="4372"></path></svg>
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
export { default as Vue } from './Vue.vue'
|
||||
export { default as Code } from './Code.vue'
|
||||
export { default as Wechat } from './Wechat.vue'
|
||||
export { default as BugFill } from './BugFill.vue'
|
||||
export { default as BugLine } from './BugLine.vue'
|
||||
export { default as FileWord } from './FileWord.vue'
|
||||
export { default as FileExcel } from './FileExcel.vue'
|
||||
export { default as FilePpt } from './FilePpt.vue'
|
||||
export { default as Organization } from './Organization.vue'
|
||||
export { default as Upload } from './Upload.vue'
|
||||
export { default as Download } from './Download.vue'
|
||||
export { default as Back } from './Back.vue'
|
||||
@@ -0,0 +1,103 @@
|
||||
/* 全局 */
|
||||
#app, body, html {width: 100%;height: 100%;background-color: #f6f8f9;font-size: 12px;}
|
||||
a {color: #333;text-decoration: none;}
|
||||
a:hover, a:focus {color: #000;text-decoration: none;}
|
||||
a:link {text-decoration: none;}
|
||||
a:-webkit-any-link {text-decoration: none;}
|
||||
a,button,input,textarea{-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing: border-box;outline:none !important; -webkit-appearance: none;}
|
||||
* {margin: 0;padding: 0;box-sizing: border-box;outline: none;}
|
||||
|
||||
/* 大布局样式 */
|
||||
.aminui {display: flex;flex-flow: column;}
|
||||
.aminui-wrapper {display: flex;flex:1;overflow: auto;}
|
||||
|
||||
/* 全局滚动条样式 */
|
||||
.scrollable {-webkit-overflow-scrolling: touch;}
|
||||
::-webkit-scrollbar {width: 5px;height: 5px;}
|
||||
::-webkit-scrollbar-thumb {background-color: rgba(50, 50, 50, 0.3);}
|
||||
::-webkit-scrollbar-thumb:hover {background-color: rgba(50, 50, 50, 0.6);}
|
||||
::-webkit-scrollbar-track {background-color: rgba(50, 50, 50, 0.1);}
|
||||
::-webkit-scrollbar-track:hover {background-color: rgba(50, 50, 50, 0.2);}
|
||||
|
||||
/*布局设置*/
|
||||
.layout-setting {position: fixed;width: 40px;height: 40px;border-radius: 3px 0 0 3px;bottom: 100px;right: 0px;z-index: 100;background: #409EFF;display: flex;flex-direction: column;align-items: center;justify-content: center;cursor: pointer;}
|
||||
.layout-setting i {font-size: 18px;color: #fff;}
|
||||
|
||||
/* 头部 */
|
||||
.adminui-header {height: 58px;background: #fff;color: #222b45; border-bottom: 1px solid rgba(0, 0, 0, 0.05); display: flex;justify-content:space-between;}
|
||||
.adminui-header-left {display: flex;align-items: center;padding-left:20px;}
|
||||
.adminui-header-right {display: flex;align-items: center;}
|
||||
.adminui-header .logo-bar {font-size: 20px;font-weight: bold;display: flex;align-items: center;}
|
||||
.adminui-header .logo-bar .logo {margin-right: 10px;height: 35px;}
|
||||
.adminui-header .nav {display: flex;height: 100%;margin-left: 40px;}
|
||||
.adminui-header .nav li {padding:0 10px;margin: 0 10px 0 0;font-size: 14px;color: rgba(255, 255, 255, 0.6);list-style: none;height: 100%;display: flex;align-items: center;cursor: pointer;}
|
||||
.adminui-header .nav li i {margin-right: 5px;}
|
||||
.adminui-header .nav li:hover {color: #222b45;}
|
||||
.adminui-header .nav li.active {background: rgba(255, 255, 255, 0.1);color: #222b45;}
|
||||
.adminui-header .user-bar .panel-item:hover {background: rgba(255, 255, 255, 0.1)!important;}
|
||||
.adminui-header .user-bar .user label{color: #222b45;}
|
||||
|
||||
/* 左侧菜单 */
|
||||
.aminui-side-split {width:65px;flex-shrink:0;background: #222b45;display: flex;flex-flow: column;}
|
||||
.aminui-side-split-top {height: 49px;}
|
||||
.aminui-side-split-top a {display: inline-block;width: 100%;height: 100%;display: flex;align-items: center;justify-content: center;}
|
||||
.aminui-side-split-top .logo {height:30px;vertical-align: bottom;}
|
||||
.adminui-side-split-scroll {overflow: auto;overflow-x:hidden;height: 100%;flex: 1;}
|
||||
.aminui-side-split li {cursor: pointer;width: 65px;height: 65px;color: #fff;text-align: center;display: flex;flex-direction: column;align-items: center;justify-content: center;}
|
||||
.aminui-side-split li i {font-size: 18px;}
|
||||
.aminui-side-split li p {margin-top:5px;}
|
||||
.aminui-side-split li:hover {background: rgba(255, 255, 255, 0.1);}
|
||||
.aminui-side-split li.active {background: #409EFF;}
|
||||
|
||||
.adminui-side-split-scroll::-webkit-scrollbar-thumb {background-color: rgba(255, 255, 255, 0.4);border-radius:5px;}
|
||||
.adminui-side-split-scroll::-webkit-scrollbar-thumb:hover {background-color: rgba(255, 255, 255, 0.5);}
|
||||
.adminui-side-split-scroll::-webkit-scrollbar-track {background-color: rgba(255, 255, 255, 0);}
|
||||
.adminui-side-split-scroll::-webkit-scrollbar-track:hover {background-color: rgba(255, 255, 255, 0);}
|
||||
|
||||
.aminui-side {display: flex;flex-flow: column;flex-shrink:0;width:210px;background: #fff;box-shadow: 2px 0 8px 0 rgba(29,35,41,.05);border-right: 1px solid #e6e6e6;transition:width 0.3s;}
|
||||
.adminui-side-top {border-bottom: 1px solid #ebeef5;height:50px;line-height: 50px;}
|
||||
.adminui-side-top h2 {padding:0 20px;font-size: 17px;color: #3c4a54;}
|
||||
.adminui-side-scroll {overflow: auto;overflow-x:hidden;flex: 1;}
|
||||
.adminui-side-bottom {border-top: 1px solid #ebeef5;height:51px;cursor: pointer;display: flex;align-items: center;justify-content: center;}
|
||||
.adminui-side-bottom i {font-size: 16px;}
|
||||
.adminui-side-bottom:hover {color: var(--el-color-primary);}
|
||||
.aminui-side.isCollapse {width: 65px;}
|
||||
.el-menu .menu-tag {position: absolute;height: 18px;line-height: 18px;background: var(--el-color-danger);font-size: 12px;color: #fff;right: 20px;border-radius:18px;padding:0 6px;}
|
||||
.el-menu .el-sub-menu__title .menu-tag {right: 40px;}
|
||||
.el-menu--horizontal > li .menu-tag {display: none;}
|
||||
|
||||
/* 右侧内容 */
|
||||
.aminui-body {flex: 1;display: flex;flex-flow: column;}
|
||||
|
||||
.adminui-topbar {height: 50px;border-bottom: 1px solid #ebeef5;background: #fff;box-shadow: 0 1px 4px rgba(0,21,41,.08);display: flex;justify-content:space-between;}
|
||||
.adminui-topbar .left-panel {display: flex;align-items: center;}
|
||||
.adminui-topbar .right-panel {display: flex;align-items: center;}
|
||||
|
||||
.right-panel-search {display: flex;align-items: center;}
|
||||
.right-panel-search > * + * {margin-left:10px;}
|
||||
|
||||
.adminui-tags {height:35px;background: #fff;border-bottom: 1px solid #e6e6e6;}
|
||||
.adminui-tags ul {display: flex;overflow: hidden;}
|
||||
.adminui-tags li {cursor: pointer;display: inline-block;float: left;height:34px;line-height: 34px;position: relative;flex-shrink: 0;}
|
||||
.adminui-tags li::after {content: " ";width:1px;height:100%;position: absolute;right:0px;background-image: linear-gradient(#fff, #e6e6e6);}
|
||||
.adminui-tags li a {display: inline-block;padding:0 10px;width:100%;height:100%;color: #999;text-decoration:none;display: flex;align-items: center;}
|
||||
.adminui-tags li i {margin-left:10px;border-radius: 3px;width:18px;height:18px;display: flex;align-items: center;justify-content: center;}
|
||||
.adminui-tags li i:hover {background: rgba(0,0,0,.2);color: #fff;}
|
||||
.adminui-tags li:hover {background: #ecf5ff;}
|
||||
.adminui-tags li.active {background: #409EFF;}
|
||||
.adminui-tags li.active a {color: #fff;}
|
||||
.adminui-tags li.sortable-ghost {opacity: 0;}
|
||||
|
||||
.adminui-main {overflow: auto;background-color: #f6f8f9;flex: 1;}
|
||||
|
||||
/*页面最大化*/
|
||||
.aminui.main-maximize {
|
||||
.main-maximize-exit {display: block;}
|
||||
.aminui-side-split, .aminui-side, .adminui-header, .adminui-topbar, .adminui-tags {display: none;}
|
||||
}
|
||||
.main-maximize-exit {display: none;position: fixed;z-index: 3000;top:-20px;left:50%;margin-left: -20px;border-radius: 50%;width: 40px;height: 40px;cursor: pointer;background: rgba(0,0,0,0.2);text-align: center;}
|
||||
.main-maximize-exit i {font-size: 14px;margin-top: 22px;color: #fff;}
|
||||
.main-maximize-exit:hover {background: rgba(0,0,0,0.4);}
|
||||
|
||||
/*定宽页面*/
|
||||
.sc-page {width: 1230px;margin: 0 auto;}
|
||||
@@ -0,0 +1,37 @@
|
||||
@import 'element-plus/theme-chalk/src/dark/css-vars.scss';
|
||||
|
||||
html.dark {
|
||||
//变量
|
||||
--el-text-color-primary: #d0d0d0;
|
||||
--el-color-primary-dark-2: var(--el-color-primary-light-2) !important;
|
||||
--el-color-primary-light-9: var(--el-color-primary-dark-8) !important;
|
||||
--el-color-primary-light-8: var(--el-color-primary-dark-7) !important;
|
||||
--el-color-primary-light-7: var(--el-color-primary-dark-6) !important;
|
||||
--el-color-primary-light-5: var(--el-color-primary-dark-4) !important;
|
||||
--el-color-primary-light-3: var(--el-color-primary-dark-3) !important;
|
||||
|
||||
//背景
|
||||
#app {background: var(--el-bg-color);}
|
||||
|
||||
//登录背景
|
||||
.login_bg {background: var(--el-bg-color);}
|
||||
|
||||
//框架
|
||||
.adminui-header {background: var(--el-bg-color-overlay);border-bottom: 1px solid var(--el-border-color-light);height:59px;}
|
||||
.aminui-side-split {background: var(--el-bg-color);}
|
||||
.aminui-side-split li {color: var(--el-text-color-primary);}
|
||||
.aminui-side {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);}
|
||||
.adminui-side-top, .adminui-side-bottom {border-color: var(--el-border-color-light);}
|
||||
.adminui-side-top h2 {color: var(--el-text-color-primary);}
|
||||
.adminui-topbar, .adminui-tags {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);}
|
||||
.adminui-main {background: var(--el-bg-color);}
|
||||
.drawerBG {background: var(--el-bg-color);}
|
||||
.adminui-header-menu .el-menu {--el-menu-bg-color:var(--el-bg-color-overlay) !important;--el-menu-hover-bg-color: #171819 !important;}
|
||||
|
||||
//组件
|
||||
.el-header, .el-main.nopadding, .el-footer {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);}
|
||||
.el-main {background: var(--el-bg-color);}
|
||||
.el-aside {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);}
|
||||
.el-table .el-table__body-wrapper {background: var(--el-bg-color);}
|
||||
.el-table th.is-sortable:hover {background: #111;}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
input::-webkit-outer-spin-button,input::-webkit-inner-spin-button { -webkit-appearance: none;}
|
||||
|
||||
.action-icon{cursor: pointer; width: 1.2em; height: 1.2em; color: #409EFC; vertical-align: middle; margin-right: 5px;}
|
||||
.el-tabs__new-tab{margin: 10px;}
|
||||
|
||||
.el-drawer__header{margin-bottom: 10px;}
|
||||
|
||||
.el-card__header{padding: 10px; padding-bottom: 0; font-size: 14px;}
|
||||
.el-card__body{padding: 10px;}
|
||||
|
||||
.el-dialog__body{padding: calc(var(--el-dialog-padding-primary)) var(--el-dialog-padding-primary);}
|
||||
.el-upload-dragger, .el-upload-dragger.is-dragover{padding: 0; border: none;}
|
||||
@@ -0,0 +1,83 @@
|
||||
/* 覆盖element-plus样式 */
|
||||
|
||||
:root {
|
||||
--el-color-primary: #409EFF;
|
||||
--el-color-primary-light-1: #53a7ff;
|
||||
--el-color-primary-light-2: #66b1ff;
|
||||
--el-color-primary-light-3: #79bbff;
|
||||
--el-color-primary-light-4: #8cc4ff;
|
||||
--el-color-primary-light-5: #9fceff;
|
||||
--el-color-primary-light-6: #b2d8ff;
|
||||
--el-color-primary-light-7: #c5e1ff;
|
||||
--el-color-primary-light-8: #d8ebff;
|
||||
--el-color-primary-light-9: #ebf5ff;
|
||||
--el-color-primary-dark-1: #398ee5;
|
||||
--el-color-primary-dark-2: #337ecc;
|
||||
--el-color-primary-dark-3: #2c6eb2;
|
||||
--el-color-primary-dark-4: #265e99;
|
||||
--el-color-primary-dark-5: #204f7f;
|
||||
--el-color-primary-dark-6: #193f66;
|
||||
--el-color-primary-dark-7: #132f4c;
|
||||
--el-color-primary-dark-8: #0c1f32;
|
||||
--el-color-primary-dark-9: #060f19;
|
||||
}
|
||||
|
||||
.el-menu {border: none!important;}
|
||||
.el-menu .el-menu-item a {color: inherit;text-decoration: none;display: block;width:100%;height:100%;position: absolute;top:0px;left:0px;}
|
||||
.el-form-item-msg {font-size: 12px;color: #999;clear: both;width: 100%;}
|
||||
.el-container {height: 100%;}
|
||||
.el-aside {border-right: 1px solid var(--el-border-color-light);}
|
||||
.el-container + .el-aside {border-right: 0;border-left: 1px solid var(--el-border-color-light);}
|
||||
.el-header {background: #fff;border-bottom: 1px solid var(--el-border-color-light);padding:13px 15px;display: flex;justify-content: space-between;align-items: center;}
|
||||
.el-header .left-panel {display: flex;align-items: center;}
|
||||
.el-header .right-panel {display: flex;align-items: center;}
|
||||
.el-header .right-panel > * + * {margin-left:10px;}
|
||||
.el-footer {background: #fff;border-top: 1px solid var(--el-border-color-light);padding:13px 15px;height: 51px;}
|
||||
.el-main {padding:15px;}
|
||||
.el-main.nopadding {padding:0;background: #fff;}
|
||||
.el-drawer__body {overflow: auto;padding:0;}
|
||||
.el-popconfirm__main {margin: 14px 0;}
|
||||
.el-card__header {border-bottom: 0;font-size: 17px;font-weight: bold;padding:15px 20px 0px 20px;}
|
||||
.el-dialog__title {font-size: 17px;font-weight: bold;}
|
||||
.el-drawer__header>:first-child {font-size: 17px;font-weight: bold;}
|
||||
.el-tree.menu .el-tree-node__content {height:36px;}
|
||||
.el-tree.menu .el-tree-node__content .el-tree-node__label .icon {margin-right: 5px;}
|
||||
.el-progress__text {font-size: 12px!important;}
|
||||
.el-progress__text i {font-size: 14.4px!important;}
|
||||
.el-step.is-horizontal .el-step__line {height:1px;}
|
||||
.el-step__title {font-size: 14px;}
|
||||
.drawerBG {background: #f6f8f9;}
|
||||
.el-button+.el-dropdown {margin-left: 10px;}
|
||||
.el-button-group+.el-dropdown {margin-left: 10px;}
|
||||
.el-tag+.el-tag {margin-left: 10px;}
|
||||
.el-button-group+.el-button-group {margin-left: 10px;}
|
||||
.el-tabs__nav-wrap::after {height: 1px;}
|
||||
.el-table th.is-sortable {transition: .1s;}
|
||||
.el-table th.is-sortable:hover {background: #eee;}
|
||||
.el-table .el-table__body-wrapper {background: #f6f8f9;}
|
||||
.el-col .el-card {margin-bottom: 15px;}
|
||||
.el-main {flex-basis: 100%;}
|
||||
.el-main > .scTable .el-table--border::before {display: none;}
|
||||
.el-main > .scTable .el-table--border::after {display: none;}
|
||||
.el-main > .scTable .el-table--border .el-table__inner-wrapper::after {display: none;}
|
||||
.el-main > .scTable .el-table__border-left-patch {display: none;}
|
||||
.el-main > .scTable .el-table--border .el-table__inner-wrapper tr:first-child td:first-child {border-left: 0;}
|
||||
.el-main > .scTable .el-table--border .el-table__inner-wrapper tr:first-child th:first-child {border-left: 0;}
|
||||
.el-table.el-table--large {font-size: 14px;}
|
||||
.el-table.el-table--small {font-size: 12px;}
|
||||
.el-table {font-size: 12px;}
|
||||
.el-radio-button__inner {font-size: 12px;}
|
||||
.el-checkbox-button__inner {font-size: 12px;}
|
||||
.el-sub-menu .el-icon {font-size: 17px;}
|
||||
.el-sub-menu .el-sub-menu__icon-arrow {font-size: 12px;}
|
||||
|
||||
.aminui-side-split li.active {background-color: var(--el-color-primary);}
|
||||
.adminui-tags li:hover {background-color: var(--el-color-primary-light-9);}
|
||||
.adminui-tags li.active {background-color: var(--el-color-primary)!important;}
|
||||
.contextmenu li:hover {background-color: var(--el-color-primary-light-9)!important;color: var(--el-color-primary-light-2)!important;}
|
||||
.data-box .item-background {background-color: var(--el-color-primary)!important;}
|
||||
.layout-setting,.diy-grid-setting {background-color: var(--el-color-primary)!important;}
|
||||
|
||||
/* 覆盖tinymce样式 */
|
||||
.sceditor .tox-tinymce {border: 1px solid #DCDFE6;border-radius: 0;}
|
||||
body .tox-tinymce-aux {z-index: 5700;}
|
||||
@@ -0,0 +1,50 @@
|
||||
@media (max-width: 992px){
|
||||
// 移动端样式覆盖
|
||||
.el-form-item {display: block;}
|
||||
.el-form-item__label {display: block;text-align: left;padding: 0 0 10px;}
|
||||
.el-dialog {width: 90%!important;}
|
||||
.el-dialog.is-fullscreen {width: 100%!important;}
|
||||
.el-drawer.rtl {width: 90%!important;}
|
||||
.el-form-item__content {margin-left: 0px!important;}
|
||||
|
||||
.adminui-main {
|
||||
>.el-container {display: block;height:auto;}
|
||||
>.el-container > .el-aside {width: 100%!important;border: 0}
|
||||
}
|
||||
.scTable {
|
||||
.el-table,
|
||||
.el-table__body-wrapper {display: block!important;height:auto!important;}
|
||||
.el-scrollbar__wrap {height:auto!important;}
|
||||
.scTable-page {padding: 0 5px!important;}
|
||||
.el-pagination__total,
|
||||
.el-pagination__jump,
|
||||
.el-pagination__sizes {display: none!important;}
|
||||
}
|
||||
|
||||
.headerPublic {
|
||||
height: auto!important;display: block;
|
||||
.left-panel {overflow: auto;}
|
||||
.left-panel::-webkit-scrollbar{display: none;}
|
||||
.right-panel {display: block;border-top: 1px solid var(--el-border-color-light);margin-top: 15px;}
|
||||
.right-panel .right-panel-search {display: block;}
|
||||
.right-panel .right-panel-search >* {width: 100%;margin: 0;margin-top: 15px;}
|
||||
}
|
||||
.adminui-main > .el-container >*:first-child:not(.el-aside):not(.el-header) {border: 0;margin-top: 0;}
|
||||
.adminui-main > .el-container >*:first-child:not(.el-aside):not(.el-header) + .el-aside {margin-top: 0;}
|
||||
.adminui-main > .el-container > .el-aside {border-bottom: 1px solid var(--el-border-color-light)!important;}
|
||||
.adminui-main > .el-container > .el-container {border-top: 1px solid var(--el-border-color-light);border-bottom: 1px solid var(--el-border-color-light);margin-top: 15px;}
|
||||
.adminui-main > .el-container > .el-container + .el-aside {border-top: 1px solid var(--el-border-color-light);margin-top: 15px;}
|
||||
.adminui-main > .el-container > .el-header {@extend .headerPublic;}
|
||||
.adminui-main > .el-container > .el-main.nopadding {border-top: 1px solid var(--el-border-color-light);border-bottom: 1px solid var(--el-border-color-light);margin-top: 15px;}
|
||||
.adminui-main > .el-container > .el-main + .el-aside {border-left: 0!important;border-top: 1px solid var(--el-border-color-light);margin-top: 15px;}
|
||||
.adminui-main > .el-container > .el-footer {margin-top: 15px;border-bottom: 1px solid var(--el-border-color-light);}
|
||||
.adminui-main > .el-container > .el-container > .el-header {@extend .headerPublic}
|
||||
.adminui-main > .el-container > .el-container > .el-header .left-panel {display: block;}
|
||||
.adminui-main > .el-container > .el-container > .el-header .right-panel {display: block;margin-top: 15px;}
|
||||
|
||||
.sc-page {width: 100%;margin: 0;}
|
||||
|
||||
.common-main .el-form {width: 100% !important;}
|
||||
.common-header-logo label {display: none;}
|
||||
.common-header-title {display: none;}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/* USERCENTER */
|
||||
.page-user {
|
||||
.user-info-top {text-align: center;}
|
||||
.user-info-top h2 {font-size: 18px;margin-top: 5px;}
|
||||
.user-info-top p {margin: 8px 0 10px 0;}
|
||||
.menu {background: none;}
|
||||
.menu .el-menu-item {font-size: 12px;--el-menu-item-height:50px;}
|
||||
.menu .el-menu-item-group {border-top: 1px solid var(--el-border-color-light);}
|
||||
.menu .el-menu-item-group:first-child {border: 0;}
|
||||
}
|
||||
|
||||
/*static-table*/
|
||||
.static-table {border-collapse: collapse;width: 100%;font-size: 14px;margin-bottom: 45px;line-height: 1.5em;}
|
||||
.static-table th {text-align: left;white-space: nowrap;color: #909399;font-weight: 400;border-bottom: 1px solid #dcdfe6;padding: 15px;max-width: 250px;}
|
||||
.static-table td {border-bottom: 1px solid #dcdfe6;padding: 15px;max-width: 250px;color: #606266;}
|
||||
|
||||
/*header-tabs*/
|
||||
.header-tabs {padding:10px 0 0 0;display:block;border:0!important;height:50px;background: none;}
|
||||
.header-tabs .el-tabs__header {padding-left:10px;margin: 0;}
|
||||
.header-tabs .el-tabs__content {display: none;}
|
||||
.header-tabs .el-tabs__nav {border-radius: 0 !important;}
|
||||
.header-tabs .el-tabs__item {font-size: 13px;}
|
||||
.header-tabs .el-tabs__item.is-active {background-color: var(--el-bg-color-overlay);}
|
||||
|
||||
/*common-page*/
|
||||
.common-page {}
|
||||
.common-header-left {display: flex;align-items: center;}
|
||||
.common-header-logo {display: flex;align-items: center;}
|
||||
.common-header-logo img {height:30px;margin-right: 10px;vertical-align: bottom;}
|
||||
.common-header-logo label {font-size: 20px;}
|
||||
.common-header-title {font-size: 16px;border-left: 1px solid var(--el-border-color-light);margin-left: 15px;padding-left: 15px;}
|
||||
.common-header-right {display: flex;align-items: center;}
|
||||
.common-header-right a {font-size: 14px;color: var(--el-color-primary);cursor: pointer;}
|
||||
.common-header-right a:hover {color: var(--el-color-primary-light-3);}
|
||||
.common-container {max-width: 1240px;margin:30px auto 30px auto;}
|
||||
.common-main {padding:20px;}
|
||||
.common-title {font-size: 26px;margin-bottom: 20px;font-weight: normal;}
|
||||
.common-main .el-form {width: 500px;margin:30px auto;}
|
||||
.common-main .el-steps .el-step__title {font-size: 14px;}
|
||||
.common-main .el-steps .el-step__icon {border: 1px solid;}
|
||||
.common-main .yzm {display: flex;width: 100%;}
|
||||
.common-main .yzm .el-button {margin-left: 10px;}
|
||||
.common-main .link {color: var(--el-color-primary);cursor: pointer;}
|
||||
.common-main .link:hover {color: var(--el-color-primary-light-3);}
|
||||
@@ -0,0 +1,6 @@
|
||||
@import 'app.scss';
|
||||
@import 'fix.scss';
|
||||
@import 'pages.scss';
|
||||
@import 'media.scss';
|
||||
@import 'dark.scss';
|
||||
@import 'diy.scss';
|
||||
@@ -0,0 +1,115 @@
|
||||
<!--
|
||||
* @Descripttion: 代码编辑器
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2022年5月20日21:46:29
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-code-editor" :style="{'height':_height}">
|
||||
<textarea ref="textarea" v-model="contentValue"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { markRaw } from "vue"
|
||||
|
||||
//框架
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
|
||||
//主题
|
||||
import 'codemirror/theme/idea.css'
|
||||
import 'codemirror/theme/darcula.css'
|
||||
|
||||
//功能
|
||||
import 'codemirror/addon/selection/active-line'
|
||||
|
||||
//语言
|
||||
import 'codemirror/mode/javascript/javascript'
|
||||
import 'codemirror/mode/sql/sql'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: "javascript"
|
||||
},
|
||||
height: {
|
||||
type: [String,Number],
|
||||
default: 300,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: "idea"
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
contentValue: this.modelValue,
|
||||
coder: null,
|
||||
opt: {
|
||||
theme: this.theme, //主题
|
||||
styleActiveLine: true, //高亮当前行
|
||||
lineNumbers: true, //行号
|
||||
lineWrapping: false, //自动换行
|
||||
tabSize: 4, //Tab缩进
|
||||
indentUnit: 4, //缩进单位
|
||||
indentWithTabs : true, //自动缩进
|
||||
mode : this.mode, //语言
|
||||
readOnly: this.readOnly, //只读
|
||||
...this.options
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_height() {
|
||||
return Number(this.height)?Number(this.height)+'px':this.height
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.contentValue = val
|
||||
if (val !== this.coder.getValue()) {
|
||||
this.coder.setValue(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
//获取挂载的所有modes
|
||||
//console.log(CodeMirror.modes)
|
||||
},
|
||||
methods: {
|
||||
init(){
|
||||
this.coder = markRaw(CodeMirror.fromTextArea(this.$refs.textarea, this.opt))
|
||||
this.coder.on('change', (coder) => {
|
||||
this.contentValue = coder.getValue()
|
||||
this.$emit('update:modelValue', this.contentValue)
|
||||
})
|
||||
},
|
||||
formatStrInJson(strValue) {
|
||||
return JSON.stringify(JSON.parse(strValue), null, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-code-editor {font-size: 14px;border: 1px solid #ddd;line-height: 150%;}
|
||||
.sc-code-editor:deep(.CodeMirror) {height: 100%;}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
<!--
|
||||
* @Descripttion: scContextmenu组件
|
||||
* @version: 1.1
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年7月23日09:25:57
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年5月30日20:17:42
|
||||
* @other: 代码完全开源,欢迎参考,也欢迎PR
|
||||
-->
|
||||
|
||||
<template>
|
||||
<transition name="el-zoom-in-top">
|
||||
<div v-if="visible" ref="contextmenu" class="sc-contextmenu" :style="{left:left+'px',top:top+'px'}" @contextmenu.prevent="fun">
|
||||
<ul class="sc-contextmenu__menu">
|
||||
<slot></slot>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
provide() {
|
||||
return {
|
||||
menuClick: this.menuClick
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
top: 0,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(value) {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', this.cm, true)
|
||||
}else{
|
||||
document.body.removeEventListener('click', this.cm, true)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
cm(e){
|
||||
let sp = this.$refs.contextmenu
|
||||
if(sp&&!sp.contains(e.target)){
|
||||
this.closeMenu()
|
||||
}
|
||||
},
|
||||
menuClick(command){
|
||||
this.closeMenu()
|
||||
this.$emit('command', command)
|
||||
},
|
||||
openMenu(e) {
|
||||
e.preventDefault()
|
||||
this.visible = true
|
||||
this.left = e.clientX + 1
|
||||
this.top = e.clientY + 1
|
||||
|
||||
this.$nextTick(() => {
|
||||
var ex = e.clientX + 1
|
||||
var ey = e.clientY + 1
|
||||
var innerWidth = window.innerWidth
|
||||
var innerHeight = window.innerHeight
|
||||
var menuHeight = this.$refs.contextmenu.offsetHeight
|
||||
var menuWidth = this.$refs.contextmenu.offsetWidth
|
||||
//位置修正公示
|
||||
//left = (当前点击X + 菜单宽度 > 可视区域宽度 ? 可视区域宽度 - 菜单宽度 : 当前点击X)
|
||||
//top = (当前点击Y + 菜单高度 > 可视区域高度 ? 当前点击Y - 菜单高度 : 当前点击Y)
|
||||
this.left = ex + menuWidth > innerWidth ? innerWidth - menuWidth : ex
|
||||
this.top = ey + menuHeight > innerHeight ? ey - menuHeight : ey
|
||||
})
|
||||
this.$emit('visibleChange', true)
|
||||
},
|
||||
closeMenu() {
|
||||
this.visible = false;
|
||||
this.$emit('visibleChange', false)
|
||||
},
|
||||
fun(){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sc-contextmenu {position: fixed;z-index: 3000;font-size: 12px;}
|
||||
.sc-contextmenu__menu {display: inline-block;min-width: 120px;border: 1px solid #e4e7ed;background: #fff;box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);z-index: 3000;list-style-type: none;padding: 10px 0;}
|
||||
.sc-contextmenu__menu > hr {margin:5px 0;border: none;height: 1px;font-size: 0px;background-color: #ebeef5;}
|
||||
.sc-contextmenu__menu > li {margin:0;cursor: pointer;line-height: 30px;padding: 0 17px 0 10px;color: #606266;display: flex;justify-content: space-between;white-space: nowrap;text-decoration: none;position: relative;}
|
||||
.sc-contextmenu__menu > li:hover {background-color: #ecf5ff;color: #66b1ff;}
|
||||
.sc-contextmenu__menu > li.disabled {cursor: not-allowed;color: #bbb;background: transparent;}
|
||||
.sc-contextmenu__icon {display: inline-block;width: 14px;font-size: 14px;margin-right: 10px;}
|
||||
.sc-contextmenu__suffix {margin-left: 40px;color: #999;}
|
||||
.sc-contextmenu__menu li ul {position: absolute;top:0px;left:100%;display: none;margin: -11px 0;}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<!--
|
||||
* @Descripttion: scContextmenuItem组件
|
||||
* @version: 1.2
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年7月23日16:29:36
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年2月8日15:51:07
|
||||
-->
|
||||
|
||||
<template>
|
||||
<hr v-if="divided">
|
||||
<li :class="disabled?'disabled':''" @click.stop="liClick" @mouseenter="openSubmenu($event)" @mouseleave="closeSubmenu($event)">
|
||||
<span class="title">
|
||||
<el-icon class="sc-contextmenu__icon"><component v-if="icon" :is="icon" /></el-icon>
|
||||
{{title}}
|
||||
</span>
|
||||
<span class="sc-contextmenu__suffix">
|
||||
<el-icon v-if="$slots.default"><el-icon-arrow-right /></el-icon>
|
||||
<template v-else>{{suffix}}</template>
|
||||
</span>
|
||||
<ul v-if="$slots.default" class="sc-contextmenu__menu">
|
||||
<slot></slot>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
command: { type: String, default: "" },
|
||||
title: { type: String, default: "" },
|
||||
suffix: { type: String, default: "" },
|
||||
icon: { type: String, default: "" },
|
||||
divided: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
},
|
||||
inject: ['menuClick'],
|
||||
methods: {
|
||||
liClick(){
|
||||
if(this.$slots.default){
|
||||
return false
|
||||
}
|
||||
if(this.disabled){
|
||||
return false
|
||||
}
|
||||
this.menuClick(this.command)
|
||||
},
|
||||
openSubmenu(e){
|
||||
var menu = e.target.querySelector('ul')
|
||||
if(!menu){
|
||||
return false
|
||||
}
|
||||
menu.style.display = 'inline-block'
|
||||
var rect = menu.getBoundingClientRect()
|
||||
var menuX = rect.left
|
||||
var menuY = rect.top
|
||||
var innerWidth = window.innerWidth
|
||||
var innerHeight = window.innerHeight
|
||||
var menuHeight = menu.offsetHeight
|
||||
var menuWidth = menu.offsetWidth
|
||||
if(menuX + menuWidth > innerWidth){
|
||||
menu.style.left = 'auto'
|
||||
menu.style.right = '100%'
|
||||
}
|
||||
if(menuY + menuHeight > innerHeight){
|
||||
menu.style.top = 'auto'
|
||||
menu.style.bottom = '0'
|
||||
}
|
||||
},
|
||||
closeSubmenu(e){
|
||||
var menu = e.target.querySelector('ul')
|
||||
if(!menu){
|
||||
return false
|
||||
}
|
||||
menu.removeAttribute("style")
|
||||
menu.style.display = 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<el-form>
|
||||
<el-form-item label="类型">
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio-button label="任意值" :value="0"></el-radio-button>
|
||||
<el-radio-button label="范围" :value="1"></el-radio-button>
|
||||
<el-radio-button label="间隔" :value="2"></el-radio-button>
|
||||
<el-radio-button label="指定" :value="3"></el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="范围" v-if="form.type==1">
|
||||
<el-input-number v-model="form.range.start" :min="1" :max="31" controls-position="right"></el-input-number>
|
||||
<span style="padding:0 15px;">-</span>
|
||||
<el-input-number v-model="form.range.end" :min="1" :max="31" controls-position="right"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" v-if="form.type==2">
|
||||
<el-input-number v-model="form.loop.start" :min="1" :max="31" controls-position="right"></el-input-number>
|
||||
号开始,每
|
||||
<el-input-number v-model="form.loop.end" :min="1" :max="31" controls-position="right"></el-input-number>
|
||||
天执行一次
|
||||
</el-form-item>
|
||||
<el-form-item label="指定" v-if="form.type==3">
|
||||
<el-select v-model="form.appoint" multiple style="width: 100%;">
|
||||
<el-option v-for="(item, index) in day" :key="index" :label="item" :value="item"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['update:modelValue'],
|
||||
props: {
|
||||
modelValue: { type: String, default: "*" },
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
form: {
|
||||
type: '0',
|
||||
range: {
|
||||
start: 1,
|
||||
end: 1
|
||||
},
|
||||
loop: {
|
||||
start: 1,
|
||||
end: 1
|
||||
},
|
||||
appoint: []
|
||||
},
|
||||
day: []
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue:{
|
||||
handler(val){
|
||||
if(val == '*'){
|
||||
this.form.type = 0
|
||||
}else if(val.indexOf('-') > -1){
|
||||
this.form.type = 1
|
||||
}else if(val.indexOf('/') > -1){
|
||||
this.form.type = 2
|
||||
}else if(val.indexOf(',') > -1){
|
||||
this.form.type = 3
|
||||
}else{
|
||||
this.form.type = 3
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
"form": {
|
||||
handler(val){
|
||||
let data = ''
|
||||
if(val.type == 0){
|
||||
data = '*'
|
||||
}else if(val.type==1){
|
||||
data = this.form.range.start + '-' + this.form.range.end
|
||||
}else if(val.type==2){
|
||||
data = this.form.loop.start + '/' + this.form.loop.end
|
||||
}else if(val.type==3){
|
||||
data = this.form.appoint.length>0 ? this.form.appoint.join(',') : '1'
|
||||
}else{
|
||||
data = '*'
|
||||
}
|
||||
this.$emit('update:modelValue', data)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<el-form>
|
||||
<el-form-item label="类型">
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio-button label="任意值" :value="0"></el-radio-button>
|
||||
<el-radio-button label="范围" :value="1"></el-radio-button>
|
||||
<el-radio-button label="间隔" :value="2"></el-radio-button>
|
||||
<el-radio-button label="指定" :value="3"></el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="范围" v-if="form.type==1">
|
||||
<el-input-number v-model="form.range.start" :min="0" :max="23" controls-position="right"></el-input-number>
|
||||
<span style="padding:0 15px;">-</span>
|
||||
<el-input-number v-model="form.range.end" :min="0" :max="23" controls-position="right"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" v-if="form.type==2">
|
||||
<el-input-number v-model="form.loop.start" :min="0" :max="23" controls-position="right"></el-input-number>
|
||||
小时开始,每
|
||||
<el-input-number v-model="form.loop.end" :min="0" :max="23" controls-position="right"></el-input-number>
|
||||
小时执行一次
|
||||
</el-form-item>
|
||||
<el-form-item label="指定" v-if="form.type==3">
|
||||
<el-select v-model="form.appoint" multiple style="width: 100%;">
|
||||
<el-option v-for="(item, index) in hour" :key="index" :label="item" :value="item"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['update:modelValue'],
|
||||
props: {
|
||||
modelValue: { type: String, default: "*" },
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
form: {
|
||||
type: '0',
|
||||
range: {
|
||||
start: 1,
|
||||
end: 1
|
||||
},
|
||||
loop: {
|
||||
start: 1,
|
||||
end: 1
|
||||
},
|
||||
appoint: []
|
||||
},
|
||||
hour: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue:{
|
||||
handler(val){
|
||||
if(val == '*'){
|
||||
this.form.type = 0
|
||||
}else if(val.indexOf('-') > -1){
|
||||
this.form.type = 1
|
||||
}else if(val.indexOf('/') > -1){
|
||||
this.form.type = 2
|
||||
}else if(val.indexOf(',') > -1){
|
||||
this.form.type = 3
|
||||
}else{
|
||||
this.form.type = 3
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
"form": {
|
||||
handler(val){
|
||||
let data = ''
|
||||
if(val.type == 0){
|
||||
data = '*'
|
||||
}else if(val.type==1){
|
||||
data = this.form.range.start + '-' + this.form.range.end
|
||||
}else if(val.type==2){
|
||||
data = this.form.loop.start + '/' + this.form.loop.end
|
||||
}else if(val.type==3){
|
||||
data = this.form.appoint.length>0 ? this.form.appoint.join(',') : '1'
|
||||
}else{
|
||||
data = '*'
|
||||
}
|
||||
this.$emit('update:modelValue', data)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<el-form>
|
||||
<el-form-item label="类型">
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio-button label="任意值" :value="0"></el-radio-button>
|
||||
<el-radio-button label="范围" :value="1"></el-radio-button>
|
||||
<el-radio-button label="间隔" :value="2"></el-radio-button>
|
||||
<el-radio-button label="指定" :value="3"></el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="范围" v-if="form.type==1">
|
||||
<el-input-number v-model="form.range.start" :min="0" :max="23" controls-position="right"></el-input-number>
|
||||
<span style="padding:0 15px;">-</span>
|
||||
<el-input-number v-model="form.range.end" :min="0" :max="23" controls-position="right"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" v-if="form.type==2">
|
||||
<el-input-number v-model="form.loop.start" :min="0" :max="23" controls-position="right"></el-input-number>
|
||||
分钟开始,每
|
||||
<el-input-number v-model="form.loop.end" :min="0" :max="23" controls-position="right"></el-input-number>
|
||||
分钟执行一次
|
||||
</el-form-item>
|
||||
<el-form-item label="指定" v-if="form.type==3">
|
||||
<el-select v-model="form.appoint" multiple style="width: 100%;">
|
||||
<el-option v-for="(item, index) in minute" :key="index" :label="item" :value="item"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['update:modelValue'],
|
||||
props: {
|
||||
modelValue: { type: String, default: "*" },
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
form: {
|
||||
type: '0',
|
||||
range: {
|
||||
start: 1,
|
||||
end: 1
|
||||
},
|
||||
loop: {
|
||||
start: 1,
|
||||
end: 1
|
||||
},
|
||||
appoint: []
|
||||
},
|
||||
minute: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,51, 52, 53, 54, 55, 56, 57, 58, 59]
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue:{
|
||||
handler(val){
|
||||
if(val == '*'){
|
||||
this.form.type = 0
|
||||
}else if(val.indexOf('-') > -1){
|
||||
this.form.type = 1
|
||||
}else if(val.indexOf('/') > -1){
|
||||
this.form.type = 2
|
||||
}else if(val.indexOf(',') > -1){
|
||||
this.form.type = 3
|
||||
}else{
|
||||
this.form.type = 3
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
"form": {
|
||||
handler(val){
|
||||
let data = ''
|
||||
if(val.type == 0){
|
||||
data = '*'
|
||||
}else if(val.type==1){
|
||||
data = this.form.range.start + '-' + this.form.range.end
|
||||
}else if(val.type==2){
|
||||
data = this.form.loop.start + '/' + this.form.loop.end
|
||||
}else if(val.type==3){
|
||||
data = this.form.appoint.length>0 ? this.form.appoint.join(',') : '1'
|
||||
}else{
|
||||
data = '*'
|
||||
}
|
||||
this.$emit('update:modelValue', data)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<el-form>
|
||||
<el-form-item label="类型">
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio-button label="任意值" :value="0"></el-radio-button>
|
||||
<el-radio-button label="范围" :value="1"></el-radio-button>
|
||||
<el-radio-button label="间隔" :value="2"></el-radio-button>
|
||||
<el-radio-button label="指定" :value="3"></el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="范围" v-if="form.type==1">
|
||||
<el-input-number v-model="form.range.start" :min="1" :max="12" controls-position="right"></el-input-number>
|
||||
<span style="padding:0 15px;">-</span>
|
||||
<el-input-number v-model="form.range.end" :min="1" :max="12" controls-position="right"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" v-if="form.type==2">
|
||||
<el-input-number v-model="form.loop.start" :min="1" :max="12" controls-position="right"></el-input-number>
|
||||
月开始,每
|
||||
<el-input-number v-model="form.loop.end" :min="1" :max="12" controls-position="right"></el-input-number>
|
||||
月执行一次
|
||||
</el-form-item>
|
||||
<el-form-item label="指定" v-if="form.type==3">
|
||||
<el-select v-model="form.appoint" multiple style="width: 100%;">
|
||||
<el-option v-for="(item, index) in data.month" :key="index" :label="item" :value="item"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['update:modelValue'],
|
||||
props: {
|
||||
modelValue: { type: String, default: "*" },
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
form: {
|
||||
type: '0',
|
||||
range: {
|
||||
start: 1,
|
||||
end: 1
|
||||
},
|
||||
loop: {
|
||||
start: 1,
|
||||
end: 1
|
||||
},
|
||||
appoint: []
|
||||
},
|
||||
data: {
|
||||
month: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue:{
|
||||
handler(val){
|
||||
if(val == '*'){
|
||||
this.form.type = 0
|
||||
}else if(val.indexOf('-') > -1){
|
||||
this.form.type = 1
|
||||
}else if(val.indexOf('/') > -1){
|
||||
this.form.type = 2
|
||||
}else if(val.indexOf(',') > -1){
|
||||
this.form.type = 3
|
||||
}else{
|
||||
this.form.type = 3
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
"form": {
|
||||
handler(val){
|
||||
let data = ''
|
||||
if(val.type == 0){
|
||||
data = '*'
|
||||
}else if(val.type==1){
|
||||
data = this.form.range.start + '-' + this.form.range.end
|
||||
}else if(val.type==2){
|
||||
data = this.form.loop.start + '/' + this.form.loop.end
|
||||
}else if(val.type==3){
|
||||
data = this.form.appoint.length>0 ? this.form.appoint.join(',') : '1'
|
||||
}else{
|
||||
data = '*'
|
||||
}
|
||||
this.$emit('update:modelValue', data)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<el-form>
|
||||
<el-form-item label="类型">
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio-button label="任意值" :value="0"></el-radio-button>
|
||||
<el-radio-button label="范围" :value="1"></el-radio-button>
|
||||
<el-radio-button label="间隔" :value="2"></el-radio-button>
|
||||
<el-radio-button label="指定" :value="3"></el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="范围" v-if="form.type==1">
|
||||
<el-input-number v-model="form.range.start" :min="0" :max="59" controls-position="right"></el-input-number>
|
||||
<span style="padding:0 15px;">-</span>
|
||||
<el-input-number v-model="form.range.end" :min="0" :max="59" controls-position="right"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" v-if="form.type==2">
|
||||
<el-input-number v-model="form.loop.start" :min="0" :max="59" controls-position="right"></el-input-number>
|
||||
秒开始,每
|
||||
<el-input-number v-model="form.loop.end" :min="0" :max="59" controls-position="right"></el-input-number>
|
||||
秒执行一次
|
||||
</el-form-item>
|
||||
<el-form-item label="指定" v-if="form.type==3">
|
||||
<el-select v-model="form.appoint" multiple style="width: 100%;">
|
||||
<el-option v-for="(item, index) in second" :key="index" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['update:modelValue'],
|
||||
props: {
|
||||
modelValue: { type: String, default: "*" },
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
form: {
|
||||
type: '0',
|
||||
range: {
|
||||
start: 0,
|
||||
end: 59
|
||||
},
|
||||
loop: {
|
||||
start: 0,
|
||||
end: 59
|
||||
},
|
||||
appoint: []
|
||||
},
|
||||
second: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59]
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue:{
|
||||
handler(val){
|
||||
if(val == '*'){
|
||||
this.form.type = 0
|
||||
}else if(val.indexOf('-') > -1){
|
||||
this.form.type = 1
|
||||
}else if(val.indexOf('/') > -1){
|
||||
this.form.type = 2
|
||||
}else if(val.indexOf(',') > -1){
|
||||
this.form.type = 3
|
||||
}else{
|
||||
this.form.type = 3
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
"form": {
|
||||
handler(val){
|
||||
let data = ''
|
||||
if(val.type == 0){
|
||||
data = '*'
|
||||
}else if(val.type==1){
|
||||
data = this.form.range.start + '-' + this.form.range.end
|
||||
}else if(val.type==2){
|
||||
data = this.form.loop.start + '/' + this.form.loop.end
|
||||
}else if(val.type==3){
|
||||
data = this.form.appoint.length>0 ? this.form.appoint.join(',') : '1'
|
||||
}else{
|
||||
data = '*'
|
||||
}
|
||||
this.$emit('update:modelValue', data)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<el-input v-model="defaultValue" v-bind="$attrs">
|
||||
<template #append>
|
||||
<el-dropdown size="medium" @command="handleShortcuts">
|
||||
<el-button icon="el-icon-arrow-down"></el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="* * * * *">每分钟</el-dropdown-item>
|
||||
<el-dropdown-item command="0 * * * *">每小时</el-dropdown-item>
|
||||
<el-dropdown-item command="0 0 * * *">每天零点</el-dropdown-item>
|
||||
<el-dropdown-item command="0 0 1 * *">每月一号零点</el-dropdown-item>
|
||||
<el-dropdown-item command="0 0 L * *">每月最后一天零点</el-dropdown-item>
|
||||
<el-dropdown-item command="0 0 ? * 1">每周星期日零点</el-dropdown-item>
|
||||
<el-dropdown-item v-for="(item, index) in shortcuts" :key="item.value" :divided="index==0" :command="item.value">{{item.text}}</el-dropdown-item>
|
||||
<el-dropdown-item icon="el-icon-plus" divided command="custom">自定义</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-dialog title="cron规则生成器" v-model="dialogVisible" :width="580" destroy-on-close append-to-body>
|
||||
<div class="sc-cron">
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane v-for="(item, index) in tabsList" :key="index" :name="item.name">
|
||||
<template #label>
|
||||
<div class="sc-cron-num">
|
||||
<h2>{{item.label}}</h2>
|
||||
<h4>{{item.value}}</h4>
|
||||
</div>
|
||||
</template>
|
||||
<component :is="item.name" v-model="item.value" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible=false" >取 消</el-button>
|
||||
<el-button type="primary" @click="submit()">确 认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import second from './components/second.vue'
|
||||
import minute from './components/minute.vue'
|
||||
import hour from './components/hour.vue'
|
||||
import day from './components/day.vue'
|
||||
import month from './components/month.vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: String, default: "* * * * * ?" },
|
||||
shortcuts: { type: Array, default: () => [] }
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
defaultValue: '',
|
||||
dialogVisible: false,
|
||||
activeName: 'second',
|
||||
tabsList: [
|
||||
{name: 'second', label: '秒', value: '*'},
|
||||
{name: 'minute', label: '分钟', value: '*'},
|
||||
{name: 'hour', label: '小时', value: '*'},
|
||||
{name: 'day', label: '日', value: '*'},
|
||||
{name: 'month', label: '月', value: '*'},
|
||||
// {name: 'week', label: '周', value: '*'}
|
||||
// {name: 'year', label: '年', value: '*'}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue: {
|
||||
handler(val){
|
||||
this.defaultValue = val
|
||||
let arr = val.split(' ')
|
||||
this.tabsList.forEach((item, index) => {
|
||||
item.value = arr[index]
|
||||
})
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
components:{
|
||||
second,
|
||||
minute,
|
||||
hour,
|
||||
day,
|
||||
month
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
methods:{
|
||||
handleShortcuts(command){
|
||||
if(command == 'custom'){
|
||||
this.open()
|
||||
}else{
|
||||
this.defaultValue = command
|
||||
this.$emit('update:modelValue', this.defaultValue)
|
||||
}
|
||||
},
|
||||
open(){
|
||||
this.dialogVisible = true
|
||||
},
|
||||
submit(){
|
||||
let data = []
|
||||
for(let item of this.tabsList) {
|
||||
data.push(item.value)
|
||||
}
|
||||
this.defaultValue = data.join(' ')
|
||||
this.dialogVisible = false
|
||||
this.$emit('update:modelValue', this.defaultValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-cron:deep(.el-tabs__item) {height: auto;line-height: 1;padding:0 7px;vertical-align: bottom;}
|
||||
.sc-cron-num {text-align: center;margin-bottom: 15px;width: 100%;}
|
||||
.sc-cron-num h2 {font-size: 12px;margin-bottom: 15px;font-weight: normal;}
|
||||
.sc-cron-num h4 {display: block;height: 32px;line-height: 30px;width: 100%;font-size: 12px;padding:0 15px;background: var(--el-color-primary-light-9);border-radius:4px;}
|
||||
.sc-cron:deep(.el-tabs__item.is-active) .sc-cron-num h4 {background: var(--el-color-primary);color: #fff;}
|
||||
|
||||
[data-theme='dark'] .sc-cron-num h4 {background: var(--el-color-white);}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<!--
|
||||
* @Descripttion: 图像裁剪组件
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年7月24日17:05:43
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
* @other: 代码完全开源,欢迎参考,也欢迎PR
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-cropper">
|
||||
<div class="sc-cropper__img">
|
||||
<img :src="src" ref="img">
|
||||
</div>
|
||||
<div class="sc-cropper__preview">
|
||||
<h4>图像预览</h4>
|
||||
<div class="sc-cropper__preview__img" ref="preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Cropper from 'cropperjs'
|
||||
import 'cropperjs/dist/cropper.css'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
src: { type: String, default: "" },
|
||||
compress: {type: Number, default: 1},
|
||||
aspectRatio: {type: Number, default: NaN},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
crop: null
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
aspectRatio(val){
|
||||
this.crop.setAspectRatio(val)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init(){
|
||||
this.crop = new Cropper(this.$refs.img, {
|
||||
viewMode: 2,
|
||||
dragMode: 'move',
|
||||
responsive: false,
|
||||
aspectRatio: this.aspectRatio,
|
||||
preview: this.$refs.preview
|
||||
})
|
||||
},
|
||||
setAspectRatio(aspectRatio){
|
||||
this.crop.setAspectRatio(aspectRatio)
|
||||
},
|
||||
getCropData(cb, type='image/jpeg'){
|
||||
cb(this.crop.getCroppedCanvas().toDataURL(type, this.compress))
|
||||
},
|
||||
getCropBlob(cb, type='image/jpeg'){
|
||||
this.crop.getCroppedCanvas().toBlob((blob) => {
|
||||
cb(blob)
|
||||
}, type, this.compress)
|
||||
},
|
||||
getCropFile(cb, fileName='fileName.jpg', type='image/jpeg'){
|
||||
this.crop.getCroppedCanvas().toBlob((blob) => {
|
||||
let file = new File([blob], fileName, {type: type})
|
||||
cb(file)
|
||||
}, type, this.compress)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-cropper {height:300px;}
|
||||
.sc-cropper__img {height:100%;width:400px;float: left;background: #EBEEF5;}
|
||||
.sc-cropper__img img {display: none;}
|
||||
.sc-cropper__preview {width: 120px;margin-left: 20px;float: left;}
|
||||
.sc-cropper__preview h4 {font-weight: normal;font-size: 12px;color: #999;margin-bottom: 20px;}
|
||||
.sc-cropper__preview__img {overflow: hidden;width: 120px;height: 120px;border: 1px solid #ebeef5;}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<!--
|
||||
* @Descripttion: 弹窗扩展组件
|
||||
* @version: 2.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年8月27日08:51:52
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年5月14日15:13:41
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-dialog" ref="scDialog">
|
||||
<el-dialog ref="dialog" v-model="dialogVisible" :fullscreen="isFullscreen" v-bind="$attrs" :show-close="false">
|
||||
<template #header>
|
||||
<slot name="header">
|
||||
<span class="el-dialog__title">{{ title }}</span>
|
||||
</slot>
|
||||
<div class="sc-dialog__headerbtn">
|
||||
<button v-if="showFullscreen" aria-label="fullscreen" type="button" @click="setFullscreen">
|
||||
<el-icon v-if="isFullscreen" class="el-dialog__close"><el-icon-bottom-left /></el-icon>
|
||||
<el-icon v-else class="el-dialog__close"><el-icon-full-screen /></el-icon>
|
||||
</button>
|
||||
<button v-if="showClose" aria-label="close" type="button" @click="closeDialog">
|
||||
<el-icon class="el-dialog__close"><el-icon-close /></el-icon>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading="loading">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<template #footer>
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: Boolean, default: false },
|
||||
title: { type: String, default: "" },
|
||||
showClose: { type: Boolean, default: true },
|
||||
showFullscreen: { type: Boolean, default: true },
|
||||
loading: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
isFullscreen: false
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(){
|
||||
this.dialogVisible = this.modelValue
|
||||
if(this.dialogVisible){
|
||||
this.isFullscreen = false
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.dialogVisible = this.modelValue
|
||||
},
|
||||
methods: {
|
||||
//关闭
|
||||
closeDialog(){
|
||||
this.dialogVisible = false
|
||||
},
|
||||
//最大化
|
||||
setFullscreen(){
|
||||
this.isFullscreen = !this.isFullscreen
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-dialog__headerbtn {position: absolute;top: var(--el-dialog-padding-primary);right: var(--el-dialog-padding-primary);}
|
||||
.sc-dialog__headerbtn button {padding: 0;background: transparent;border: none;outline: none;cursor: pointer;font-size: var(--el-message-close-size,16px);margin-left: 15px;color: var(--el-color-info);}
|
||||
.sc-dialog__headerbtn button:hover .el-dialog__close {color: var(--el-color-primary);}
|
||||
.sc-dialog:deep(.el-dialog).is-fullscreen {display: flex;flex-direction: column;top:0px !important;left:0px !important;}
|
||||
.sc-dialog:deep(.el-dialog).is-fullscreen .el-dialog__header {}
|
||||
.sc-dialog:deep(.el-dialog).is-fullscreen .el-dialog__body {flex:1;overflow: auto;}
|
||||
.sc-dialog:deep(.el-dialog).is-fullscreen .el-dialog__footer {padding-bottom: 10px;border-top: 1px solid var(--el-border-color-base);}
|
||||
</style>
|
||||
@@ -0,0 +1,74 @@
|
||||
const T = {
|
||||
"color": [
|
||||
"#409EFF",
|
||||
"#36CE9E",
|
||||
"#f56e6a",
|
||||
"#626c91",
|
||||
"#edb00d",
|
||||
"#909399"
|
||||
],
|
||||
'grid': {
|
||||
'left': '3%',
|
||||
'right': '3%',
|
||||
'bottom': '10',
|
||||
'top': '40',
|
||||
'containLabel': true
|
||||
},
|
||||
"legend": {
|
||||
"textStyle": {
|
||||
"color": "#999"
|
||||
},
|
||||
"inactiveColor": "rgba(128,128,128,0.4)"
|
||||
},
|
||||
"categoryAxis": {
|
||||
"axisLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(128,128,128,0.2)",
|
||||
"width": 1
|
||||
}
|
||||
},
|
||||
"axisTick": {
|
||||
"show": false,
|
||||
"lineStyle": {
|
||||
"color": "#333"
|
||||
}
|
||||
},
|
||||
"axisLabel": {
|
||||
"color": "#999"
|
||||
},
|
||||
"splitLine": {
|
||||
"show": false,
|
||||
"lineStyle": {
|
||||
"color": [
|
||||
"#eee"
|
||||
]
|
||||
}
|
||||
},
|
||||
"splitArea": {
|
||||
"show": false,
|
||||
"areaStyle": {
|
||||
"color": [
|
||||
"rgba(255,255,255,0.01)",
|
||||
"rgba(0,0,0,0.01)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"valueAxis": {
|
||||
"axisLine": {
|
||||
"show": false,
|
||||
"lineStyle": {
|
||||
"color": "#999"
|
||||
}
|
||||
},
|
||||
"splitLine": {
|
||||
"show": true,
|
||||
"lineStyle": {
|
||||
"color": "rgba(128,128,128,0.2)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default T
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div ref="scEcharts" :style="{height:height, width:width}"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
import T from './echarts-theme-T.js';
|
||||
echarts.registerTheme('T', T);
|
||||
const unwarp = (obj) => obj && (obj.__v_raw || obj.valueOf() || obj);
|
||||
|
||||
export default {
|
||||
...echarts,
|
||||
name: "scEcharts",
|
||||
props: {
|
||||
height: { type: String, default: "100%" },
|
||||
width: { type: String, default: "100%" },
|
||||
nodata: {type: Boolean, default: false },
|
||||
option: { type: Object, default: () => {} }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActivat: false,
|
||||
myChart: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
option: {
|
||||
deep:true,
|
||||
handler (v) {
|
||||
unwarp(this.myChart).setOption(v);
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
myOptions: function() {
|
||||
return this.option || {};
|
||||
}
|
||||
},
|
||||
activated(){
|
||||
if(!this.isActivat){
|
||||
this.$nextTick(() => {
|
||||
this.myChart.resize()
|
||||
})
|
||||
}
|
||||
},
|
||||
deactivated(){
|
||||
this.isActivat = false;
|
||||
},
|
||||
mounted(){
|
||||
this.isActivat = true;
|
||||
this.$nextTick(() => {
|
||||
this.draw();
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
draw(){
|
||||
var myChart = echarts.init(this.$refs.scEcharts, 'T');
|
||||
myChart.setOption(this.myOptions);
|
||||
this.myChart = myChart;
|
||||
window.addEventListener('resize', () => myChart.resize());
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,80 @@
|
||||
export default class UploadAdapter {
|
||||
constructor( loader, options ) {
|
||||
this.loader = loader;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
upload() {
|
||||
return this.loader.file
|
||||
.then( file => new Promise( ( resolve, reject ) => {
|
||||
this._initRequest();
|
||||
this._initListeners( resolve, reject, file );
|
||||
this._sendRequest( file );
|
||||
} ) );
|
||||
}
|
||||
|
||||
abort() {
|
||||
if ( this.xhr ) {
|
||||
this.xhr.abort();
|
||||
}
|
||||
}
|
||||
|
||||
_initRequest() {
|
||||
const xhr = this.xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open( 'POST', this.options.upload.uploadUrl, true );
|
||||
xhr.responseType = 'json';
|
||||
}
|
||||
|
||||
_initListeners( resolve, reject, file ) {
|
||||
const xhr = this.xhr;
|
||||
const loader = this.loader;
|
||||
const genericErrorText = `Couldn't upload file: ${ file.name }.`;
|
||||
|
||||
xhr.addEventListener( 'error', () => reject( genericErrorText ) );
|
||||
xhr.addEventListener( 'abort', () => reject() );
|
||||
xhr.addEventListener( 'load', () => {
|
||||
const response = xhr.response;
|
||||
if ( !response || response.code == 0 ) {
|
||||
return reject( response && response.code == 0 ? response.message : genericErrorText );
|
||||
}
|
||||
resolve( {
|
||||
default: response.data.url ? response.data.url : response.data.src
|
||||
} );
|
||||
} );
|
||||
|
||||
if ( xhr.upload ) {
|
||||
xhr.upload.addEventListener( 'progress', evt => {
|
||||
if ( evt.lengthComputable ) {
|
||||
loader.uploadTotal = evt.total;
|
||||
loader.uploaded = evt.loaded;
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
_sendRequest( file ) {
|
||||
// Set headers if specified.
|
||||
const headers = this.options.upload.headers || {};
|
||||
const extendData = this.options.upload.extendData || {};
|
||||
// Use the withCredentials flag if specified.
|
||||
const withCredentials = this.options.upload.withCredentials || false;
|
||||
const uploadName = this.options.upload.uploadName || 'file';
|
||||
for (const headerName of Object.keys(headers)) {
|
||||
this.xhr.setRequestHeader(headerName, headers[headerName]);
|
||||
}
|
||||
this.xhr.withCredentials = withCredentials;
|
||||
const data = new FormData();
|
||||
for (const key of Object.keys(extendData)) {
|
||||
data.append(key, extendData[key]);
|
||||
}
|
||||
data.append( uploadName, file );
|
||||
this.xhr.send( data );
|
||||
}
|
||||
}
|
||||
|
||||
export function UploadAdapterPlugin( editor ) {
|
||||
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
|
||||
return new UploadAdapter( loader, editor.config._config );
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div :style="{'--editor-height': editorHeight}">
|
||||
<ckeditor :editor="editor" v-model="editorData" :config="editorConfig" :disabled="disabled" @blur="onBlur" @focus="onFocus"></ckeditor>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ClassicEditor, Alignment, AutoImage, Autoformat, BlockQuote, Bold, CodeBlock, DataFilter, DataSchema, Essentials, FindAndReplace,
|
||||
FontBackgroundColor, FontColor, FontFamily, FontSize, GeneralHtmlSupport, Heading, Highlight, HorizontalLine, Image, ImageCaption, ImageInsert, ImageResize,
|
||||
ImageStyle, ImageToolbar, ImageUpload, Indent, IndentBlock, Italic, Link, LinkImage, List, MediaEmbed, MediaEmbedToolbar, Mention, Paragraph, PasteFromOffice,
|
||||
RemoveFormat, SelectAll, ShowBlocks, SourceEditing, SpecialCharacters, SpecialCharactersArrows, SpecialCharactersCurrency, SpecialCharactersEssentials, SpecialCharactersLatin,
|
||||
SpecialCharactersMathematical, SpecialCharactersText, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize,
|
||||
TableProperties, TableToolbar, TextTransformation, TodoList, Underline, Undo, WordCount
|
||||
} from 'ckeditor5';
|
||||
import { Ckeditor } from '@ckeditor/ckeditor5-vue';
|
||||
import { UploadAdapterPlugin } from './UploadAdapter.js'
|
||||
|
||||
import coreTranslations from 'ckeditor5/translations/zh-cn.js'
|
||||
import 'ckeditor5/ckeditor5.css';
|
||||
|
||||
export default {
|
||||
name: 'scCkeditor',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入内容……'
|
||||
},
|
||||
toolbar: {
|
||||
type: String,
|
||||
default: 'basic'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '400px'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Ckeditor
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
editorData: '',
|
||||
editor: ClassicEditor,
|
||||
editorHeight: 0,
|
||||
toolbars: {
|
||||
full: [
|
||||
'sourceEditing', 'undo', 'redo', 'heading', 'style',
|
||||
'|', 'superscript', 'subscript', 'removeFormat', 'bold', 'italic', 'underline', 'link', 'fontBackgroundColor', 'fontFamily', 'fontSize', 'fontColor',
|
||||
'|', 'outdent', 'indent', 'alignment', 'bulletedList', 'numberedList', 'todoList',
|
||||
'|', 'blockQuote', 'insertTable', 'imageInsert', 'mediaEmbed', 'highlight', 'horizontalLine', 'selectAll', 'showBlocks', 'specialCharacters', 'codeBlock', 'findAndReplace'
|
||||
],
|
||||
basic: [
|
||||
'sourceEditing', 'undo', 'redo', 'heading',
|
||||
'|', 'removeFormat', 'bold', 'italic', 'underline', 'link', 'fontBackgroundColor', 'fontFamily', 'fontSize', 'fontColor',
|
||||
'|', 'outdent', 'indent', 'alignment', 'bulletedList', 'numberedList', 'todoList',
|
||||
'|', 'insertTable', 'imageInsert', 'mediaEmbed'
|
||||
],
|
||||
simple: [
|
||||
'undo', 'redo', 'heading',
|
||||
'|', 'removeFormat', 'bold', 'italic', 'underline', 'link', 'fontBackgroundColor', 'fontFamily', 'fontSize', 'fontColor',
|
||||
'|', 'insertTable', 'imageInsert', 'mediaEmbed'
|
||||
]
|
||||
},
|
||||
editorConfig: {
|
||||
language: {ui: 'zh-cn', content: 'zh-cn'},
|
||||
translations: [ coreTranslations ],
|
||||
plugins: [ Alignment, AutoImage, Autoformat, BlockQuote, Bold, CodeBlock, DataFilter, DataSchema, Essentials, FindAndReplace,
|
||||
FontBackgroundColor, FontColor, FontFamily, FontSize, GeneralHtmlSupport, Heading, Highlight, HorizontalLine, Image, ImageCaption, ImageInsert, ImageResize,
|
||||
ImageStyle, ImageToolbar, ImageUpload, Indent, IndentBlock, Italic, Link, LinkImage, List, MediaEmbed, MediaEmbedToolbar, Mention, Paragraph, PasteFromOffice,
|
||||
RemoveFormat, SelectAll, ShowBlocks, SourceEditing, SpecialCharacters, SpecialCharactersArrows, SpecialCharactersCurrency, SpecialCharactersEssentials, SpecialCharactersLatin,
|
||||
SpecialCharactersMathematical, SpecialCharactersText, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize,
|
||||
TableProperties, TableToolbar, TextTransformation, TodoList, Underline, Undo, WordCount, UploadAdapterPlugin
|
||||
],
|
||||
toolbar: {shouldNotGroupWhenFull: true},
|
||||
placeholder: '',
|
||||
image: {
|
||||
styles: ['alignLeft', 'alignCenter', 'alignRight'],
|
||||
toolbar: ['imageTextAlternative', 'toggleImageCaption', '|', 'imageStyle:alignLeft', 'imageStyle:alignCenter', 'imageStyle:alignRight', '|', 'linkImage']
|
||||
},
|
||||
mediaEmbed: {
|
||||
providers: [
|
||||
{name: 'mp4', url: /\.(mp4|avi|mov|flv|wmv|mkv)$/i, html: match => {
|
||||
const url = match['input']
|
||||
return ('<video controls width="100%" height="100%" src="${url}"></video>')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
fontSize: {
|
||||
options: [
|
||||
10, 12, 14, 16, 18, 20, 22, 24, 26, 30, 32, 36
|
||||
]
|
||||
},
|
||||
style: {
|
||||
definitions: [
|
||||
{
|
||||
name: 'Article category',
|
||||
element: 'h3',
|
||||
classes: [ 'category' ]
|
||||
},
|
||||
{
|
||||
name: 'Info box',
|
||||
element: 'p',
|
||||
classes: [ 'info-box' ]
|
||||
},
|
||||
]
|
||||
},
|
||||
upload: {
|
||||
uploadUrl: this.$API.common.upload.url,
|
||||
withCredentials: false,
|
||||
extendData: {type: 'images'},
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + this.$TOOL.data.get('TOKEN')
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler(newVal) {
|
||||
this.editorData = newVal ?? ''
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
placeholder: {
|
||||
handler(newVal) {
|
||||
this.editorConfig.placeholder = newVal
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
toolbar: {
|
||||
handler(newVal) {
|
||||
this.editorConfig.toolbar.items = this.toolbars[newVal]
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
height: {
|
||||
handler(newVal) {
|
||||
this.editorHeight = newVal
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
onBlur(event, editor) {
|
||||
this.editorData = this.editorData.replace(/<img[^>]*>/gi, (match) => {
|
||||
return match.replace(/width="[^"]*"/gi, '').replace(/height="[^"]*"/gi, '')
|
||||
})
|
||||
this.$emit('update:modelValue', this.editorData)
|
||||
},
|
||||
onFocus(event, editor) {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root{
|
||||
--ck-z-panel: 9999
|
||||
}
|
||||
.ck-content{
|
||||
height: var(--editor-height);
|
||||
}
|
||||
.ck-source-editing-area, .ck-source-editing-area textarea{height: var(--editor-height);}
|
||||
.ck-source-editing-area textarea{overflow-y: scroll !important;}
|
||||
</style>
|
||||
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<div ref="aiEditor" :style="{height: editorHeight, width: '100%'}"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {AiEditor} from "aieditor";
|
||||
import "aieditor/dist/style.css"
|
||||
|
||||
export default {
|
||||
name: 'scCkeditor',
|
||||
emits: ['update:modelValue'],
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入内容……'
|
||||
},
|
||||
toolbar: {
|
||||
type: String,
|
||||
default: 'basic'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '400px'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
defaultValue: '',
|
||||
editorHeight: '300px',
|
||||
toolbars: {
|
||||
full: [
|
||||
"undo", "redo", "brush", "eraser",
|
||||
"|", "heading", "font-family", "font-size",
|
||||
"|", "bold", "italic", "underline", "strike", "link", "code", "subscript", "superscript", "hr", "todo", "emoji",
|
||||
"|", "highlight", "font-color",
|
||||
"|", "align", "line-height",
|
||||
"|", "bullet-list", "ordered-list", "indent-decrease", "indent-increase", "break",
|
||||
"|", "image", "video", "attachment", "quote", "code-block", "table",
|
||||
"|", "source-code", "printer", "fullscreen", "ai"
|
||||
],
|
||||
basic: [
|
||||
"undo", "redo", "brush", "eraser",
|
||||
"|", "heading", "font-family", "font-size",
|
||||
"|", "bold", "italic", "underline", "strike", "link", "code", "subscript", "superscript", "hr", "todo", "emoji",
|
||||
"|", "highlight", "font-color",
|
||||
"|", "align", "line-height",
|
||||
"|", "bullet-list", "ordered-list", "indent-decrease", "indent-increase", "break",
|
||||
"|", "image", "video", "attachment", "quote", "code-block", "table",
|
||||
],
|
||||
simple: ["undo", "redo",
|
||||
"|", "font-family", "font-size",
|
||||
"|", "bold", "italic", "underline", "strike", "emoji",
|
||||
"|", "highlight", "font-color",
|
||||
"|", "image", "link"]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(newValue) {
|
||||
if (newValue !== this.defaultValue) {
|
||||
this.defaultValue = newValue
|
||||
}
|
||||
},
|
||||
height: {
|
||||
handler(newValue) {
|
||||
if (indexOf(newValue, 'px') === -1){
|
||||
this.editorHeight = newValue > 200 ? newValue + 'px' : '200px'
|
||||
}else{
|
||||
newValue = newValue.replace('px', '')
|
||||
this.editorHeight = newValue > 200 ? newValue + 'px' : '200px'
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.editor = new AiEditor({
|
||||
element: this.$refs.aiEditor,
|
||||
placeholder: this.placeholder,
|
||||
content: this.defaultValue,
|
||||
toolbarKeys: this.toolbars[this.toolbar],
|
||||
onChange: (editor) => {
|
||||
this.$emit('update:modelValue', editor.getHtml())
|
||||
},
|
||||
editable: !this.disabled,
|
||||
textSelectionBubbleMenu: {
|
||||
enable: true,
|
||||
items: ["Bold", "Italic", "Underline", "Strike", "code", "comment"],
|
||||
},
|
||||
image: {
|
||||
uploadUrl: this.$API.common.upload.url,
|
||||
uploadHeaders: {
|
||||
Authorization: 'Bearer ' + this.$TOOL.data.get('TOKEN')
|
||||
},
|
||||
uploadFormName: 'file',
|
||||
uploader: (file, uploadUrl, headers, formName) => {
|
||||
const formData = new FormData();
|
||||
formData.append(formName, file);
|
||||
formData.append('type', 'images');
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(uploadUrl, {
|
||||
method: "post",
|
||||
headers: {'Accept': 'application/json', ...headers},
|
||||
body: formData,
|
||||
}).then((resp) => resp.json())
|
||||
.then(json => {
|
||||
resolve({json});
|
||||
}).catch((error) => {
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
},
|
||||
uploaderEvent: {
|
||||
onSuccess: (file, response) => {
|
||||
let res = response.json
|
||||
if (res.code === 1) {
|
||||
return {errorCode: 0, data: {
|
||||
src: res.data.url
|
||||
}}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
video: {
|
||||
uploadUrl: this.$API.common.upload.url,
|
||||
uploadHeaders: {
|
||||
Authorization: 'Bearer ' + this.$TOOL.data.get('TOKEN')
|
||||
},
|
||||
uploadFormName: 'file',
|
||||
uploader: (file, uploadUrl, headers, formName) => {
|
||||
const formData = new FormData();
|
||||
formData.append(formName, file);
|
||||
formData.append('type', 'video');
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(uploadUrl, {
|
||||
method: "post",
|
||||
headers: {'Accept': 'application/json', ...headers},
|
||||
body: formData,
|
||||
}).then((resp) => resp.json())
|
||||
.then(json => {
|
||||
resolve(json);
|
||||
}).catch((error) => {
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
},
|
||||
uploaderEvent: {
|
||||
onSuccess: (file, response) => {
|
||||
let res = response.json
|
||||
if (res.code === 1) {
|
||||
return {errorCode: 0, data: {
|
||||
src: res.data.url
|
||||
}}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
attachment: {
|
||||
uploadUrl: this.$API.common.upload.url,
|
||||
uploadHeaders: {
|
||||
Authorization: 'Bearer ' + this.$TOOL.data.get('TOKEN')
|
||||
},
|
||||
uploadFormName: 'file',
|
||||
uploader: (file, uploadUrl, headers, formName) => {
|
||||
const formData = new FormData();
|
||||
formData.append(formName, file);
|
||||
formData.append('type', 'files');
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(uploadUrl, {
|
||||
method: "post",
|
||||
headers: {'Accept': 'application/json', ...headers},
|
||||
body: formData,
|
||||
}).then((resp) => resp.json())
|
||||
.then(json => {
|
||||
resolve(json);
|
||||
}).catch((error) => {
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
},
|
||||
uploaderEvent: {
|
||||
onSuccess: (file, response) => {
|
||||
let res = response.json
|
||||
if (res.code === 1) {
|
||||
return {errorCode: 0, data: {
|
||||
src: res.data.url
|
||||
}}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
<template>
|
||||
<el-table ref="table" :data="columnData" row-key="prop" style="width: 100%" border>
|
||||
<el-table-column prop="" label="排序" width="60">
|
||||
<el-tag class="move" style="cursor: move;"><el-icon style="cursor: move;"><el-icon-d-caret/></el-icon></el-tag>
|
||||
</el-table-column>
|
||||
<el-table-column prop="label" label="列名">
|
||||
<template #default="scope">
|
||||
<el-tag round :effect="scope.row.hide?'light':'dark'" :type="scope.row.hide?'info':''">{{ scope.row.label }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="hide" label="显示" width="60">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.hide" :active-value="false" :inactive-value="true"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export default {
|
||||
emits: ['success'],
|
||||
props: {
|
||||
column: { type: Array, default: () => [] }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columnData: this.column
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.rowDrop()
|
||||
},
|
||||
methods: {
|
||||
rowDrop(){
|
||||
const _this = this
|
||||
const tbody = this.$refs.table.$el.querySelector('.el-table__body-wrapper tbody')
|
||||
Sortable.create(tbody, {
|
||||
handle: ".move",
|
||||
animation: 200,
|
||||
ghostClass: "ghost",
|
||||
onEnd({ newIndex, oldIndex }) {
|
||||
const tableData = _this.columnData
|
||||
const currRow = tableData.splice(oldIndex, 1)[0]
|
||||
tableData.splice(newIndex, 0, currRow)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,199 @@
|
||||
<!--
|
||||
* @Descripttion: 文件导出
|
||||
* @version: 1.1
|
||||
* @Author: sakuya
|
||||
* @Date: 2022年5月24日16:20:12
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年6月13日17:32:05
|
||||
-->
|
||||
|
||||
<template>
|
||||
<slot :open="open">
|
||||
<el-button type="primary" plain @click="open">导出</el-button>
|
||||
</slot>
|
||||
<el-drawer v-model="dialog" title="导出" :size="400" direction="rtl" append-to-body destroy-on-close>
|
||||
<el-main style="padding: 0 20px 20px 20px;">
|
||||
<div v-loading="downLoading" element-loading-text="正在处理中...">
|
||||
<div v-if="downLoading && progress" style="position: absolute;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;z-index: 3000;">
|
||||
<el-progress :text-inside="true" :stroke-width="20" :percentage="downLoadProgress" style="width: 100%;margin-bottom: 120px;"/>
|
||||
</div>
|
||||
<el-tabs>
|
||||
<el-tab-pane label="常规" lazy>
|
||||
<el-form label-width="100px" label-position="left" style="margin: 10px 0 20px 0;">
|
||||
<el-form-item label="文件名">
|
||||
<el-input v-model="formData.fileName" placeholder="请输入文件名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文件类型">
|
||||
<el-select v-model="formData.fileType" placeholder="请选择文件类型">
|
||||
<el-option v-for="item in fileTypes" :key="item" :label="'*.'+item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<slot name="form" :formData="formData"></slot>
|
||||
</el-form>
|
||||
<el-button v-if="async" type="primary" icon="el-icon-plus" style="width: 100%;" @click="download" :loading="asyncLoading">发起导出任务</el-button>
|
||||
<el-button v-else type="primary" icon="el-icon-download" style="width: 100%;" @click="download">下 载</el-button>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="列设置" v-if="columnData.length>0" lazy>
|
||||
<columnSet :column="columnData"></columnSet>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="其他参数" v-if="data && showData" lazy>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item v-for=" (val, key) in data" :key="key" :label="key">{{val}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-main>
|
||||
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import columnSet from './column'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
columnSet
|
||||
},
|
||||
props: {
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
fileName: { type: String, default: "" },
|
||||
fileTypes: { type: Array, default: () => ['xlsx'] },
|
||||
data: { type: Object, default: () => {} },
|
||||
showData: { type: Boolean, default: false },
|
||||
async: { type: Boolean, default: false },
|
||||
column: { type: Array, default: () => [] },
|
||||
blob: { type: Boolean, default: false },
|
||||
progress: { type: Boolean, default: true }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
formData: {
|
||||
fileName: this.fileName,
|
||||
fileType: this.fileTypes[0]
|
||||
},
|
||||
columnData: [],
|
||||
downLoading: false,
|
||||
downLoadProgress: 0,
|
||||
asyncLoading: false
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
'formData.fileType'(val) {
|
||||
if(this.formData.fileName.includes(".")){
|
||||
this.formData.fileName = this.formData.fileName.substring(0, this.formData.fileName.lastIndexOf('.')) + "." + val
|
||||
}else{
|
||||
this.formData.fileName = this.formData.fileName + "." + val
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.dialog = true
|
||||
this.formData = {
|
||||
fileName: (this.fileName?this.fileName:(new Date().getTime()+'')) + "." + this.fileTypes[0],
|
||||
fileType: this.fileTypes[0]
|
||||
}
|
||||
this.columnData = JSON.parse(JSON.stringify(this.column))
|
||||
},
|
||||
close() {
|
||||
this.dialog = false
|
||||
},
|
||||
download() {
|
||||
let columnArr = {
|
||||
column: this.columnData.filter(n => !n.hide).map(n => n.prop).join(",")
|
||||
}
|
||||
let assignData = {...this.data, ...this.formData, ...columnArr}
|
||||
if(this.async){
|
||||
this.asyncDownload(this.apiObj, this.formData.fileName, assignData)
|
||||
}else if(this.blob){
|
||||
this.downloadFile(this.apiObj, this.formData.fileName, assignData)
|
||||
}else{
|
||||
this.linkFile(this.apiObj.url, this.formData.fileName, assignData)
|
||||
}
|
||||
},
|
||||
linkFile(url, fileName, data={}){
|
||||
let a = document.createElement("a")
|
||||
a.style = "display: none"
|
||||
a.target = "_blank"
|
||||
//a.download = fileName
|
||||
a.href = url + this.toQueryString(data)
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
},
|
||||
downloadFile(apiObj, fileName, data={}){
|
||||
this.downLoading = true
|
||||
var _this = this
|
||||
apiObj.get(data, {
|
||||
responseType: 'blob',
|
||||
onDownloadProgress(e){
|
||||
if(e.lengthComputable){
|
||||
_this.downLoadProgress = parseInt(e.loaded / e.total * 100)
|
||||
}
|
||||
}
|
||||
}).then(res => {
|
||||
this.downLoading = false
|
||||
this.downLoadProgress = 0
|
||||
// let url = URL.createObjectURL(res.data)
|
||||
let url = res.data
|
||||
let a = document.createElement("a")
|
||||
a.style = "display: none"
|
||||
a.target = "_blank"
|
||||
a.download = fileName
|
||||
a.href = url
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
// URL.revokeObjectURL(url)
|
||||
}).catch(err => {
|
||||
this.downLoading = false
|
||||
this.downLoadProgress = 0
|
||||
this.$notify.error({
|
||||
title: '下载文件失败',
|
||||
message: err
|
||||
})
|
||||
})
|
||||
},
|
||||
asyncDownload(apiObj, fileName, data={}){
|
||||
this.asyncLoading = true
|
||||
apiObj.get(data).then(res => {
|
||||
this.asyncLoading = false
|
||||
if(res.code == 1){
|
||||
this.dialog = false
|
||||
this.$msgbox({
|
||||
title: "成功发起任务",
|
||||
message: `<div><img style="height:200px" :src="'static/images/tasks-example.png'"/></div><p>已成功发起导出任务,您可以操作其他事务</p><p>稍后可在 <b>任务中心</b> 查看执行结果</p>`,
|
||||
type: "success",
|
||||
confirmButtonText: "知道了",
|
||||
dangerouslyUseHTMLString: true,
|
||||
center: true
|
||||
}).catch(() => {})
|
||||
}else{
|
||||
this.$alert(res.message || "未知错误", "发起任务失败", {
|
||||
type: "error",
|
||||
center: true
|
||||
}).catch(() => {})
|
||||
}
|
||||
}).catch(() => {
|
||||
this.asyncLoading = false
|
||||
})
|
||||
},
|
||||
toQueryString(obj){
|
||||
let arr = []
|
||||
for (var k in obj) {
|
||||
arr.push(`${k}=${obj[k]}`)
|
||||
}
|
||||
return (arr.length>0?"?":"") + arr.join('&')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,133 @@
|
||||
<!--
|
||||
* @Descripttion: 文件导入
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2022年5月24日11:30:03
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<slot :open="open">
|
||||
<el-button type="primary" plain @click="open">导入</el-button>
|
||||
</slot>
|
||||
<el-dialog v-model="dialog" title="导入" :width="550" :close-on-click-modal="false" append-to-body destroy-on-close>
|
||||
<el-progress v-if="loading" :text-inside="true" :stroke-width="20" :percentage="percentage" style="margin-bottom: 15px;"/>
|
||||
<div v-loading="loading">
|
||||
<el-upload ref="uploader"
|
||||
drag
|
||||
:accept="accept"
|
||||
:maxSize="maxSize"
|
||||
:limit="1"
|
||||
:data="data"
|
||||
:show-file-list="false"
|
||||
:http-request="request"
|
||||
:before-upload="before"
|
||||
:on-progress="progress"
|
||||
:on-success="success"
|
||||
:on-error="error"
|
||||
>
|
||||
<slot name="uploader">
|
||||
<el-icon class="el-icon--upload"><el-icon-upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
将文件拖到此处或 <em>点击选择文件上传</em>
|
||||
</div>
|
||||
</slot>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
<template v-if="tip">{{tip}}</template>
|
||||
<template v-else>请上传小于或等于 {{maxSize}}M 的 {{accept}} 格式文件</template>
|
||||
<p v-if="templateUrl" style="margin-top: 7px;">
|
||||
<el-link :href="templateUrl" target="_blank" type="primary" :underline="false">下载导入模板</el-link>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<el-form v-if="$slots.form" inline label-width="100px" label-position="left" style="margin-top: 18px;">
|
||||
<slot name="form" :formData="formData"></slot>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['success'],
|
||||
props: {
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
data: { type: Object, default: () => {} },
|
||||
accept: { type: String, default: ".xls, .xlsx" },
|
||||
maxSize: { type: Number, default: 10 },
|
||||
tip: { type: String, default: "" },
|
||||
templateUrl: { type: String, default: "" }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
loading: false,
|
||||
percentage: 0,
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
open(){
|
||||
this.dialog = true
|
||||
this.formData = {}
|
||||
},
|
||||
close(){
|
||||
this.dialog = false
|
||||
},
|
||||
before(file){
|
||||
const maxSize = file.size / 1024 / 1024 < this.maxSize;
|
||||
if (!maxSize) {
|
||||
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
|
||||
return false;
|
||||
}
|
||||
this.loading = true
|
||||
},
|
||||
progress(e){
|
||||
this.percentage = e.percent
|
||||
},
|
||||
success(res, file){
|
||||
this.$refs.uploader.handleRemove(file)
|
||||
this.$refs.uploader.clearFiles()
|
||||
this.loading = false
|
||||
this.percentage = 0
|
||||
this.$emit('success', res, this.close)
|
||||
},
|
||||
error(err){
|
||||
this.loading = false
|
||||
this.percentage = 0
|
||||
this.$notify.error({
|
||||
title: '上传文件未成功',
|
||||
message: err
|
||||
})
|
||||
},
|
||||
request(param){
|
||||
Object.assign(param.data, this.formData)
|
||||
const data = new FormData();
|
||||
data.append(param.filename, param.file);
|
||||
for (const key in param.data) {
|
||||
data.append(key, param.data[key]);
|
||||
}
|
||||
this.apiObj.post(data, {
|
||||
onUploadProgress: e => {
|
||||
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
|
||||
param.onProgress({percent: complete})
|
||||
}
|
||||
}).then(res => {
|
||||
param.onSuccess(res)
|
||||
}).catch(err => {
|
||||
param.onError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,283 @@
|
||||
<!--
|
||||
* @Descripttion: 资源文件选择器
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年10月11日16:01:40
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-file-select">
|
||||
<div class="sc-file-select__side" v-loading="menuLoading">
|
||||
<div class="sc-file-select__side-menu">
|
||||
<el-tree ref="group" class="menu" :data="menu" :node-key="treeProps.key" :props="treeProps" :current-node-key="menu.length>0?menu[0][treeProps.key]:''" highlight-current @node-click="groupClick">
|
||||
<template #default="{ node }">
|
||||
<span class="el-tree-node__label">
|
||||
<el-icon class="icon"><el-icon-folder /></el-icon>{{node.label}}
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
<div class="sc-file-select__side-msg" v-if="multiple">
|
||||
已选择 <b>{{value.length}}</b> / <b>{{max}}</b> 项
|
||||
</div>
|
||||
</div>
|
||||
<div class="sc-file-select__files" v-loading="listLoading">
|
||||
<div class="sc-file-select__top">
|
||||
<div class="upload" v-if="!hideUpload">
|
||||
<el-upload class="sc-file-select__upload" action="" multiple :show-file-list="false" :accept="accept" :on-change="uploadChange" :before-upload="uploadBefore" :on-progress="uploadProcess" :on-success="uploadSuccess" :on-error="uploadError" :http-request="uploadRequest">
|
||||
<el-button type="primary" icon="el-icon-upload">本地上传</el-button>
|
||||
</el-upload>
|
||||
<span class="tips"><el-icon><el-icon-warning /></el-icon>大小不超过{{maxSize}}MB</span>
|
||||
</div>
|
||||
<div class="keyword">
|
||||
<el-input v-model="keyword" prefix-icon="el-icon-search" placeholder="文件名搜索" clearable @keyup.enter="search" @clear="search"></el-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sc-file-select__list">
|
||||
<el-scrollbar ref="scrollbar">
|
||||
<el-empty v-if="fileList.length==0 && data.length==0" description="无数据" :image-size="80"></el-empty>
|
||||
<div v-for="(file, index) in fileList" :key="index" class="sc-file-select__item">
|
||||
<div class="sc-file-select__item__file">
|
||||
<div class="sc-file-select__item__upload">
|
||||
<el-progress type="circle" :percentage="file.progress" :width="70"></el-progress>
|
||||
</div>
|
||||
<el-image :src="file.tempImg" fit="contain"></el-image>
|
||||
</div>
|
||||
<p>{{file.name}}</p>
|
||||
</div>
|
||||
<div v-for="item in data" :key="item[fileProps.key]" class="sc-file-select__item" :class="{active: value.includes(item[fileProps.url]) }" @click="select(item)">
|
||||
<div class="sc-file-select__item__file">
|
||||
<div class="sc-file-select__item__checkbox" v-if="multiple">
|
||||
<el-icon><el-icon-check /></el-icon>
|
||||
</div>
|
||||
<div class="sc-file-select__item__select" v-else>
|
||||
<el-icon><el-icon-check /></el-icon>
|
||||
</div>
|
||||
<div class="sc-file-select__item__box"></div>
|
||||
<el-image v-if="_isImg(item[fileProps.url])" :src="item[fileProps.url]" fit="contain" lazy></el-image>
|
||||
<div v-else class="item-file item-file-doc">
|
||||
<i v-if="files[_getExt(item[fileProps.url])]" :class="files[_getExt(item[fileProps.url])].icon" :style="{color:files[_getExt(item[fileProps.url])].color}"></i>
|
||||
<i v-else class="sc-icon-file-list-fill" style="color: #999;"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p :title="item[fileProps.fileName]">{{item[fileProps.fileName]}}</p>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="sc-file-select__pagination">
|
||||
<el-pagination small background layout="prev, pager, next" :total="total" :page-size="pageSize" v-model:currentPage="currentPage" @current-change="reload"></el-pagination>
|
||||
</div>
|
||||
<div class="sc-file-select__do">
|
||||
<slot name="do"></slot>
|
||||
<el-button type="primary" :disabled="value.length<=0" @click="submit">确 定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/fileSelect"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: null,
|
||||
hideUpload: { type: Boolean, default: false },
|
||||
multiple: { type: Boolean, default: false },
|
||||
max: {type: Number, default: config.max},
|
||||
onlyImage: { type: Boolean, default: false },
|
||||
maxSize: {type: Number, default: config.maxSize},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyword: null,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
data: [],
|
||||
menu: [],
|
||||
menuId: '',
|
||||
value: this.multiple ? [] : '',
|
||||
fileList: [],
|
||||
accept: this.onlyImage ? "image/gif, image/jpeg, image/png" : "",
|
||||
listLoading: false,
|
||||
menuLoading: false,
|
||||
treeProps: config.menuProps,
|
||||
fileProps: config.fileProps,
|
||||
files: config.files
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
multiple(){
|
||||
this.value = this.multiple ? [] : ''
|
||||
this.$emit('update:modelValue', JSON.parse(JSON.stringify(this.value)));
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getMenu()
|
||||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
//获取分类数据
|
||||
async getMenu(){
|
||||
this.menuLoading = true
|
||||
var res = await config.menuApiObj.get()
|
||||
this.menu = res.data
|
||||
this.menuLoading = false
|
||||
},
|
||||
//获取列表数据
|
||||
async getData(){
|
||||
this.listLoading = true
|
||||
var reqData = {
|
||||
[config.request.menuKey]: this.menuId,
|
||||
[config.request.page]: this.currentPage,
|
||||
[config.request.pageSize]: this.pageSize,
|
||||
[config.request.keyword]: this.keyword
|
||||
}
|
||||
if(this.onlyImage){
|
||||
reqData.type = 'image'
|
||||
}
|
||||
var res = await config.listApiObj.get(reqData)
|
||||
var parseData = config.listParseData(res)
|
||||
this.data = parseData.rows
|
||||
this.total = parseData.total
|
||||
this.listLoading = false
|
||||
this.$refs.scrollbar.setScrollTop(0)
|
||||
},
|
||||
//树点击事件
|
||||
groupClick(data){
|
||||
this.menuId = data.id
|
||||
this.currentPage = 1
|
||||
this.keyword = null
|
||||
this.getData()
|
||||
},
|
||||
//分页刷新表格
|
||||
reload(){
|
||||
this.getData()
|
||||
},
|
||||
search(){
|
||||
this.currentPage = 1
|
||||
this.getData()
|
||||
},
|
||||
select(item){
|
||||
const itemUrl = item[this.fileProps.url]
|
||||
if(this.multiple){
|
||||
if(this.value.includes(itemUrl)){
|
||||
this.value.splice(this.value.findIndex(f => f == itemUrl), 1)
|
||||
}else{
|
||||
this.value.push(itemUrl)
|
||||
}
|
||||
}else{
|
||||
if(this.value.includes(itemUrl)){
|
||||
this.value = ''
|
||||
}else{
|
||||
this.value = itemUrl
|
||||
}
|
||||
}
|
||||
},
|
||||
submit(){
|
||||
const value = JSON.parse(JSON.stringify(this.value))
|
||||
this.$emit('update:modelValue', value);
|
||||
this.$emit('submit', value);
|
||||
},
|
||||
//上传处理
|
||||
uploadChange(file, fileList){
|
||||
file.tempImg = URL.createObjectURL(file.raw);
|
||||
this.fileList = fileList
|
||||
},
|
||||
uploadBefore(file){
|
||||
const maxSize = file.size / 1024 / 1024 < this.maxSize;
|
||||
if (!maxSize) {
|
||||
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
uploadRequest(param){
|
||||
var apiObj = config.apiObj;
|
||||
const data = new FormData();
|
||||
data.append("file", param.file);
|
||||
data.append([config.request.menuKey], this.menuId);
|
||||
apiObj.post(data, {
|
||||
onUploadProgress: e => {
|
||||
param.onProgress(e)
|
||||
}
|
||||
}).then(res => {
|
||||
param.onSuccess(res)
|
||||
}).catch(err => {
|
||||
param.onError(err)
|
||||
})
|
||||
},
|
||||
uploadProcess(event, file){
|
||||
file.progress = Number((event.loaded / event.total * 100).toFixed(2))
|
||||
},
|
||||
uploadSuccess(res, file){
|
||||
this.fileList.splice(this.fileList.findIndex(f => f.uid == file.uid), 1)
|
||||
var response = config.uploadParseData(res);
|
||||
this.data.unshift({
|
||||
[this.fileProps.key]: response.id,
|
||||
[this.fileProps.fileName]: response.fileName,
|
||||
[this.fileProps.url]: response.url
|
||||
})
|
||||
if(!this.multiple){
|
||||
this.value = response.url
|
||||
}
|
||||
},
|
||||
uploadError(err){
|
||||
this.$notify.error({
|
||||
title: '上传文件错误',
|
||||
message: err
|
||||
})
|
||||
},
|
||||
//内置函数
|
||||
_isImg(fileUrl){
|
||||
const imgExt = ['.jpg', '.jpeg', '.png', '.gif', '.bmp']
|
||||
const fileExt = fileUrl.substring(fileUrl.lastIndexOf("."))
|
||||
return imgExt.indexOf(fileExt) != -1
|
||||
},
|
||||
_getExt(fileUrl){
|
||||
return fileUrl.substring(fileUrl.lastIndexOf(".") + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-file-select {display: flex;}
|
||||
.sc-file-select__files {flex: 1;}
|
||||
|
||||
.sc-file-select__list {height:400px;}
|
||||
.sc-file-select__item {display: inline-block;float: left;margin:0 15px 25px 0;width:110px;cursor: pointer;}
|
||||
.sc-file-select__item__file {width:110px;height:110px;position: relative;}
|
||||
.sc-file-select__item__file .el-image {width:110px;height:110px;}
|
||||
.sc-file-select__item__box {position: absolute;top:0;right:0;bottom:0;left:0;border: 2px solid var(--el-color-success);z-index: 1;display: none;}
|
||||
.sc-file-select__item__box::before {content: '';position: absolute;top:0;right:0;bottom:0;left:0;background: var(--el-color-success);opacity: 0.2;display: none;}
|
||||
.sc-file-select__item:hover .sc-file-select__item__box {display: block;}
|
||||
.sc-file-select__item.active .sc-file-select__item__box {display: block;}
|
||||
.sc-file-select__item.active .sc-file-select__item__box::before {display: block;}
|
||||
.sc-file-select__item p {margin-top: 10px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;-webkit-text-overflow:ellipsis;text-align: center;}
|
||||
.sc-file-select__item__checkbox {position: absolute;width: 20px;height: 20px;top:7px;right:7px;z-index: 2;background: rgba(0,0,0,0.2);border: 1px solid #fff;display: flex;flex-direction: column;align-items: center;justify-content: center;}
|
||||
.sc-file-select__item__checkbox i {font-size: 14px;color: #fff;font-weight: bold;display: none;}
|
||||
.sc-file-select__item__select {position: absolute;width: 20px;height: 20px;top:0px;right:0px;z-index: 2;background: var(--el-color-success);display: none;flex-direction: column;align-items: center;justify-content: center;}
|
||||
.sc-file-select__item__select i {font-size: 14px;color: #fff;font-weight: bold;}
|
||||
.sc-file-select__item.active .sc-file-select__item__checkbox {background: var(--el-color-success);}
|
||||
.sc-file-select__item.active .sc-file-select__item__checkbox i {display: block;}
|
||||
.sc-file-select__item.active .sc-file-select__item__select {display: flex;}
|
||||
.sc-file-select__item__file .item-file {width:110px;height:110px;display: flex;flex-direction: column;align-items: center;justify-content: center;}
|
||||
.sc-file-select__item__file .item-file i {font-size: 40px;}
|
||||
.sc-file-select__item__file .item-file.item-file-doc {color: #409eff;}
|
||||
|
||||
.sc-file-select__item__upload {position: absolute;top:0;right:0;bottom:0;left:0;z-index: 1;background: rgba(255,255,255,0.7);display: flex;flex-direction: column;align-items: center;justify-content: center;}
|
||||
|
||||
.sc-file-select__side {width: 200px;margin-right: 15px;border-right: 1px solid rgba(128,128,128,0.2);display: flex;flex-flow: column;}
|
||||
.sc-file-select__side-menu {flex: 1;}
|
||||
.sc-file-select__side-msg {height:32px;line-height: 32px;}
|
||||
|
||||
.sc-file-select__top {margin-bottom: 15px;display: flex;justify-content: space-between;}
|
||||
.sc-file-select__upload {display: inline-block;}
|
||||
.sc-file-select__top .tips {font-size: 12px;margin-left: 10px;color: #999;}
|
||||
.sc-file-select__top .tips i {font-size: 14px;margin-right: 5px;position: relative;bottom: -0.125em;}
|
||||
.sc-file-select__pagination {margin:15px 0;}
|
||||
|
||||
.sc-file-select__do {text-align: right;}
|
||||
</style>
|
||||
@@ -0,0 +1,310 @@
|
||||
<!--
|
||||
* @Descripttion: 过滤器V2
|
||||
* @version: 2.5
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年7月30日14:48:41
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年5月13日21:15:44
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-filterBar">
|
||||
<slot :filterLength="filterObjLength" :openFilter="openFilter">
|
||||
<el-badge :value="filterObjLength" type="danger" :hidden="filterObjLength<=0">
|
||||
<el-button icon="el-icon-filter" @click="openFilter"></el-button>
|
||||
</el-badge>
|
||||
</slot>
|
||||
|
||||
<el-drawer title="过滤器" v-model="drawer" :size="650" append-to-body>
|
||||
<el-container v-loading="saveLoading">
|
||||
<el-main style="padding:0">
|
||||
<el-tabs class="root">
|
||||
<el-tab-pane lazy>
|
||||
<template #label>
|
||||
<div class="tabs-label">过滤项</div>
|
||||
</template>
|
||||
<el-scrollbar>
|
||||
<div class="sc-filter-main">
|
||||
<h2>设置过滤条件</h2>
|
||||
<div v-if="filter.length<=0" class="nodata">
|
||||
没有默认过滤条件,请点击增加过滤项
|
||||
</div>
|
||||
<table v-else>
|
||||
<colgroup>
|
||||
<col width="50">
|
||||
<col width="140">
|
||||
<col v-if="showOperator" width="120">
|
||||
<col>
|
||||
<col width="40">
|
||||
</colgroup>
|
||||
<tr v-for="(item,index) in filter" :key="index">
|
||||
<td>
|
||||
<el-tag>{{index+1}}</el-tag>
|
||||
</td>
|
||||
<td>
|
||||
<py-select v-model="item.field" :options="fields" placeholder="过滤字段" filterable @change="fieldChange(item)">
|
||||
</py-select>
|
||||
</td>
|
||||
<td v-if="showOperator">
|
||||
<el-select v-model="item.operator" placeholder="运算符">
|
||||
<el-option v-for="ope in item.field.operators || operator" :key="ope.value" :label="ope.label" :value="ope.value"></el-option>
|
||||
</el-select>
|
||||
</td>
|
||||
<td>
|
||||
<el-input v-if="!item.field.type" v-model="item.value" placeholder="请选择过滤字段" disabled></el-input>
|
||||
<!-- 输入框 -->
|
||||
<el-input v-if="item.field.type=='text'" v-model="item.value" :placeholder="item.field.placeholder||'请输入'"></el-input>
|
||||
<!-- 下拉框 -->
|
||||
<el-select v-if="item.field.type=='select'" v-model="item.value" :placeholder="item.field.placeholder||'请选择'" filterable :multiple="item.field.extend.multiple" :loading="item.selectLoading" @visible-change="visibleChange($event, item)" :remote="item.field.extend.remote" :remote-method="(query)=>{remoteMethod(query, item)}">
|
||||
<el-option v-for="field in item.field.extend.data" :key="field.value" :label="field.label" :value="field.value"></el-option>
|
||||
</el-select>
|
||||
<!-- 日期 -->
|
||||
<el-date-picker v-if="item.field.type=='date'" v-model="item.value" type="date" value-format="YYYY-MM-DD" :placeholder="item.field.placeholder||'请选择日期'" style="width: 100%;"></el-date-picker>
|
||||
<!-- 日期范围 -->
|
||||
<el-date-picker v-if="item.field.type=='daterange'" v-model="item.value" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100%;"></el-date-picker>
|
||||
<!-- 日期时间 -->
|
||||
<el-date-picker v-if="item.field.type=='datetime'" v-model="item.value" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" :placeholder="item.field.placeholder||'请选择日期'" style="width: 100%;"></el-date-picker>
|
||||
<!-- 日期时间范围 -->
|
||||
<el-date-picker v-if="item.field.type=='datetimerange'" v-model="item.value" type="datetimerange" value-format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100%;"></el-date-picker>
|
||||
<!-- 开关 -->
|
||||
<el-switch v-if="item.field.type=='switch'" v-model="item.value" active-value="1" inactive-value="0"></el-switch>
|
||||
<!-- 标签 -->
|
||||
<el-select v-if="item.field.type=='tags'" v-model="item.value" multiple filterable allow-create default-first-option no-data-text="输入关键词后按回车确认" :placeholder="item.field.placeholder||'请输入'"></el-select>
|
||||
</td>
|
||||
<td>
|
||||
<el-icon class="del" @click="delFilter(index)"><el-icon-delete /></el-icon>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<el-button type="primary" text icon="el-icon-plus" @click="addFilter">增加过滤项</el-button>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane lazy>
|
||||
<template #label>
|
||||
<div class="tabs-label">常用</div>
|
||||
</template>
|
||||
<el-scrollbar>
|
||||
<my ref="my" :data="myFilter" :filterName="filterName" @selectMyfilter="selectMyfilter"></my>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<el-button type="primary" @click="ok" :disabled="filter.length<=0">立即过滤</el-button>
|
||||
<el-button type="primary" plain @click="saveMy" :disabled="filter.length<=0">另存为常用</el-button>
|
||||
<el-button @click="clear">清空过滤</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/filterBar"
|
||||
import pySelect from './pySelect'
|
||||
import my from './my'
|
||||
|
||||
export default {
|
||||
name: 'filterBar',
|
||||
components: {
|
||||
pySelect,
|
||||
my
|
||||
},
|
||||
props: {
|
||||
filterName: { type: String, default: "" },
|
||||
showOperator: { type: Boolean, default: true },
|
||||
options: { type: Object, default: () => {} }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
drawer: false,
|
||||
operator: config.operator,
|
||||
fields: this.options,
|
||||
filter: [],
|
||||
myFilter: [],
|
||||
filterObjLength: 0,
|
||||
saveLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filterObj(){
|
||||
const obj = {}
|
||||
this.filter.forEach((item) => {
|
||||
obj[item.field.value] = this.showOperator ? `${item.value}${config.separator}${item.operator}` : `${item.value}`
|
||||
})
|
||||
return obj
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
//默认显示的过滤项
|
||||
this.fields.forEach((item) => {
|
||||
if(item.selected){
|
||||
this.filter.push({
|
||||
field: item,
|
||||
operator: item.operator || 'include',
|
||||
value: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
//打开过滤器
|
||||
openFilter(){
|
||||
this.drawer = true
|
||||
},
|
||||
//增加过滤项
|
||||
addFilter(){
|
||||
if(this.fields.length<=0){
|
||||
this.$message.warning('无过滤项');
|
||||
return false
|
||||
}
|
||||
const filterNum = this.fields[this.filter.length] || this.fields[0]
|
||||
this.filter.push({
|
||||
field: filterNum,
|
||||
operator: filterNum.operator || 'include',
|
||||
value: ''
|
||||
})
|
||||
},
|
||||
//删除过滤项
|
||||
delFilter(index){
|
||||
this.filter.splice(index, 1)
|
||||
},
|
||||
//过滤项字段变更事件
|
||||
fieldChange(tr){
|
||||
let oldType = tr.field.type
|
||||
tr.field.type = ''
|
||||
this.$nextTick(() => {
|
||||
tr.field.type = oldType
|
||||
})
|
||||
tr.operator = tr.field.operator || 'include'
|
||||
tr.value = ''
|
||||
},
|
||||
//下拉框显示事件处理异步
|
||||
async visibleChange(isopen, item){
|
||||
if(isopen && item.field.extend.request && !item.field.extend.remote){
|
||||
item.selectLoading = true;
|
||||
try {
|
||||
var data = await item.field.extend.request()
|
||||
}catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
item.field.extend.data = data;
|
||||
item.selectLoading = false;
|
||||
}
|
||||
},
|
||||
//下拉框显示事件处理异步搜索
|
||||
async remoteMethod(query, item){
|
||||
if(query !== ''){
|
||||
item.selectLoading = true;
|
||||
try {
|
||||
var data = await item.field.extend.request(query);
|
||||
}catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
item.field.extend.data = data;
|
||||
item.selectLoading = false;
|
||||
}else{
|
||||
item.field.extend.data = [];
|
||||
}
|
||||
},
|
||||
//选择常用过滤
|
||||
selectMyfilter(item){
|
||||
//常用过滤回显当前过滤项
|
||||
this.filter = []
|
||||
this.fields.forEach((field) => {
|
||||
var filterValue = item.filterObj[field.value]
|
||||
if(filterValue){
|
||||
var operator = filterValue.split("|")[1]
|
||||
var value = filterValue.split("|")[0]
|
||||
if(field.type=='select' && field.extend.multiple){
|
||||
value = value.split(",")
|
||||
}else if(field.type=='daterange'){
|
||||
value = value.split(",")
|
||||
}
|
||||
this.filter.push({
|
||||
field: field,
|
||||
operator: operator,
|
||||
value: value
|
||||
})
|
||||
}
|
||||
})
|
||||
this.filterObjLength = Object.keys(item.filterObj).length
|
||||
this.$emit('filterChange',item.filterObj)
|
||||
this.drawer = false
|
||||
},
|
||||
//立即过滤
|
||||
ok(){
|
||||
this.filterObjLength = this.filter.length
|
||||
this.$emit('filterChange',this.filterObj)
|
||||
this.drawer = false
|
||||
},
|
||||
//保存常用
|
||||
saveMy(){
|
||||
this.$prompt('常用过滤名称', '另存为常用', {
|
||||
inputPlaceholder: '请输入识别度较高的常用过滤名称',
|
||||
inputPattern: /\S/,
|
||||
inputErrorMessage: '名称不能为空'
|
||||
})
|
||||
.then(async ({ value }) => {
|
||||
this.saveLoading = true
|
||||
const saveObj = {
|
||||
title: value,
|
||||
filterObj: this.filterObj
|
||||
}
|
||||
try {
|
||||
var save = await config.saveMy(this.filterName, saveObj)
|
||||
}catch (error) {
|
||||
this.saveLoading = false
|
||||
console.log(error);
|
||||
return false
|
||||
}
|
||||
if(!save){
|
||||
return false
|
||||
}
|
||||
|
||||
this.myFilter.push(saveObj)
|
||||
this.$message.success(`${this.filterName} 保存常用成功`)
|
||||
this.saveLoading = false
|
||||
})
|
||||
.catch(() => {
|
||||
//
|
||||
})
|
||||
},
|
||||
//清空过滤
|
||||
clear(){
|
||||
this.filter = []
|
||||
this.filterObjLength = 0
|
||||
this.$emit('filterChange',this.filterObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabs-label {padding:0 20px;}
|
||||
|
||||
.nodata {height:46px;line-height: 46px;margin:15px 0;border: 1px dashed #e6e6e6;color: #999;text-align: center;border-radius: 3px;}
|
||||
|
||||
.sc-filter-main {padding:20px;border-bottom: 1px solid #e6e6e6;background: #fff;}
|
||||
.sc-filter-main h2 {font-size: 12px;color: #999;font-weight: normal;}
|
||||
.sc-filter-main table {width: 100%;margin: 15px 0;}
|
||||
.sc-filter-main table tr {}
|
||||
.sc-filter-main table td {padding:5px 10px 5px 0;}
|
||||
.sc-filter-main table td:deep(.el-input .el-input__inner) {vertical-align: top;}
|
||||
.sc-filter-main table td .el-select {display: block;}
|
||||
.sc-filter-main table td .el-date-editor.el-input {display: block;width: 100%;}
|
||||
.sc-filter-main table td .del {background: #fff;color: #999;width: 32px;height: 32px;line-height: 32px;text-align: center;border-radius:50%;font-size: 12px;cursor: pointer;}
|
||||
.sc-filter-main table td .del:hover {background: #F56C6C;color: #fff;}
|
||||
|
||||
.root {display: flex;height: 100%;flex-direction: column}
|
||||
.root:deep(.el-tabs__header) {margin: 0;}
|
||||
.root:deep(.el-tabs__content) {flex: 1;background: #f6f8f9;}
|
||||
.root:deep(.el-tabs__content) .el-tab-pane{overflow: auto;height:100%;}
|
||||
|
||||
.dark .root:deep(.el-tabs__content) {background: var(--el-bg-color-overlay);}
|
||||
.dark .sc-filter-main {background: var(--el-bg-color);border-color:var(--el-border-color-light);}
|
||||
.dark .sc-filter-main table td .del {background: none;}
|
||||
.dark .sc-filter-main table td .del:hover {background: #F56C6C;}
|
||||
.dark .nodata {border-color:var(--el-border-color-light);}
|
||||
</style>
|
||||
@@ -0,0 +1,112 @@
|
||||
<!--
|
||||
* @Descripttion: 过滤器V2 常用组件
|
||||
* @version: 2.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年7月31日16:49:56
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-filter-my">
|
||||
<div v-if="loading" class="sc-filter-my-loading">
|
||||
<el-skeleton :rows="2" animated />
|
||||
</div>
|
||||
<template v-else>
|
||||
<el-empty v-if="myFilter.length<=0" :image-size="100">
|
||||
<template #description>
|
||||
<h2>没有常用的过滤</h2>
|
||||
<p style="margin-top: 10px;max-width: 300px;">常用过滤可以将多个过滤条件保存为一个集合,方便下次进行相同条件的过滤</p>
|
||||
</template>
|
||||
</el-empty>
|
||||
<ul v-else class="sc-filter-my-list">
|
||||
<h2>我的常用过滤</h2>
|
||||
<li v-for="(item, index) in myFilter" :key="index" @click="selectMyfilter(item)">
|
||||
<label>{{item.title}}</label>
|
||||
<el-popconfirm title="确认删除此常用过滤吗?" @confirm="closeMyfilter(item, index)">
|
||||
<template #reference>
|
||||
<el-icon class="del" @click.stop="()=>{}"><el-icon-delete /></el-icon>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/filterBar";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
filterName: { type: String, default: "" },
|
||||
data: { type: Object, default: () => {} }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
myFilter: []
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
data: {
|
||||
handler(){
|
||||
this.myFilter = this.data
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.myFilter = this.data
|
||||
this.getMyfilter()
|
||||
},
|
||||
methods: {
|
||||
//选择常用过滤
|
||||
selectMyfilter(item){
|
||||
this.$emit('selectMyfilter', item)
|
||||
},
|
||||
//删除常用过滤
|
||||
async closeMyfilter(item, index){
|
||||
try {
|
||||
var del = await config.delMy(this.filterName)
|
||||
}catch (error) {
|
||||
return false
|
||||
}
|
||||
if(!del){
|
||||
return false
|
||||
}
|
||||
this.myFilter.splice(index, 1)
|
||||
this.$message.success('删除常用成功')
|
||||
},
|
||||
//远程获取我的常用
|
||||
async getMyfilter(){
|
||||
this.loading = true
|
||||
try {
|
||||
this.myFilter = await config.getMy(this.filterName)
|
||||
}catch (error) {
|
||||
return false
|
||||
}
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-filter-my {}
|
||||
.sc-filter-my-loading {padding:15px;}
|
||||
.sc-filter-my-list {list-style-type: none;background: #fff;border-bottom: 1px solid #e6e6e6;}
|
||||
.sc-filter-my-list h2 {font-size: 12px;color: #999;font-weight: normal;padding:20px;}
|
||||
.sc-filter-my-list li {padding:12px 20px;cursor: pointer;position: relative;color: #3c4a54;padding-right:80px;}
|
||||
.sc-filter-my-list li:hover {background: #ecf5ff;color: #409EFF;}
|
||||
.sc-filter-my-list li label {cursor: pointer;font-size: 14px;line-height: 1.8;}
|
||||
.sc-filter-my-list li label span {color: #999;margin-right: 10px;}
|
||||
.sc-filter-my-list li .del {position: absolute;right:20px;top:8px;border-radius:50%;width: 32px;height: 32px;display: flex;align-items: center;justify-content: center;color: #999;}
|
||||
.sc-filter-my-list li .del:hover {background: #F56C6C;color: #fff;}
|
||||
|
||||
[data-theme='dark'] .sc-filter-my .el-empty h2 {color: #fff;}
|
||||
[data-theme='dark'] .sc-filter-my-list {background: none;border-color:var(--el-border-color-base);}
|
||||
[data-theme='dark'] .sc-filter-my-list li {color: #d0d0d0;}
|
||||
[data-theme='dark'] .sc-filter-my-list li:hover {background: var(--el-color-white);}
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,51 @@
|
||||
<!--
|
||||
* @Descripttion: 二次封装el-select 支持拼音
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年7月31日22:26:56
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<el-select v-bind="$attrs" :filter-method="filterMethod" @visible-change="visibleChange">
|
||||
<el-option v-for="field in optionsList" :key="field.value" :label="field.label" :value="field"></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pinyin from 'pinyin-match'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
options: { type: Array, default: () => [] }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
optionsList: [],
|
||||
optionsList_: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.optionsList = this.options
|
||||
this.optionsList_ = [...this.options]
|
||||
},
|
||||
methods: {
|
||||
filterMethod(keyword){
|
||||
if(keyword){
|
||||
this.optionsList = this.optionsList_
|
||||
this.optionsList = this.optionsList.filter((item) =>
|
||||
pinyin.match(item.label, keyword)
|
||||
);
|
||||
}else{
|
||||
this.optionsList = this.optionsList_
|
||||
}
|
||||
},
|
||||
visibleChange(isopen){
|
||||
if(isopen){
|
||||
this.optionsList = this.optionsList_
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,295 @@
|
||||
<!--
|
||||
* @Descripttion: 动态表单渲染器
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年9月22日09:26:25
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<el-skeleton v-if="renderLoading || Object.keys(form).length==0" animated />
|
||||
|
||||
<el-form v-else ref="form" :model="form" :label-width="config.labelWidth" :label-position="config.labelPosition" v-loading="loading" element-loading-text="Loading...">
|
||||
<el-row :gutter="15">
|
||||
<template v-for="(item, index) in config.formItems" :key="index">
|
||||
<el-col :span="item.span || 24" v-if="!hideHandle(item)">
|
||||
<sc-title v-if="item.type=='title'" :title="item.label"></sc-title>
|
||||
<el-form-item v-else :prop="item.name" :rules="rulesHandle(item)">
|
||||
<template #label>
|
||||
{{item.label}}
|
||||
<el-tooltip v-if="item.tips" :content="item.tips">
|
||||
<el-icon><el-icon-question-filled /></el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<!-- input -->
|
||||
<template v-if="item.type=='input' || item.type=='string'" >
|
||||
<el-input v-model="form[item.name]" :placeholder="item.options?.placeholder" clearable :maxlength="item.options?.maxlength" show-word-limit :disabled="item.disabled" />
|
||||
</template>
|
||||
<!-- input -->
|
||||
<template v-else-if="item.type=='textarea'" >
|
||||
<el-input type="textarea" v-model="form[item.name]" :placeholder="item.options?.placeholder" clearable :maxlength="item.options.maxlength" show-word-limit :disabled="item.disabled" />
|
||||
</template>
|
||||
<!-- checkbox -->
|
||||
<template v-else-if="item.type=='checkbox'" >
|
||||
<template v-if="item.name" >
|
||||
<el-checkbox v-model="form[item.name][_item.name]" :label="_item.label" v-for="(_item, _index) in item.options.items" :key="_index" :disabled="item.disabled"></el-checkbox>
|
||||
</template>
|
||||
<template v-else >
|
||||
<el-checkbox v-model="form[_item.name]" :label="_item.label" v-for="(_item, _index) in item.options.items" :key="_index" :disabled="item.disabled"></el-checkbox>
|
||||
</template>
|
||||
</template>
|
||||
<!-- checkboxGroup -->
|
||||
<template v-else-if="item.type=='checkboxGroup'" >
|
||||
<el-checkbox-group v-model="form[item.name]" :disabled="item.disabled">
|
||||
<el-checkbox v-for="_item in item.options.items" :key="_item.value" :label="_item.value">{{_item.label}}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</template>
|
||||
<!-- upload -->
|
||||
<template v-else-if="item.type=='upload' || item.type == 'image'" >
|
||||
<el-form-item :prop="item.name">
|
||||
<sc-upload v-model="form[item.name]" :title="item.label"></sc-upload>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<!-- switch -->
|
||||
<template v-else-if="item.type=='switch' || item.type=='boolean'" >
|
||||
<el-switch v-model="form[item.name]" :active-value="1" :inactive-value="0" :disabled="item.disabled" />
|
||||
</template>
|
||||
<!-- select -->
|
||||
<template v-else-if="item.type=='select'" >
|
||||
<el-select v-model="form[item.name]" :multiple="item.options.multiple" :placeholder="item.options?.placeholder" clearable filterable style="width: 100%;" :disabled="item.disabled">
|
||||
<el-option v-for="option in item.options.items" :key="option.value" :label="option.label" :value="option.value"></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
<template v-else-if="item.type=='scSelect'" >
|
||||
<sc-select v-model="form[item.name]" :dic="item.options?.dic" :apiObj="item.options?.apiObj" :props="item.options?.props" style="min-width: 240px;" />
|
||||
</template>
|
||||
<template v-else-if="item.type=='scSelectTree'" >
|
||||
<sc-select-tree v-model="form[item.name]" :dic="item.options?.dic" :apiObj="item.options?.apiObj" :props="item.options?.props" style="min-width: 240px;" />
|
||||
</template>
|
||||
<!-- cascader -->
|
||||
<template v-else-if="item.type=='cascader'" >
|
||||
<el-cascader v-model="form[item.name]" :options="item.options.items" clearable></el-cascader>
|
||||
</template>
|
||||
<!-- date -->
|
||||
<template v-else-if="item.type=='date'" >
|
||||
<el-date-picker v-model="form[item.name]" :type="item.options.type" :shortcuts="item.options.shortcuts" :default-time="item.options.defaultTime" :value-format="item.options.valueFormat" :placeholder="item.options?.placeholder || '请选择'"></el-date-picker>
|
||||
</template>
|
||||
<!-- number -->
|
||||
<template v-else-if="item.type=='number'" >
|
||||
<el-input-number v-model="form[item.name]" controls-position="right" :placeholder="item.options?.placeholder" />
|
||||
</template>
|
||||
<!-- radio -->
|
||||
<template v-else-if="item.type=='radio'" >
|
||||
<el-radio-group v-model="form[item.name]">
|
||||
<el-radio v-for="_item in item.options.items" :key="_item.value" :label="_item.value">{{_item.label}}</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<!-- color -->
|
||||
<template v-else-if="item.type=='color'" >
|
||||
<el-color-picker v-model="form[item.name]" />
|
||||
</template>
|
||||
<!-- rate -->
|
||||
<template v-else-if="item.type=='rate'" >
|
||||
<el-rate style="margin-top: 6px;" v-model="form[item.name]"></el-rate>
|
||||
</template>
|
||||
<!-- slider -->
|
||||
<template v-else-if="item.type=='slider'" >
|
||||
<el-slider v-model="form[item.name]" :marks="item.options.marks"></el-slider>
|
||||
</template>
|
||||
<!-- tableselect -->
|
||||
<template v-else-if="item.type=='tableselect'" >
|
||||
<tableselect-render v-model="form[item.name]" :item="item"></tableselect-render>
|
||||
</template>
|
||||
<!-- editor -->
|
||||
<template v-else-if="item.type=='editor'" >
|
||||
<sc-editor v-model="form[item.name]" placeholder="请输入" :height="400"></sc-editor>
|
||||
</template>
|
||||
<!-- noComponent -->
|
||||
<template v-else>
|
||||
<el-tag type="danger">[{{item.type}}] Component not found</el-tag>
|
||||
</template>
|
||||
<div v-if="item.message" class="el-form-item-msg">{{item.message}}</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<el-col :span="24">
|
||||
<el-form-item>
|
||||
<slot>
|
||||
<el-button type="primary" @click="submit">提交</el-button>
|
||||
</slot>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from "@/utils/request"
|
||||
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const tableselectRender = defineAsyncComponent(() => import('./items/tableselect'))
|
||||
const scEditor = defineAsyncComponent(() => import('@/components/scEditor'))
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: Object, default: () => {} },
|
||||
config: { type: Object, default: () => {} },
|
||||
loading: { type: Boolean, default: false },
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
},
|
||||
components: {
|
||||
tableselectRender,
|
||||
scEditor
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
renderLoading: false
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(){
|
||||
if(this.hasConfig){
|
||||
this.deepMerge(this.form, this.modelValue)
|
||||
}
|
||||
},
|
||||
config(){
|
||||
this.render()
|
||||
},
|
||||
form:{
|
||||
handler(val){
|
||||
this.$emit("update:modelValue", val)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasConfig(){
|
||||
return Object.keys(this.config).length>0
|
||||
},
|
||||
hasValue(){
|
||||
return Object.keys(this.modelValue).length>0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
if(this.hasConfig){
|
||||
this.render()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//构建form对象
|
||||
render() {
|
||||
this.config.formItems.forEach((item) => {
|
||||
if(item.type == 'checkbox'){
|
||||
if(item.name){
|
||||
const value = {}
|
||||
item.options.items.forEach((option) => {
|
||||
value[option.name] = option.value
|
||||
})
|
||||
this.form[item.name] = value
|
||||
}else{
|
||||
item.options.items.forEach((option) => {
|
||||
this.form[option.name] = option.value
|
||||
})
|
||||
}
|
||||
}else if(item.type == 'upload'){
|
||||
if(item.name){
|
||||
const value = {}
|
||||
item.options.items.forEach((option) => {
|
||||
value[option.name] = option.value
|
||||
})
|
||||
this.form[item.name] = value
|
||||
}else{
|
||||
item.options.items.forEach((option) => {
|
||||
this.form[option.name] = option.value
|
||||
})
|
||||
}
|
||||
}else{
|
||||
this.form[item.name] = item.value
|
||||
}
|
||||
})
|
||||
if(this.hasValue){
|
||||
this.form = this.deepMerge(this.form, this.modelValue)
|
||||
}
|
||||
this.getData()
|
||||
},
|
||||
//处理远程选项数据
|
||||
getData() {
|
||||
this.renderLoading = true
|
||||
var remoteData = []
|
||||
this.config.formItems.forEach((item) => {
|
||||
if(item.options && item.options.remote){
|
||||
var req = http.get(item.options.remote.api, item.options.remote.data).then(res=>{
|
||||
item.options.items = res.data
|
||||
})
|
||||
remoteData.push(req)
|
||||
}
|
||||
})
|
||||
Promise.all(remoteData).then(()=>{
|
||||
this.renderLoading = false
|
||||
})
|
||||
},
|
||||
//合并深结构对象
|
||||
deepMerge(obj1, obj2) {
|
||||
let key;
|
||||
for (key in obj2) {
|
||||
obj1[key] = obj1[key] && obj1[key].toString() === "[object Object]" && (obj2[key] && obj2[key].toString() === "[object Object]") ? this.deepMerge(obj1[key], obj2[key]) : (obj1[key] = obj2[key])
|
||||
}
|
||||
return obj1
|
||||
//return JSON.parse(JSON.stringify(obj1))
|
||||
},
|
||||
//处理动态隐藏
|
||||
hideHandle(item){
|
||||
if(item.hideHandle){
|
||||
const exp = eval(item.hideHandle.replace(/\$/g,"this.form"))
|
||||
return exp
|
||||
}
|
||||
return false
|
||||
},
|
||||
//处理动态必填
|
||||
rulesHandle(item){
|
||||
if(item.requiredHandle){
|
||||
const exp = eval(item.requiredHandle.replace(/\$/g,"this.form"))
|
||||
var requiredRule = item.rules.find(t => 'required' in t)
|
||||
requiredRule.required = exp
|
||||
}
|
||||
return item.rules
|
||||
},
|
||||
//数据验证
|
||||
validate(valid, obj){
|
||||
return this.$refs.form.validate(valid, obj)
|
||||
},
|
||||
scrollToField(prop){
|
||||
return this.$refs.form.scrollToField(prop)
|
||||
},
|
||||
resetFields(){
|
||||
return this.$refs.form.resetFields()
|
||||
},
|
||||
//提交
|
||||
submit(){
|
||||
if(!this.apiObj){
|
||||
this.$emit("submit", this.form)
|
||||
}else{
|
||||
this.$refs.form.validate().then(async () => {
|
||||
let res = await this.apiObj.post(this.form)
|
||||
if(res.code == 1){
|
||||
this.$emit('update:modelValue', this.form)
|
||||
this.$emit('onSuccess', res)
|
||||
}else{
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$message.error('请认真填写信息!')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<sc-table-select v-model="value" :apiObj="apiObj" :table-width="600" :multiple="item.options.multiple" :props="item.options.props" style="width: 100%;">
|
||||
<el-table-column v-for="(_item, _index) in item.options.column" :key="_index" :prop="_item.prop" :label="_item.label" :width="_item.width"></el-table-column>
|
||||
</sc-table-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uploadRender',
|
||||
props: {
|
||||
modelValue: [String, Number, Boolean, Date, Object, Array],
|
||||
item: { type: Object, default: () => {} }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: this.modelValue,
|
||||
apiObj: this.getApiObj()
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
value(val){
|
||||
this.$emit("update:modelValue", val)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
getApiObj(){
|
||||
return eval(`this.`+this.item.options.apiObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,123 @@
|
||||
<!--
|
||||
* @Descripttion: 表单表格
|
||||
* @version: 1.3
|
||||
* @Author: sakuya
|
||||
* @Date: 2023年2月9日12:32:26
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2023年2月17日10:41:47
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-form-table" ref="scFormTable">
|
||||
<el-table :data="data" ref="table" border stripe>
|
||||
<el-table-column type="index" width="50" fixed="left">
|
||||
<template #header>
|
||||
<el-button v-if="!hideAdd" type="primary" icon="el-icon-plus" circle @click="rowAdd"></el-button>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<div :class="['sc-form-table-handle', {'sc-form-table-handle-delete':!hideDelete}]">
|
||||
<span>{{scope.$index + 1}}</span>
|
||||
<el-button v-if="!hideDelete" type="danger" icon="el-icon-delete" plain circle @click="rowDel(scope.row, scope.$index)"></el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="" width="50" v-if="dragSort">
|
||||
<template #default>
|
||||
<div class="move" style="cursor: move;"><el-icon-d-caret style="width: 1em; height: 1em;"/></div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<slot></slot>
|
||||
<template #empty>
|
||||
{{placeholder}}
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: Array, default: () => [] },
|
||||
addTemplate: { type: Object, default: () => {} },
|
||||
placeholder: { type: String, default: "暂无数据" },
|
||||
dragSort: { type: Boolean, default: false },
|
||||
hideAdd: { type: Boolean, default: false },
|
||||
hideDelete: { type: Boolean, default: false }
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.data = this.modelValue
|
||||
if(this.dragSort){
|
||||
this.rowDrop()
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(){
|
||||
this.data = this.modelValue
|
||||
},
|
||||
data: {
|
||||
handler(){
|
||||
this.$emit('update:modelValue', this.data);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
rowDrop(){
|
||||
const _this = this
|
||||
const tbody = this.$refs.table.$el.querySelector('.el-table__body-wrapper tbody')
|
||||
Sortable.create(tbody, {
|
||||
handle: ".move",
|
||||
animation: 300,
|
||||
ghostClass: "ghost",
|
||||
onEnd({ newIndex, oldIndex }) {
|
||||
_this.data.splice(newIndex, 0, _this.data.splice(oldIndex, 1)[0])
|
||||
const newArray = _this.data.slice(0)
|
||||
const tmpHeight = _this.$refs.scFormTable.offsetHeight
|
||||
_this.$refs.scFormTable.style.setProperty('height', tmpHeight + 'px')
|
||||
_this.data = []
|
||||
_this.$nextTick(() => {
|
||||
_this.data = newArray
|
||||
_this.$nextTick(() => {
|
||||
_this.$refs.scFormTable.style.removeProperty('height')
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
rowAdd(){
|
||||
const temp = JSON.parse(JSON.stringify(this.addTemplate))
|
||||
this.data.push(temp)
|
||||
},
|
||||
rowDel(row, index){
|
||||
this.data.splice(index, 1)
|
||||
},
|
||||
//插入行
|
||||
pushRow(row){
|
||||
const temp = row || JSON.parse(JSON.stringify(this.addTemplate))
|
||||
this.data.push(temp)
|
||||
},
|
||||
//根据index删除
|
||||
deleteRow(index){
|
||||
this.data.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-form-table {width: 100%;}
|
||||
.sc-form-table .sc-form-table-handle {text-align: center;}
|
||||
.sc-form-table .sc-form-table-handle span {display: inline-block;}
|
||||
.sc-form-table .sc-form-table-handle button {display: none;}
|
||||
.sc-form-table .hover-row .sc-form-table-handle-delete span {display: none;}
|
||||
.sc-form-table .hover-row .sc-form-table-handle-delete button {display: inline-block;}
|
||||
.sc-form-table .move {text-align: center;font-size: 14px;margin-top: 3px;}
|
||||
</style>
|
||||
@@ -0,0 +1,128 @@
|
||||
<!--
|
||||
* @Descripttion: 图标选择器组件
|
||||
* @version: 2.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年7月27日10:02:46
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年6月6日13:48:49
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-icon-select">
|
||||
<div class="sc-icon-select__wrapper" :class="{'hasValue':value}" @click="open">
|
||||
<el-input :prefix-icon="value||'el-icon-plus'" v-model="value" :disabled="disabled" readonly></el-input>
|
||||
</div>
|
||||
<el-dialog title="图标选择器" v-model="dialogVisible" :width="760" destroy-on-close append-to-body>
|
||||
<div class="sc-icon-select__dialog" style="margin: -20px 0 -10px 0;">
|
||||
<el-form :rules="{}">
|
||||
<el-form-item prop="searchText">
|
||||
<el-input class="sc-icon-select__search-input" prefix-icon="el-icon-search" v-model="searchText" placeholder="搜索" clearable/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-tabs>
|
||||
<el-tab-pane v-for="item in data" :key="item.name" lazy>
|
||||
<template #label>
|
||||
{{item.name}} <el-tag type="info">{{item.icons.length}}</el-tag>
|
||||
</template>
|
||||
<div class="sc-icon-select__list">
|
||||
<el-scrollbar>
|
||||
<ul @click="selectIcon">
|
||||
<el-empty v-if="item.icons.length==0" :image-size="100" description="未查询到相关图标" />
|
||||
<li v-for="icon in item.icons" :key="icon">
|
||||
<span :data-icon="icon"></span>
|
||||
<el-icon><component :is="icon" /></el-icon>
|
||||
</li>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="clear" text>清除</el-button>
|
||||
<el-button @click="dialogVisible=false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/iconSelect"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: String, default: "" },
|
||||
disabled: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: "",
|
||||
dialogVisible: false,
|
||||
data: [],
|
||||
searchText: ""
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(val){
|
||||
this.value = val
|
||||
},
|
||||
value(val){
|
||||
this.$emit('update:modelValue', val)
|
||||
},
|
||||
searchText(val){
|
||||
this.search(val)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.value = this.modelValue
|
||||
this.data.push(...config.icons)
|
||||
},
|
||||
methods: {
|
||||
open(){
|
||||
if(this.disabled){
|
||||
return false
|
||||
}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
selectIcon(e){
|
||||
if(e.target.tagName != 'SPAN'){
|
||||
return false
|
||||
}
|
||||
this.value = e.target.dataset.icon
|
||||
this.dialogVisible = false
|
||||
},
|
||||
clear(){
|
||||
this.value = ""
|
||||
this.dialogVisible = false
|
||||
},
|
||||
search(text){
|
||||
if(text){
|
||||
const filterData = JSON.parse(JSON.stringify(config.icons))
|
||||
filterData.forEach(t => {
|
||||
t.icons = t.icons.filter(n => n.includes(text))
|
||||
})
|
||||
this.data = filterData
|
||||
}else{
|
||||
this.data = JSON.parse(JSON.stringify(config.icons))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-icon-select {display: inline-flex;}
|
||||
.sc-icon-select__wrapper {cursor: pointer;display: inline-flex;}
|
||||
.sc-icon-select__wrapper:deep(.el-input__wrapper).is-focus {box-shadow: 0 0 0 1px var(--el-input-hover-border-color) inset;}
|
||||
.sc-icon-select__wrapper:deep(.el-input__inner) {flex-grow:0;width: 0;}
|
||||
.sc-icon-select__wrapper:deep(.el-input__icon) {margin: 0;font-size: 16px;}
|
||||
.sc-icon-select__wrapper.hasValue:deep(.el-input__icon) {color: var(--el-text-color-regular);}
|
||||
|
||||
.sc-icon-select__list {height:270px;overflow: auto;}
|
||||
.sc-icon-select__list ul {}
|
||||
.sc-icon-select__list li {display: inline-block;width:80px;height:80px;margin:5px;vertical-align: top;transition: all 0.1s;border-radius: 4px;position: relative;}
|
||||
.sc-icon-select__list li span {position: absolute;top:0;left:0;right:0;bottom:0;z-index: 1;cursor: pointer;}
|
||||
.sc-icon-select__list li i {display: inline-block;width: 100%;height:100%;font-size: 26px;color: #6d7882;display: flex;justify-content: center;align-items: center;border-radius: 4px;}
|
||||
.sc-icon-select__list li:hover {box-shadow: 0 0 1px 4px var(--el-color-primary);background: var(--el-color-primary-light-9);}
|
||||
.sc-icon-select__list li:hover i {color: var(--el-color-primary);}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-input v-bind="$attrs" v-model="defaultValue" placeholder="请输入关键字" style="width: 100%;" readonly @foucs="showMap" @click="showMap" />
|
||||
<el-dialog title="地图" v-model="visible" width="800px">
|
||||
<template #default="scope">
|
||||
<b-map ref="map" width="100%" height="500px" ak="Mnx3XVAmyovK9wohstKt94nRfDpTy8qd" :center="point.lng ? point : '南昌'" :zoom="zoom" :minZoom="3" :mapType="'BMAP_NORMAL_MAP'" :enableDragging="true" :enableScrollWheelZoom="true" @dblclick="mapClick">
|
||||
<BCityList :offset="{ x: 20, y: 20 }" />
|
||||
<b-zoom :offset="{ x: 22, y: 40 }" />
|
||||
|
||||
<template v-if="point.lng != ''">
|
||||
<BMarker :position="point"></BMarker>
|
||||
<BCircle strokeStyle="solid" strokeColor="#0099ff" :strokeOpacity="0.8" fillColor="#0099ff" :fillOpacity="0.5" :center="point" />
|
||||
</template>
|
||||
</b-map>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BMap, BZoom, BCityList, BMarker, BCircle } from 'vue3-baidu-map-gl'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
point: {lng: '', lat: ''},
|
||||
zoom: 12,
|
||||
defaultValue: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.defaultValue = this.modelValue
|
||||
},
|
||||
watch: {
|
||||
modelValue:{
|
||||
handler(val) {
|
||||
this.defaultValue = val
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
BMap,
|
||||
BZoom,
|
||||
BCityList,
|
||||
BMarker,
|
||||
BCircle
|
||||
},
|
||||
methods: {
|
||||
showMap(){
|
||||
this.visible = true
|
||||
if(this.defaultValue) {
|
||||
let arr = this.defaultValue.split(',')
|
||||
this.point = {
|
||||
lng: arr[1],
|
||||
lat: arr[0]
|
||||
}
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.zoom = 20
|
||||
this.$refs.map.resetCenter()
|
||||
})
|
||||
},
|
||||
mapClick(e) {
|
||||
this.$emit('update:modelValue', e.latlng.lat+','+e.latlng.lng)
|
||||
this.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!--
|
||||
* @Descripttion: 状态指示器
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年11月11日09:30:12
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<span class="sc-state" :class="[{'sc-status-processing':pulse}, 'sc-state-bg--'+type]"></span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
type: { type: String, default: "primary" },
|
||||
pulse: { type: Boolean, default: false }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-state {display: inline-block;background: #000;width: 8px;height: 8px;border-radius: 50%;vertical-align: middle;}
|
||||
.sc-status-processing {position: relative;}
|
||||
.sc-status-processing:after {position: absolute;top:0px;left:0px;width: 100%;height: 100%;border-radius: 50%;background: inherit;content: '';animation: warn 1.2s ease-in-out infinite;}
|
||||
|
||||
.sc-state-bg--primary {background: var(--el-color-primary);}
|
||||
.sc-state-bg--success {background: var(--el-color-success);}
|
||||
.sc-state-bg--warning {background: var(--el-color-warning);}
|
||||
.sc-state-bg--danger {background: var(--el-color-danger);}
|
||||
.sc-state-bg--info {background: var(--el-color-info);}
|
||||
|
||||
@keyframes warn {
|
||||
0% {
|
||||
transform: scale(0.5);
|
||||
opacity: 1;
|
||||
}
|
||||
30% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
transform: scale(2.5);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,66 @@
|
||||
<!--
|
||||
* @Descripttion: 趋势标记
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年11月11日11:07:10
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<span class="sc-trend" :class="'sc-trend--'+type">
|
||||
<el-icon v-if="iconType=='P'" class="sc-trend-icon"><el-icon-top /></el-icon>
|
||||
<el-icon v-if="iconType=='N'" class="sc-trend-icon"><el-icon-bottom /></el-icon>
|
||||
<el-icon v-if="iconType=='Z'" class="sc-trend-icon"><el-icon-right /></el-icon>
|
||||
<em class="sc-trend-prefix">{{prefix}}</em>
|
||||
<em class="sc-trend-value">{{modelValue}}</em>
|
||||
<em class="sc-trend-suffix">{{suffix}}</em>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: Number, default: 0 },
|
||||
prefix: { type: String, default: "" },
|
||||
suffix: { type: String, default: "" },
|
||||
reverse: { type: Boolean, default: false }
|
||||
},
|
||||
computed: {
|
||||
absValue(){
|
||||
return Math.abs(this.modelValue);
|
||||
},
|
||||
iconType(v){
|
||||
if(this.modelValue == 0){
|
||||
v = 'Z'
|
||||
}else if(this.modelValue < 0){
|
||||
v = 'N'
|
||||
}else if(this.modelValue > 0){
|
||||
v = 'P'
|
||||
}
|
||||
return v
|
||||
},
|
||||
type(v){
|
||||
if(this.modelValue == 0){
|
||||
v = 'Z'
|
||||
}else if(this.modelValue < 0){
|
||||
v = this.reverse?'P':'N'
|
||||
}else if(this.modelValue > 0){
|
||||
v = this.reverse?'N':'P'
|
||||
}
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-trend {display: flex;align-items: center;}
|
||||
.sc-trend-icon {margin-right: 2px;}
|
||||
.sc-trend em {font-style: normal;}
|
||||
.sc-trend-prefix {margin-right: 2px;}
|
||||
.sc-trend-suffix {margin-left: 2px;}
|
||||
.sc-trend--P {color: #f56c6c;}
|
||||
.sc-trend--N {color: #67c23a;}
|
||||
.sc-trend--Z {color: #555;}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<!--
|
||||
* @Descripttion: 页面头部样式组件
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年7月20日08:49:07
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-page-header">
|
||||
<div v-if="icon" class="sc-page-header__icon">
|
||||
<span>
|
||||
<el-icon><component :is="icon" /></el-icon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="sc-page-header__title">
|
||||
<h2>{{ title }}</h2>
|
||||
<p v-if="description || $slots.default">
|
||||
<slot>
|
||||
{{ description }}
|
||||
</slot>
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="$slots.main" class="sc-page-header__main">
|
||||
<slot name="main"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: { type: String, required: true, default: "" },
|
||||
description: { type: String, default: "" },
|
||||
icon: { type: String, default: "" },
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-page-header {background: #fff;border-bottom: 1px solid #e6e6e6;padding:20px 25px;display: flex;}
|
||||
.sc-page-header__icon {width: 50px;}
|
||||
.sc-page-header__icon span {display: inline-block;width: 30px;height: 30px;background: #409EFF;border-radius: 40%;display: flex;align-items: center;justify-content: center;}
|
||||
.sc-page-header__icon span i {color: #fff;font-size: 14px;}
|
||||
.sc-page-header__title {flex: 1;}
|
||||
.sc-page-header__title h2 {font-size: 17px;color: #3c4a54;font-weight: bold;margin-top: 3px;}
|
||||
.sc-page-header__title p {font-size: 13px;color: #999;margin-top: 15px;}
|
||||
|
||||
[data-theme='dark'] .sc-page-header {background:#2b2b2b ;border-color:var(--el-border-color-base);}
|
||||
[data-theme='dark'] .sc-page-header__title h2 {color: #d0d0d0;}
|
||||
</style>
|
||||
@@ -0,0 +1,92 @@
|
||||
<!--
|
||||
* @Descripttion: 密码强度检测
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2022年6月2日15:36:01
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-password-strength">
|
||||
<div class="sc-password-strength-bar" :class="`sc-password-strength-level-${level}`"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: String, default: "" },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
level: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue() {
|
||||
this.strength(this.modelValue)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.strength(this.modelValue)
|
||||
},
|
||||
methods: {
|
||||
strength(v){
|
||||
var _level = 0
|
||||
//长度
|
||||
var has_length = v.length >= 6
|
||||
//包含数字
|
||||
var has_number = /\d/.test(v)
|
||||
//包含小写英文
|
||||
var has_lovercase = /[a-z]/.test(v)
|
||||
//包含大写英文
|
||||
var has_uppercase = /[A-Z]/.test(v)
|
||||
//没有连续的字符3位
|
||||
var no_continuity = !/(\w)\1{2}/.test(v)
|
||||
//包含特殊字符
|
||||
var has_special = /[`~!@#$%^&*()_+<>?:"{},./;'[\]]/.test(v)
|
||||
|
||||
if(v.length <= 0){
|
||||
_level = 0
|
||||
this.level = _level
|
||||
return false
|
||||
}
|
||||
if(!has_length){
|
||||
_level = 1
|
||||
this.level = _level
|
||||
return false
|
||||
}
|
||||
if(has_number){
|
||||
_level += 1
|
||||
}
|
||||
if(has_lovercase){
|
||||
_level += 1
|
||||
}
|
||||
if(has_uppercase){
|
||||
_level += 1
|
||||
}
|
||||
if(no_continuity){
|
||||
_level += 1
|
||||
}
|
||||
if(has_special){
|
||||
_level += 1
|
||||
}
|
||||
this.level = _level
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-password-strength {height: 5px;width: 100%;background: var(--el-color-info-light-5);border-radius: 5px;position: relative;margin:10px 0;}
|
||||
.sc-password-strength:before {left: 20%;}
|
||||
.sc-password-strength:after {right: 20%;}
|
||||
.sc-password-strength:before, .sc-password-strength:after {position: absolute;content: "";display: block;width: 20%;height: inherit;border: 5px solid var(--el-bg-color-overlay);border-top: 0;border-bottom: 0;z-index: 1;background-color: transparent;box-sizing: border-box;}
|
||||
.sc-password-strength-bar {position: absolute;height: inherit;width: 0%;border-radius: inherit;transition: width .5s ease-in-out,background .25s;background: transparent;}
|
||||
.sc-password-strength-level-1 {width: 20%;background-color: var(--el-color-error);}
|
||||
.sc-password-strength-level-2 {width: 40%;background-color: var(--el-color-error);}
|
||||
.sc-password-strength-level-3 {width: 60%;background-color: var(--el-color-warning);}
|
||||
.sc-password-strength-level-4 {width: 80%;background-color: var(--el-color-success);}
|
||||
.sc-password-strength-level-5 {width: 100%;background-color: var(--el-color-success);}
|
||||
</style>
|
||||
@@ -0,0 +1,88 @@
|
||||
<!--
|
||||
* @Descripttion: 生成二维码组件
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年12月20日14:22:20
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<img ref="img"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QRcode from "./qrcode"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
text: { type: String, required: true, default: "" },
|
||||
size: { type: Number, default: 100 },
|
||||
logo: { type: String, default: "" },
|
||||
logoSize: { type: Number, default: 30 },
|
||||
logoPadding: { type: Number, default: 5 },
|
||||
colorDark: { type: String, default: "#000000" },
|
||||
colorLight: { type: String, default: "#ffffff" },
|
||||
correctLevel: { type: Number, default: 2 },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
qrcode: null
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
text(){
|
||||
this.draw()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.draw()
|
||||
},
|
||||
methods: {
|
||||
//创建原始二维码DOM
|
||||
async create(){
|
||||
return new Promise((resolve) => {
|
||||
var element = document.createElement("div");
|
||||
new QRcode(element, {
|
||||
text: this.text,
|
||||
width: this.size,
|
||||
height: this.size,
|
||||
colorDark: this.colorDark,
|
||||
colorLight: this.colorLight,
|
||||
correctLevel: this.correctLevel
|
||||
})
|
||||
if (element.getElementsByTagName("canvas")[0]) {
|
||||
this.qrcode = element
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
},
|
||||
//绘制LOGO
|
||||
async drawLogo(){
|
||||
return new Promise((resolve) => {
|
||||
var logo = new Image()
|
||||
logo.src = this.logo
|
||||
const logoPos = (this.size - this.logoSize) / 2
|
||||
const rectSize = this.logoSize + this.logoPadding
|
||||
const rectPos = (this.size - rectSize) / 2
|
||||
var ctx = this.qrcode.getElementsByTagName("canvas")[0].getContext("2d")
|
||||
logo.onload = ()=>{
|
||||
ctx.fillRect(rectPos, rectPos, rectSize, rectSize)
|
||||
ctx.drawImage(logo, logoPos, logoPos, this.logoSize, this.logoSize)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
},
|
||||
async draw(){
|
||||
await this.create()
|
||||
if(this.logo){
|
||||
await this.drawLogo()
|
||||
}
|
||||
this.$refs.img.src = this.qrcode.getElementsByTagName("canvas")[0].toDataURL("image/png")
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,618 @@
|
||||
/**
|
||||
* @fileoverview
|
||||
* - Using the 'QRCode for Javascript library'
|
||||
* - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
|
||||
* - this library has no dependencies.
|
||||
* - source page: https://github.com/makevoid/qrcodejs
|
||||
*
|
||||
*
|
||||
* @author davidshimjs
|
||||
* @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
|
||||
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
|
||||
*/
|
||||
var QRCode;
|
||||
|
||||
(function () {
|
||||
//---------------------------------------------------------------------
|
||||
// QRCode for JavaScript
|
||||
//
|
||||
// Copyright (c) 2009 Kazuhiko Arase
|
||||
//
|
||||
// URL: http://www.d-project.com/
|
||||
//
|
||||
// Licensed under the MIT license:
|
||||
// http://www.opensource.org/licenses/mit-license.php
|
||||
//
|
||||
// The word "QR Code" is registered trademark of
|
||||
// DENSO WAVE INCORPORATED
|
||||
// http://www.denso-wave.com/qrcode/faqpatent-e.html
|
||||
//
|
||||
//---------------------------------------------------------------------
|
||||
function QR8bitByte(data) {
|
||||
this.mode = QRMode.MODE_8BIT_BYTE;
|
||||
this.data = data;
|
||||
this.parsedData = [];
|
||||
|
||||
// Added to support UTF-8 Characters
|
||||
for (var i = 0, l = this.data.length; i < l; i++) {
|
||||
var byteArray = [];
|
||||
var code = this.data.charCodeAt(i);
|
||||
|
||||
if (code > 0x10000) {
|
||||
byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
|
||||
byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
|
||||
byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
|
||||
byteArray[3] = 0x80 | (code & 0x3F);
|
||||
} else if (code > 0x800) {
|
||||
byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
|
||||
byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
|
||||
byteArray[2] = 0x80 | (code & 0x3F);
|
||||
} else if (code > 0x80) {
|
||||
byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
|
||||
byteArray[1] = 0x80 | (code & 0x3F);
|
||||
} else {
|
||||
byteArray[0] = code;
|
||||
}
|
||||
|
||||
this.parsedData.push(byteArray);
|
||||
}
|
||||
|
||||
this.parsedData = Array.prototype.concat.apply([], this.parsedData);
|
||||
|
||||
if (this.parsedData.length != this.data.length) {
|
||||
this.parsedData.unshift(191);
|
||||
this.parsedData.unshift(187);
|
||||
this.parsedData.unshift(239);
|
||||
}
|
||||
}
|
||||
|
||||
QR8bitByte.prototype = {
|
||||
getLength: function (buffer) {
|
||||
return this.parsedData.length;
|
||||
},
|
||||
write: function (buffer) {
|
||||
for (var i = 0, l = this.parsedData.length; i < l; i++) {
|
||||
buffer.put(this.parsedData[i], 8);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function QRCodeModel(typeNumber, errorCorrectLevel) {
|
||||
this.typeNumber = typeNumber;
|
||||
this.errorCorrectLevel = errorCorrectLevel;
|
||||
this.modules = null;
|
||||
this.moduleCount = 0;
|
||||
this.dataCache = null;
|
||||
this.dataList = [];
|
||||
}
|
||||
|
||||
QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
|
||||
return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
|
||||
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
|
||||
if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
|
||||
this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
|
||||
return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
|
||||
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
|
||||
this.modules[r][6]=(r%2==0);}
|
||||
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
|
||||
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
|
||||
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
|
||||
for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
|
||||
for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
|
||||
this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
|
||||
var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
|
||||
this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
|
||||
row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
|
||||
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
|
||||
if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
|
||||
+buffer.getLengthInBits()
|
||||
+">"
|
||||
+totalDataCount*8
|
||||
+")");}
|
||||
if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
|
||||
while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
|
||||
while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
|
||||
buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
|
||||
buffer.put(QRCodeModel.PAD1,8);}
|
||||
return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
|
||||
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
|
||||
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
|
||||
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
|
||||
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
|
||||
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
|
||||
return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
|
||||
return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
|
||||
return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
|
||||
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
|
||||
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
|
||||
if(r==0&&c==0){continue;}
|
||||
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
|
||||
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
|
||||
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
|
||||
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
|
||||
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
|
||||
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
|
||||
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
|
||||
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
|
||||
while(n>=256){n-=255;}
|
||||
return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
|
||||
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
|
||||
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
|
||||
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
|
||||
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
|
||||
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
|
||||
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
|
||||
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
|
||||
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
|
||||
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
|
||||
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
|
||||
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
|
||||
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
|
||||
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
|
||||
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
|
||||
if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
|
||||
this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
|
||||
|
||||
function _isSupportCanvas() {
|
||||
return typeof CanvasRenderingContext2D != "undefined";
|
||||
}
|
||||
|
||||
// android 2.x doesn't support Data-URI spec
|
||||
function _getAndroid() {
|
||||
var android = false;
|
||||
var sAgent = navigator.userAgent;
|
||||
|
||||
if (/android/i.test(sAgent)) { // android
|
||||
android = true;
|
||||
var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
|
||||
|
||||
if (aMat && aMat[1]) {
|
||||
android = parseFloat(aMat[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return android;
|
||||
}
|
||||
|
||||
var svgDrawer = (function() {
|
||||
|
||||
var Drawing = function (el, htOption) {
|
||||
this._el = el;
|
||||
this._htOption = htOption;
|
||||
};
|
||||
|
||||
Drawing.prototype.draw = function (oQRCode) {
|
||||
var _htOption = this._htOption;
|
||||
var _el = this._el;
|
||||
var nCount = oQRCode.getModuleCount();
|
||||
var nWidth = Math.floor(_htOption.width / nCount);
|
||||
var nHeight = Math.floor(_htOption.height / nCount);
|
||||
|
||||
this.clear();
|
||||
|
||||
function makeSVG(tag, attrs) {
|
||||
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
|
||||
for (var k in attrs)
|
||||
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
|
||||
return el;
|
||||
}
|
||||
|
||||
var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
|
||||
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
|
||||
_el.appendChild(svg);
|
||||
|
||||
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorLight, "width": "100%", "height": "100%"}));
|
||||
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));
|
||||
|
||||
for (var row = 0; row < nCount; row++) {
|
||||
for (var col = 0; col < nCount; col++) {
|
||||
if (oQRCode.isDark(row, col)) {
|
||||
var child = makeSVG("use", {"x": String(col), "y": String(row)});
|
||||
child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
|
||||
svg.appendChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Drawing.prototype.clear = function () {
|
||||
while (this._el.hasChildNodes())
|
||||
this._el.removeChild(this._el.lastChild);
|
||||
};
|
||||
return Drawing;
|
||||
})();
|
||||
|
||||
var useSVG = document.documentElement.tagName.toLowerCase() === "svg";
|
||||
|
||||
// Drawing in DOM by using Table tag
|
||||
var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
|
||||
var Drawing = function (el, htOption) {
|
||||
this._el = el;
|
||||
this._htOption = htOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw the QRCode
|
||||
*
|
||||
* @param {QRCode} oQRCode
|
||||
*/
|
||||
Drawing.prototype.draw = function (oQRCode) {
|
||||
var _htOption = this._htOption;
|
||||
var _el = this._el;
|
||||
var nCount = oQRCode.getModuleCount();
|
||||
var nWidth = Math.floor(_htOption.width / nCount);
|
||||
var nHeight = Math.floor(_htOption.height / nCount);
|
||||
var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
|
||||
|
||||
for (var row = 0; row < nCount; row++) {
|
||||
aHTML.push('<tr>');
|
||||
|
||||
for (var col = 0; col < nCount; col++) {
|
||||
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
|
||||
}
|
||||
|
||||
aHTML.push('</tr>');
|
||||
}
|
||||
|
||||
aHTML.push('</table>');
|
||||
_el.innerHTML = aHTML.join('');
|
||||
|
||||
// Fix the margin values as real size.
|
||||
var elTable = _el.childNodes[0];
|
||||
var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
|
||||
var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
|
||||
|
||||
if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
|
||||
elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the QRCode
|
||||
*/
|
||||
Drawing.prototype.clear = function () {
|
||||
this._el.innerHTML = '';
|
||||
};
|
||||
|
||||
return Drawing;
|
||||
})() : (function () { // Drawing in Canvas
|
||||
function _onMakeImage() {
|
||||
this._elImage.src = this._elCanvas.toDataURL("image/png");
|
||||
this._elImage.style.display = "block";
|
||||
this._elCanvas.style.display = "none";
|
||||
}
|
||||
|
||||
// Android 2.1 bug workaround
|
||||
// http://code.google.com/p/android/issues/detail?id=5141
|
||||
if (this && this._android && this._android <= 2.1) {
|
||||
var factor = 1 / window.devicePixelRatio;
|
||||
var drawImage = CanvasRenderingContext2D.prototype.drawImage;
|
||||
CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
|
||||
if (("nodeName" in image) && /img/i.test(image.nodeName)) {
|
||||
for (var i = arguments.length - 1; i >= 1; i--) {
|
||||
arguments[i] = arguments[i] * factor;
|
||||
}
|
||||
} else if (typeof dw == "undefined") {
|
||||
arguments[1] *= factor;
|
||||
arguments[2] *= factor;
|
||||
arguments[3] *= factor;
|
||||
arguments[4] *= factor;
|
||||
}
|
||||
|
||||
drawImage.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the user's browser supports Data URI or not
|
||||
*
|
||||
* @private
|
||||
* @param {Function} fSuccess Occurs if it supports Data URI
|
||||
* @param {Function} fFail Occurs if it doesn't support Data URI
|
||||
*/
|
||||
function _safeSetDataURI(fSuccess, fFail) {
|
||||
var self = this;
|
||||
self._fFail = fFail;
|
||||
self._fSuccess = fSuccess;
|
||||
|
||||
// Check it just once
|
||||
if (self._bSupportDataURI === null) {
|
||||
var el = document.createElement("img");
|
||||
var fOnError = function() {
|
||||
self._bSupportDataURI = false;
|
||||
|
||||
if (self._fFail) {
|
||||
self._fFail.call(self);
|
||||
}
|
||||
};
|
||||
var fOnSuccess = function() {
|
||||
self._bSupportDataURI = true;
|
||||
|
||||
if (self._fSuccess) {
|
||||
self._fSuccess.call(self);
|
||||
}
|
||||
};
|
||||
|
||||
el.onabort = fOnError;
|
||||
el.onerror = fOnError;
|
||||
el.onload = fOnSuccess;
|
||||
el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
|
||||
return;
|
||||
} else if (self._bSupportDataURI === true && self._fSuccess) {
|
||||
self._fSuccess.call(self);
|
||||
} else if (self._bSupportDataURI === false && self._fFail) {
|
||||
self._fFail.call(self);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Drawing QRCode by using canvas
|
||||
*
|
||||
* @constructor
|
||||
* @param {HTMLElement} el
|
||||
* @param {Object} htOption QRCode Options
|
||||
*/
|
||||
var Drawing = function (el, htOption) {
|
||||
this._bIsPainted = false;
|
||||
this._android = _getAndroid();
|
||||
|
||||
this._htOption = htOption;
|
||||
this._elCanvas = document.createElement("canvas");
|
||||
this._elCanvas.width = htOption.width;
|
||||
this._elCanvas.height = htOption.height;
|
||||
el.appendChild(this._elCanvas);
|
||||
this._el = el;
|
||||
this._oContext = this._elCanvas.getContext("2d");
|
||||
this._bIsPainted = false;
|
||||
this._elImage = document.createElement("img");
|
||||
this._elImage.alt = "Scan me!";
|
||||
this._elImage.style.display = "none";
|
||||
this._el.appendChild(this._elImage);
|
||||
this._bSupportDataURI = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw the QRCode
|
||||
*
|
||||
* @param {QRCode} oQRCode
|
||||
*/
|
||||
Drawing.prototype.draw = function (oQRCode) {
|
||||
var _elImage = this._elImage;
|
||||
var _oContext = this._oContext;
|
||||
var _htOption = this._htOption;
|
||||
|
||||
var nCount = oQRCode.getModuleCount();
|
||||
var nWidth = _htOption.width / nCount;
|
||||
var nHeight = _htOption.height / nCount;
|
||||
var nRoundedWidth = Math.round(nWidth);
|
||||
var nRoundedHeight = Math.round(nHeight);
|
||||
|
||||
_elImage.style.display = "none";
|
||||
this.clear();
|
||||
|
||||
for (var row = 0; row < nCount; row++) {
|
||||
for (var col = 0; col < nCount; col++) {
|
||||
var bIsDark = oQRCode.isDark(row, col);
|
||||
var nLeft = col * nWidth;
|
||||
var nTop = row * nHeight;
|
||||
_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
|
||||
_oContext.lineWidth = 1;
|
||||
_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
|
||||
_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
|
||||
|
||||
// 안티 앨리어싱 방지 처리
|
||||
_oContext.strokeRect(
|
||||
Math.floor(nLeft) + 0.5,
|
||||
Math.floor(nTop) + 0.5,
|
||||
nRoundedWidth,
|
||||
nRoundedHeight
|
||||
);
|
||||
|
||||
_oContext.strokeRect(
|
||||
Math.ceil(nLeft) - 0.5,
|
||||
Math.ceil(nTop) - 0.5,
|
||||
nRoundedWidth,
|
||||
nRoundedHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this._bIsPainted = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make the image from Canvas if the browser supports Data URI.
|
||||
*/
|
||||
Drawing.prototype.makeImage = function () {
|
||||
if (this._bIsPainted) {
|
||||
_safeSetDataURI.call(this, _onMakeImage);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return whether the QRCode is painted or not
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Drawing.prototype.isPainted = function () {
|
||||
return this._bIsPainted;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the QRCode
|
||||
*/
|
||||
Drawing.prototype.clear = function () {
|
||||
this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
|
||||
this._bIsPainted = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Number} nNumber
|
||||
*/
|
||||
Drawing.prototype.round = function (nNumber) {
|
||||
if (!nNumber) {
|
||||
return nNumber;
|
||||
}
|
||||
|
||||
return Math.floor(nNumber * 1000) / 1000;
|
||||
};
|
||||
|
||||
return Drawing;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Get the type by string length
|
||||
*
|
||||
* @private
|
||||
* @param {String} sText
|
||||
* @param {Number} nCorrectLevel
|
||||
* @return {Number} type
|
||||
*/
|
||||
function _getTypeNumber(sText, nCorrectLevel) {
|
||||
var nType = 1;
|
||||
var length = _getUTF8Length(sText);
|
||||
|
||||
for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
|
||||
var nLimit = 0;
|
||||
|
||||
switch (nCorrectLevel) {
|
||||
case QRErrorCorrectLevel.L :
|
||||
nLimit = QRCodeLimitLength[i][0];
|
||||
break;
|
||||
case QRErrorCorrectLevel.M :
|
||||
nLimit = QRCodeLimitLength[i][1];
|
||||
break;
|
||||
case QRErrorCorrectLevel.Q :
|
||||
nLimit = QRCodeLimitLength[i][2];
|
||||
break;
|
||||
case QRErrorCorrectLevel.H :
|
||||
nLimit = QRCodeLimitLength[i][3];
|
||||
break;
|
||||
}
|
||||
|
||||
if (length <= nLimit) {
|
||||
break;
|
||||
} else {
|
||||
nType++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nType > QRCodeLimitLength.length) {
|
||||
throw new Error("Too long data");
|
||||
}
|
||||
|
||||
return nType;
|
||||
}
|
||||
|
||||
function _getUTF8Length(sText) {
|
||||
var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
|
||||
return replacedText.length + (replacedText.length != sText ? 3 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @class QRCode
|
||||
* @constructor
|
||||
* @example
|
||||
* new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
|
||||
*
|
||||
* @example
|
||||
* var oQRCode = new QRCode("test", {
|
||||
* text : "http://naver.com",
|
||||
* width : 128,
|
||||
* height : 128
|
||||
* });
|
||||
*
|
||||
* oQRCode.clear(); // Clear the QRCode.
|
||||
* oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
|
||||
*
|
||||
* @param {HTMLElement|String} el target element or 'id' attribute of element.
|
||||
* @param {Object|String} vOption
|
||||
* @param {String} vOption.text QRCode link data
|
||||
* @param {Number} [vOption.width=256]
|
||||
* @param {Number} [vOption.height=256]
|
||||
* @param {String} [vOption.colorDark="#000000"]
|
||||
* @param {String} [vOption.colorLight="#ffffff"]
|
||||
* @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]
|
||||
*/
|
||||
QRCode = function (el, vOption) {
|
||||
this._htOption = {
|
||||
width : 256,
|
||||
height : 256,
|
||||
typeNumber : 4,
|
||||
colorDark : "#000000",
|
||||
colorLight : "#ffffff",
|
||||
correctLevel : QRErrorCorrectLevel.H
|
||||
};
|
||||
|
||||
if (typeof vOption === 'string') {
|
||||
vOption = {
|
||||
text : vOption
|
||||
};
|
||||
}
|
||||
|
||||
// Overwrites options
|
||||
if (vOption) {
|
||||
for (var i in vOption) {
|
||||
this._htOption[i] = vOption[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof el == "string") {
|
||||
el = document.getElementById(el);
|
||||
}
|
||||
|
||||
if (this._htOption.useSVG) {
|
||||
Drawing = svgDrawer;
|
||||
}
|
||||
|
||||
this._android = _getAndroid();
|
||||
this._el = el;
|
||||
this._oQRCode = null;
|
||||
this._oDrawing = new Drawing(this._el, this._htOption);
|
||||
|
||||
if (this._htOption.text) {
|
||||
this.makeCode(this._htOption.text);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make the QRCode
|
||||
*
|
||||
* @param {String} sText link data
|
||||
*/
|
||||
QRCode.prototype.makeCode = function (sText) {
|
||||
this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
|
||||
this._oQRCode.addData(sText);
|
||||
this._oQRCode.make();
|
||||
this._el.title = sText;
|
||||
this._oDrawing.draw(this._oQRCode);
|
||||
this.makeImage();
|
||||
};
|
||||
|
||||
/**
|
||||
* Make the Image from Canvas element
|
||||
* - It occurs automatically
|
||||
* - Android below 3 doesn't support Data-URI spec.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
QRCode.prototype.makeImage = function () {
|
||||
if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
|
||||
this._oDrawing.makeImage();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the QRCode
|
||||
*/
|
||||
QRCode.prototype.clear = function () {
|
||||
this._oDrawing.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* @name QRCode.CorrectLevel
|
||||
*/
|
||||
QRCode.CorrectLevel = QRErrorCorrectLevel;
|
||||
})();
|
||||
|
||||
export default QRCode;
|
||||
@@ -0,0 +1,94 @@
|
||||
<!--
|
||||
* @Descripttion: 异步选择器
|
||||
* @version: 1.1
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年8月3日15:53:37
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2023年2月23日15:17:24
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-select">
|
||||
<div v-if="initloading" class="sc-select-loading">
|
||||
<el-icon class="is-loading"><el-icon-loading /></el-icon>
|
||||
</div>
|
||||
<el-select v-bind="$attrs" :loading="loading" @visible-change="visibleChange">
|
||||
<el-option v-for="item in options" :key="item[props.value]" :label="item[props.label]" :value="objValueType ? item : item[props.value]">
|
||||
<slot name="option" :data="item"></slot>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/select";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
dic: { type: String, default: "" },
|
||||
objValueType: { type: Boolean, default: false },
|
||||
params: { type: Object, default: () => ({}) },
|
||||
props: { type: Object, default: () => ({
|
||||
label: config.props.label,
|
||||
value: config.props.value
|
||||
}) }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dicParams: this.params,
|
||||
loading: false,
|
||||
options: [],
|
||||
initloading: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
//如果有默认值就去请求接口获取options
|
||||
if(this.hasValue()){
|
||||
this.initloading = true
|
||||
this.getRemoteData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//选项显示隐藏事件
|
||||
visibleChange(ispoen){
|
||||
if(ispoen && this.options.length==0 && (this.dic || this.apiObj)){
|
||||
this.getRemoteData()
|
||||
}
|
||||
},
|
||||
//获取数据
|
||||
async getRemoteData(){
|
||||
this.loading = true
|
||||
this.dicParams[config.request.name] = this.dic
|
||||
var res = {}
|
||||
if(this.apiObj){
|
||||
res = await this.apiObj.get(this.params)
|
||||
}else if(this.dic){
|
||||
res = await config.dicApiObj.get(this.params)
|
||||
}
|
||||
var response = config.parseData(res)
|
||||
this.options = response.data
|
||||
this.loading = false
|
||||
this.initloading = false
|
||||
},
|
||||
//判断是否有回显默认值
|
||||
hasValue(){
|
||||
if(Array.isArray(this.$attrs.modelValue) && this.$attrs.modelValue.length <= 0){
|
||||
return false
|
||||
}else if(this.$attrs.modelValue){
|
||||
return true
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-select {display: inline-block;position: relative;}
|
||||
.sc-select-loading {position: absolute;top:0;left:0;right:0;bottom:0;background: #fff;z-index: 100;border-radius: 5px;border: 1px solid #EBEEF5;display: flex;align-items: center;padding-left:10px;}
|
||||
.sc-select-loading i {font-size: 14px;}
|
||||
|
||||
.dark .sc-select-loading {background: var(--el-bg-color-overlay);border-color: var(--el-border-color-light);}
|
||||
</style>
|
||||
@@ -0,0 +1,127 @@
|
||||
<!--
|
||||
* @Descripttion: 分类筛选器
|
||||
* @version: 1.0
|
||||
* @Author: sakuya
|
||||
* @Date: 2022年5月26日15:59:52
|
||||
* @LastEditors:
|
||||
* @LastEditTime:
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-select-filter">
|
||||
<div v-if="data.length<=0" class="sc-select-filter__no-data">
|
||||
暂无数据
|
||||
</div>
|
||||
<div v-for="item in data" :key="item.key" class="sc-select-filter__item">
|
||||
<div class="sc-select-filter__item-title" :style="{'width':labelWidth+'px'}"><label>{{item.title}}:</label></div>
|
||||
<div class="sc-select-filter__item-options">
|
||||
<ul>
|
||||
<li :class="{'active':selected[item.key]&&selected[item.key].includes(option.value)}" v-for="option in item.options" :key="option.value" @click="select(option, item)">
|
||||
<el-icon v-if="option.icon"><component :is="option.icon" /></el-icon>
|
||||
<span>{{option.label}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data: { type: Array, default: () => [] },
|
||||
selectedValues: { type: Object, default: () => { return {} } },
|
||||
labelWidth: {type: Number, default: 80},
|
||||
outputValueTypeToArray: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selected: {}
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
data(val) {
|
||||
val.forEach(item => {
|
||||
this.selected[item.key] = this.selectedValues[item.key] ||
|
||||
(Array.isArray(item.options) && item.options.length) ? [item.options[0].value] : []
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedString() {
|
||||
var outputData = JSON.parse(JSON.stringify(this.selected))
|
||||
for (var key in outputData) {
|
||||
outputData[key] = outputData[key].join(",")
|
||||
}
|
||||
return outputData
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//默认赋值
|
||||
this.data.forEach(item => {
|
||||
this.selected[item.key] = this.selectedValues[item.key] ||
|
||||
(Array.isArray(item.options) && item.options.length) ? [item.options[0].value] : []
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
select(option, item){
|
||||
//判断单选多选
|
||||
if(item.multiple){
|
||||
//如果多选选择的第一个
|
||||
if(option.value === item.options[0].value){
|
||||
//就赋值第一个的值
|
||||
this.selected[item.key] = [option.value]
|
||||
}else{
|
||||
//如果选择的值已有
|
||||
if(this.selected[item.key].includes(option.value)){
|
||||
//删除选择的值
|
||||
this.selected[item.key].splice(this.selected[item.key].findIndex(s => s === option.value), 1)
|
||||
//当全删光时,把第一个选中
|
||||
if(this.selected[item.key].length == 0){
|
||||
this.selected[item.key] = [item.options[0].value]
|
||||
}
|
||||
}else{
|
||||
//未有值的时候,追加选中值
|
||||
this.selected[item.key].push(option.value)
|
||||
//当含有第一个的值的时候,把第一个删除
|
||||
if(this.selected[item.key].includes(item.options[0].value)){
|
||||
this.selected[item.key].splice(this.selected[item.key].findIndex(s => s === item.options[0].value), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//单选时,如果点击了已有值就赋值
|
||||
if(!this.selected[item.key].includes(option.value)){
|
||||
this.selected[item.key] = [option.value]
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
this.change()
|
||||
},
|
||||
change(){
|
||||
if(this.outputValueTypeToArray){
|
||||
this.$emit('onChange', this.selected)
|
||||
}else{
|
||||
this.$emit('onChange', this.selectedString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-select-filter {width: 100%;}
|
||||
.sc-select-filter__item {display: flex;}
|
||||
.sc-select-filter__item-title {width: 80px;}
|
||||
.sc-select-filter__item-title label {font-size: 14px;padding-top:13px;display: inline-block;color: #999;}
|
||||
.sc-select-filter__item-options {flex: 1;border-bottom: 1px dashed var(--el-border-color-light);}
|
||||
.sc-select-filter__item-options ul {display: flex;flex-wrap: wrap;padding-top: 10px;}
|
||||
.sc-select-filter__item-options li {list-style: none;cursor: pointer;height:28px;padding:0 15px;border-radius:32px;margin: 0 10px 10px 0;display: flex;align-items: center;background: var(--el-color-primary-light-9);}
|
||||
.sc-select-filter__item-options li .el-icon {margin-right: 3px;font-size: 16px;}
|
||||
.sc-select-filter__item-options li:hover {color: var(--el-color-primary);}
|
||||
.sc-select-filter__item-options li.active {background: var(--el-color-primary);color: #fff;font-weight: bold;}
|
||||
.sc-select-filter__item:last-of-type .sc-select-filter__item-options {border: 0;}
|
||||
|
||||
.sc-select-filter__no-data {color: #999;}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<el-select v-bind="$attrs" remote-show-suffix clearable remote :remote-method="querySearchAsync" :loading="loading">
|
||||
<el-option v-for="item in options" :key="item[props.value]" :label="item[props.label]" :value="item[props.value]"/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default{
|
||||
props: {
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
props: { type: Object, default: () => {return {label: 'name', value: 'id'}} }
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
loading: false,
|
||||
options: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
//如果有默认值就去请求接口获取options
|
||||
if(this.hasValue()){
|
||||
this.initloading = true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async querySearchAsync(queryString){
|
||||
let params = {}
|
||||
params[this.props.value] = queryString
|
||||
let res = await this.apiObj.get(params)
|
||||
if(res.code == 1){
|
||||
this.options = res.data
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
//判断是否有回显默认值
|
||||
hasValue(){
|
||||
if(Array.isArray(this.$attrs.modelValue) && this.$attrs.modelValue.length <= 0){
|
||||
return false
|
||||
}else if(this.$attrs.modelValue){
|
||||
this.querySearchAsync(this.$attrs.modelValue)
|
||||
return true
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tree-select v-bind="$attrs" :data="treeData" check-strictly :render-after-expand="false" :default-expand-all="true" :props="defaultProps" clearable style="min-width: 240px;" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from '@/config/select.js'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
props: { type: Object, default: () => {} },
|
||||
hasTop: { type: Boolean, default: false }
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
treeData: [],
|
||||
defaultProps: {label: config.props.label, value: config.props.value}
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.defaultProps = Object.assign(this.defaultProps, this.props);
|
||||
this.$nextTick(() => {
|
||||
this.getData()
|
||||
})
|
||||
},
|
||||
methods:{
|
||||
async getData(){
|
||||
let data = {}
|
||||
data[this.defaultProps.label] = '顶级'
|
||||
data[this.defaultProps.value] = 0
|
||||
let res = await this.apiObj.get({is_tree: 1})
|
||||
if(res.code == 1){
|
||||
this.treeData = this.hasTop ? [data, ...res.data] : res.data
|
||||
}else{
|
||||
this.treeData = this.hasTop ? [data] : []
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,70 @@
|
||||
<!--
|
||||
* @Descripttion: 统计数值组件
|
||||
* @version: 1.1
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年6月23日13:11:32
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年5月14日19:55:09
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-statistic">
|
||||
<div class="sc-statistic-title">
|
||||
{{ title }}
|
||||
<el-tooltip v-if="tips" effect="light">
|
||||
<template #content>
|
||||
<div style="width: 200px;line-height: 2;">
|
||||
{{ tips }}
|
||||
</div>
|
||||
</template>
|
||||
<el-icon class="sc-statistic-tips"><el-icon-question-filled/></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="sc-statistic-content">
|
||||
<span v-if="prefix" class="sc-statistic-content-prefix">{{ prefix }}</span>
|
||||
<span class="sc-statistic-content-value">{{ cmtValue }}</span>
|
||||
<span v-if="suffix" class="sc-statistic-content-suffix">{{ suffix }}</span>
|
||||
</div>
|
||||
<div v-if="description || $slots.default" class="sc-statistic-description">
|
||||
<slot>
|
||||
{{ description }}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: { type: String, required: true, default: "" },
|
||||
value: { type: String, required: true, default: "" },
|
||||
prefix: { type: String, default: "" },
|
||||
suffix: { type: String, default: "" },
|
||||
description: { type: String, default: "" },
|
||||
tips: { type: String, default: "" },
|
||||
groupSeparator: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cmtValue(){
|
||||
return this.groupSeparator ? this.$TOOL.groupSeparator(this.value) : this.value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-statistic-title {font-size: 12px;color: #999;margin-bottom: 10px;display: flex;align-items: center;}
|
||||
.sc-statistic-tips {margin-left: 5px;}
|
||||
.sc-statistic-content {font-size: 20px;color: #333;}
|
||||
.sc-statistic-content-value {font-weight: bold;}
|
||||
.sc-statistic-content-prefix {margin-right: 5px;}
|
||||
.sc-statistic-content-suffix {margin-left: 5px;font-size: 12px;}
|
||||
.sc-statistic-description {margin-top: 10px;color: #999;}
|
||||
|
||||
.dark .sc-statistic-content {color: #d0d0d0;}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { h, resolveComponent } from 'vue'
|
||||
|
||||
export default {
|
||||
render() {
|
||||
return h (
|
||||
resolveComponent("el-table-column"),
|
||||
{
|
||||
index: this.index,
|
||||
...this.$attrs
|
||||
},
|
||||
this.$slots
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
index(index){
|
||||
if(this.$attrs.type=="index"){
|
||||
let page = this.$parent.$parent.currentPage
|
||||
let pageSize = this.$parent.$parent.pageSize
|
||||
return (page - 1) * pageSize + index + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div v-if="usercolumn.length>0" class="setting-column" v-loading="isSave">
|
||||
<div class="setting-column__title">
|
||||
<span class="move_b"></span>
|
||||
<span class="show_b">显示</span>
|
||||
<span class="name_b">名称</span>
|
||||
<span class="width_b">宽度</span>
|
||||
<span class="sortable_b">排序</span>
|
||||
<span class="fixed_b">固定</span>
|
||||
</div>
|
||||
<div class="setting-column__list" ref="list">
|
||||
<ul>
|
||||
<li v-for="item in usercolumn" :key="item.prop">
|
||||
<span class="move_b">
|
||||
<el-tag class="move" style="cursor: move;"><el-icon-d-caret style="width: 1em; height: 1em;"/></el-tag>
|
||||
</span>
|
||||
<span class="show_b">
|
||||
<el-switch v-model="item.hide" :active-value="false" :inactive-value="true"></el-switch>
|
||||
</span>
|
||||
<span class="name_b" :title="item.prop">{{ item.label }}</span>
|
||||
<span class="width_b">
|
||||
<el-input v-model="item.width" placeholder="auto"></el-input>
|
||||
</span>
|
||||
<span class="sortable_b">
|
||||
<el-switch v-model="item.sortable"></el-switch>
|
||||
</span>
|
||||
<span class="fixed_b">
|
||||
<el-switch v-model="item.fixed"></el-switch>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="setting-column__bottom">
|
||||
<el-button @click="backDefaul" :disabled="isSave">重置</el-button>
|
||||
<el-button @click="save" type="primary">保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无可配置的列" :image-size="80"></el-empty>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Sortable
|
||||
},
|
||||
props: {
|
||||
column: { type: Object, default: () => {} }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSave: false,
|
||||
usercolumn: JSON.parse(JSON.stringify(this.column||[]))
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
usercolumn: {
|
||||
handler(){
|
||||
this.$emit('userChange', this.usercolumn)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.usercolumn.length>0 && this.rowDrop()
|
||||
},
|
||||
methods: {
|
||||
rowDrop(){
|
||||
const _this = this
|
||||
const tbody = this.$refs.list.querySelector('ul')
|
||||
Sortable.create(tbody, {
|
||||
handle: ".move",
|
||||
animation: 300,
|
||||
ghostClass: "ghost",
|
||||
onEnd({ newIndex, oldIndex }) {
|
||||
const tableData = _this.usercolumn
|
||||
const currRow = tableData.splice(oldIndex, 1)[0]
|
||||
tableData.splice(newIndex, 0, currRow)
|
||||
}
|
||||
})
|
||||
},
|
||||
backDefaul(){
|
||||
this.$emit('back', this.usercolumn)
|
||||
},
|
||||
save(){
|
||||
this.$emit('save', this.usercolumn)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.setting-column {}
|
||||
|
||||
.setting-column__title {border-bottom: 1px solid #EBEEF5;padding-bottom:15px;}
|
||||
.setting-column__title span {display: inline-block;font-weight: bold;color: #909399;font-size: 12px;}
|
||||
.setting-column__title span.move_b {width: 30px;margin-right:15px;}
|
||||
.setting-column__title span.show_b {width: 60px;}
|
||||
.setting-column__title span.name_b {width: 140px;}
|
||||
.setting-column__title span.width_b {width: 60px;margin-right:15px;}
|
||||
.setting-column__title span.sortable_b {width: 60px;}
|
||||
.setting-column__title span.fixed_b {width: 60px;}
|
||||
|
||||
.setting-column__list {max-height:314px;overflow: auto;}
|
||||
.setting-column__list li {list-style: none;margin:10px 0;display: flex;align-items: center;}
|
||||
.setting-column__list li>span {display: inline-block;font-size: 12px;}
|
||||
.setting-column__list li span.move_b {width: 30px;margin-right:15px;}
|
||||
.setting-column__list li span.show_b {width: 60px;}
|
||||
.setting-column__list li span.name_b {width: 140px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;cursor:default;}
|
||||
.setting-column__list li span.width_b {width: 60px;margin-right:15px;}
|
||||
.setting-column__list li span.sortable_b {width: 60px;}
|
||||
.setting-column__list li span.fixed_b {width: 60px;}
|
||||
.setting-column__list li.ghost {opacity: 0.3;}
|
||||
|
||||
.setting-column__bottom {border-top: 1px solid #EBEEF5;padding-top:15px;text-align: right;}
|
||||
|
||||
.dark .setting-column__title {border-color: var(--el-border-color-light);}
|
||||
.dark .setting-column__bottom {border-color: var(--el-border-color-light);}
|
||||
</style>
|
||||
@@ -0,0 +1,409 @@
|
||||
<!--
|
||||
* @Descripttion: 数据表格组件
|
||||
* @version: 1.10
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年11月29日21:51:15
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年6月4日17:35:26
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="scTable" :style="{'height':_height}" ref="scTableMain" v-loading="loading">
|
||||
<div class="scTable-table" :style="{'height':_table_height}">
|
||||
<el-table v-bind="$attrs" :data="tableData" :row-key="rowKey" :key="toggleIndex" ref="scTable" :height="height=='auto'?null:'100%'" :size="config.size" :border="config.border" :stripe="config.stripe" :summary-method="remoteSummary?remoteSummaryMethod:summaryMethod" @sort-change="sortChange" @filter-change="filterChange">
|
||||
<slot></slot>
|
||||
<template v-for="(item, index) in userColumn" :key="index">
|
||||
<el-table-column v-if="!item.hide" :column-key="item.prop" :label="item.label" :prop="item.prop" :width="item.width" :sortable="item.sortable" :fixed="item.fixed" :filters="item.filters" :filter-method="remoteFilter||!item.filters?null:filterHandler" :show-overflow-tooltip="item.showOverflowTooltip">
|
||||
<template #default="scope">
|
||||
<slot :name="item.prop" v-bind="scope">
|
||||
{{scope.row[item.prop]}}
|
||||
</slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column min-width="1"></el-table-column>
|
||||
<template #empty>
|
||||
<el-empty :description="emptyText" :image-size="100"></el-empty>
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="scTable-page" v-if="!hidePagination || !hideDo">
|
||||
<div class="scTable-pagination">
|
||||
<el-pagination v-if="!hidePagination" background :layout="paginationLayout" :total="total" :page-size="scPageSize" :page-sizes="pageSizes" v-model:currentPage="currentPage" @current-change="paginationChange" @update:page-size="pageSizeChange"></el-pagination>
|
||||
</div>
|
||||
<div class="scTable-do" v-if="!hideDo">
|
||||
<el-button v-if="!hideRefresh" @click="refresh" icon="el-icon-refresh" circle style="margin-left:15px"></el-button>
|
||||
<el-popover v-if="column" placement="top" title="列设置" :width="500" trigger="click" :hide-after="0" @show="customColumnShow=true" @after-leave="customColumnShow=false">
|
||||
<template #reference>
|
||||
<el-button icon="el-icon-set-up" circle style="margin-left:15px"></el-button>
|
||||
</template>
|
||||
<columnSetting v-if="customColumnShow" ref="columnSetting" @userChange="columnSettingChange" @save="columnSettingSave" @back="columnSettingBack" :column="userColumn"></columnSetting>
|
||||
</el-popover>
|
||||
<el-popover v-if="!hideSetting" placement="top" title="表格设置" :width="400" trigger="click" :hide-after="0">
|
||||
<template #reference>
|
||||
<el-button icon="el-icon-setting" circle style="margin-left:15px"></el-button>
|
||||
</template>
|
||||
<el-form label-width="80px" label-position="left">
|
||||
<el-form-item label="表格尺寸">
|
||||
<el-radio-group v-model="config.size" @change="configSizeChange">
|
||||
<el-radio-button value="large">大</el-radio-button>
|
||||
<el-radio-button value="default">正常</el-radio-button>
|
||||
<el-radio-button value="small">小</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="样式">
|
||||
<el-checkbox v-model="config.border" label="纵向边框"></el-checkbox>
|
||||
<el-checkbox v-model="config.stripe" label="斑马纹"></el-checkbox>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/table";
|
||||
import columnSetting from './columnSetting'
|
||||
|
||||
export default {
|
||||
name: 'scTable',
|
||||
components: {
|
||||
columnSetting
|
||||
},
|
||||
props: {
|
||||
tableName: { type: String, default: "" },
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
params: { type: Object, default: () => ({}) },
|
||||
data: { type: Object, default: () => {} },
|
||||
height: { type: [String,Number], default: "100%" },
|
||||
size: { type: String, default: "small" },
|
||||
border: { type: Boolean, default: true },
|
||||
stripe: { type: Boolean, default: false },
|
||||
pageSize: { type: Number, default: config.pageSize },
|
||||
pageSizes: { type: Array, default: config.pageSizes },
|
||||
rowKey: { type: String, default: "" },
|
||||
summaryMethod: { type: Function, default: null },
|
||||
column: { type: Object, default: () => {} },
|
||||
remoteSort: { type: Boolean, default: false },
|
||||
remoteFilter: { type: Boolean, default: false },
|
||||
remoteSummary: { type: Boolean, default: false },
|
||||
hidePagination: { type: Boolean, default: false },
|
||||
hideDo: { type: Boolean, default: false },
|
||||
hideRefresh: { type: Boolean, default: false },
|
||||
hideSetting: { type: Boolean, default: false },
|
||||
paginationLayout: { type: String, default: config.paginationLayout },
|
||||
},
|
||||
watch: {
|
||||
//监听从props里拿到值了
|
||||
data(){
|
||||
this.tableData = this.data;
|
||||
this.total = this.tableData.length;
|
||||
},
|
||||
apiObj(){
|
||||
this.tableParams = this.params;
|
||||
this.refresh();
|
||||
},
|
||||
column(){
|
||||
this.userColumn=this.column;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_height() {
|
||||
return Number(this.height)?Number(this.height)+'px':this.height
|
||||
},
|
||||
_table_height() {
|
||||
return this.hidePagination && this.hideDo ? "100%" : "calc(100% - 50px)"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
scPageSize: this.pageSize,
|
||||
isActivat: true,
|
||||
emptyText: "暂无数据",
|
||||
toggleIndex: 0,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
prop: null,
|
||||
order: null,
|
||||
loading: false,
|
||||
tableHeight:'100%',
|
||||
tableParams: this.params,
|
||||
userColumn: [],
|
||||
customColumnShow: false,
|
||||
summary: {},
|
||||
config: {
|
||||
size: this.size,
|
||||
border: this.border,
|
||||
stripe: this.stripe
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//判断是否开启自定义列
|
||||
if(this.column){
|
||||
this.getCustomColumn()
|
||||
}else{
|
||||
this.userColumn = this.column
|
||||
}
|
||||
//判断是否静态数据
|
||||
if(this.apiObj){
|
||||
this.getData();
|
||||
}else if(this.data){
|
||||
this.tableData = this.data;
|
||||
this.total = this.tableData.length
|
||||
}
|
||||
},
|
||||
activated(){
|
||||
if(!this.isActivat){
|
||||
this.$refs.scTable.doLayout()
|
||||
}
|
||||
},
|
||||
deactivated(){
|
||||
this.isActivat = false;
|
||||
},
|
||||
methods: {
|
||||
//获取列
|
||||
async getCustomColumn(){
|
||||
const userColumn = await config.columnSettingGet(this.tableName, this.column)
|
||||
this.userColumn = userColumn
|
||||
},
|
||||
//获取数据
|
||||
async getData(){
|
||||
this.tableData = []
|
||||
this.loading = true;
|
||||
var reqData = {
|
||||
[config.request.page]: this.currentPage,
|
||||
[config.request.pageSize]: this.scPageSize,
|
||||
[config.request.prop]: this.prop,
|
||||
[config.request.order]: this.order
|
||||
}
|
||||
if(this.hidePagination){
|
||||
delete reqData[config.request.page]
|
||||
delete reqData[config.request.pageSize]
|
||||
}
|
||||
Object.assign(reqData, this.tableParams)
|
||||
|
||||
try {
|
||||
var res = await this.apiObj.get(reqData);
|
||||
}catch(error){
|
||||
this.loading = false;
|
||||
this.emptyText = error.statusText;
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var response = config.parseData(res);
|
||||
}catch(error){
|
||||
this.loading = false;
|
||||
this.emptyText = "数据格式错误";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(response.code != config.successCode){
|
||||
this.loading = false;
|
||||
this.emptyText = response.msg;
|
||||
}else{
|
||||
this.emptyText = "暂无数据";
|
||||
if(this.hidePagination){
|
||||
this.tableData = response.data || [];
|
||||
}else{
|
||||
this.tableData = response.rows || [];
|
||||
}
|
||||
this.total = response.total || 0;
|
||||
this.summary = response.summary || {};
|
||||
this.loading = false;
|
||||
}
|
||||
// this.$refs.scTable.setScrollTop(0)
|
||||
this.$emit('dataChange', res, this.tableData)
|
||||
},
|
||||
//分页点击
|
||||
paginationChange(){
|
||||
this.getData();
|
||||
},
|
||||
//条数变化
|
||||
pageSizeChange(size){
|
||||
this.scPageSize = size
|
||||
this.getData();
|
||||
},
|
||||
//刷新数据
|
||||
refresh(){
|
||||
this.$refs.scTable.clearSelection();
|
||||
this.getData();
|
||||
},
|
||||
//更新数据 合并上一次params
|
||||
upData(params, page=1){
|
||||
this.currentPage = page;
|
||||
this.$refs.scTable.clearSelection();
|
||||
Object.assign(this.tableParams, params || {})
|
||||
this.getData()
|
||||
},
|
||||
//重载数据 替换params
|
||||
reload(params, page=1){
|
||||
this.currentPage = page;
|
||||
this.tableParams = params || {}
|
||||
this.$refs.scTable.clearSelection();
|
||||
this.$refs.scTable.clearSort()
|
||||
this.$refs.scTable.clearFilter()
|
||||
this.getData()
|
||||
},
|
||||
//自定义变化事件
|
||||
columnSettingChange(userColumn){
|
||||
this.userColumn = userColumn;
|
||||
this.toggleIndex += 1;
|
||||
},
|
||||
//自定义列保存
|
||||
async columnSettingSave(userColumn){
|
||||
this.$refs.columnSetting.isSave = true
|
||||
try {
|
||||
await config.columnSettingSave(this.tableName, userColumn)
|
||||
}catch(error){
|
||||
this.$message.error('保存失败')
|
||||
this.$refs.columnSetting.isSave = false
|
||||
}
|
||||
this.$message.success('保存成功')
|
||||
this.$refs.columnSetting.isSave = false
|
||||
},
|
||||
//自定义列重置
|
||||
async columnSettingBack(){
|
||||
this.$refs.columnSetting.isSave = true
|
||||
try {
|
||||
const column = await config.columnSettingReset(this.tableName, this.column)
|
||||
this.userColumn = column
|
||||
this.$refs.columnSetting.usercolumn = JSON.parse(JSON.stringify(this.userColumn||[]))
|
||||
}catch(error){
|
||||
this.$message.error('重置失败')
|
||||
this.$refs.columnSetting.isSave = false
|
||||
}
|
||||
this.$refs.columnSetting.isSave = false
|
||||
},
|
||||
//排序事件
|
||||
sortChange(obj){
|
||||
if(!this.remoteSort){
|
||||
return false
|
||||
}
|
||||
if(obj.column && obj.prop){
|
||||
this.prop = obj.prop
|
||||
this.order = obj.order
|
||||
}else{
|
||||
this.prop = null
|
||||
this.order = null
|
||||
}
|
||||
this.getData()
|
||||
},
|
||||
//本地过滤
|
||||
filterHandler(value, row, column){
|
||||
const property = column.property;
|
||||
return row[property] === value;
|
||||
},
|
||||
//过滤事件
|
||||
filterChange(filters){
|
||||
if(!this.remoteFilter){
|
||||
return false
|
||||
}
|
||||
Object.keys(filters).forEach(key => {
|
||||
filters[key] = filters[key].join(',')
|
||||
})
|
||||
this.upData(filters)
|
||||
},
|
||||
//远程合计行处理
|
||||
remoteSummaryMethod(param){
|
||||
const {columns} = param
|
||||
const sums = []
|
||||
columns.forEach((column, index) => {
|
||||
if(index === 0) {
|
||||
sums[index] = '合计'
|
||||
return
|
||||
}
|
||||
const values = this.summary[column.property]
|
||||
if(values){
|
||||
sums[index] = values
|
||||
}else{
|
||||
sums[index] = ''
|
||||
}
|
||||
})
|
||||
return sums
|
||||
},
|
||||
configSizeChange(){
|
||||
this.$refs.scTable.doLayout()
|
||||
},
|
||||
//插入行 unshiftRow
|
||||
unshiftRow(row){
|
||||
this.tableData.unshift(row)
|
||||
},
|
||||
//插入行 pushRow
|
||||
pushRow(row){
|
||||
this.tableData.push(row)
|
||||
},
|
||||
//根据key覆盖数据
|
||||
updateKey(row, rowKey=this.rowKey){
|
||||
this.tableData.filter(item => item[rowKey]===row[rowKey] ).forEach(item => {
|
||||
Object.assign(item, row)
|
||||
})
|
||||
},
|
||||
//根据index覆盖数据
|
||||
updateIndex(row, index){
|
||||
Object.assign(this.tableData[index], row)
|
||||
},
|
||||
//根据index删除
|
||||
removeIndex(index){
|
||||
this.tableData.splice(index, 1)
|
||||
},
|
||||
//根据index批量删除
|
||||
removeIndexes(indexes=[]){
|
||||
indexes.forEach(index => {
|
||||
this.tableData.splice(index, 1)
|
||||
})
|
||||
},
|
||||
//根据key删除
|
||||
removeKey(key, rowKey=this.rowKey){
|
||||
this.tableData.splice(this.tableData.findIndex(item => item[rowKey]===key), 1)
|
||||
},
|
||||
//根据keys批量删除
|
||||
removeKeys(keys=[], rowKey=this.rowKey){
|
||||
keys.forEach(key => {
|
||||
this.tableData.splice(this.tableData.findIndex(item => item[rowKey]===key), 1)
|
||||
})
|
||||
},
|
||||
//原生方法转发
|
||||
clearSelection(){
|
||||
this.$refs.scTable.clearSelection()
|
||||
},
|
||||
toggleRowSelection(row, selected){
|
||||
this.$refs.scTable.toggleRowSelection(row, selected)
|
||||
},
|
||||
toggleAllSelection(){
|
||||
this.$refs.scTable.toggleAllSelection()
|
||||
},
|
||||
toggleRowExpansion(row, expanded){
|
||||
this.$refs.scTable.toggleRowExpansion(row, expanded)
|
||||
},
|
||||
setCurrentRow(row){
|
||||
this.$refs.scTable.setCurrentRow(row)
|
||||
},
|
||||
clearSort(){
|
||||
this.$refs.scTable.clearSort()
|
||||
},
|
||||
clearFilter(columnKey){
|
||||
this.$refs.scTable.clearFilter(columnKey)
|
||||
},
|
||||
doLayout(){
|
||||
this.$refs.scTable.doLayout()
|
||||
},
|
||||
sort(prop, order){
|
||||
this.$refs.scTable.sort(prop, order)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scTable {}
|
||||
.scTable-table {height: calc(100% - 50px);}
|
||||
.scTable-page {height:50px;display: flex;align-items: center;justify-content: space-between;padding:0 15px;}
|
||||
.scTable-do {white-space: nowrap;}
|
||||
.scTable:deep(.el-table__footer) .cell {font-weight: bold;}
|
||||
.scTable:deep(.el-table__body-wrapper) .el-scrollbar__bar.is-horizontal {height: 12px;border-radius: 12px;}
|
||||
.scTable:deep(.el-table__body-wrapper) .el-scrollbar__bar.is-vertical {width: 12px;border-radius: 12px;}
|
||||
</style>
|
||||
@@ -0,0 +1,233 @@
|
||||
<!--
|
||||
* @Descripttion: 表格选择器组件
|
||||
* @version: 1.3
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年6月10日10:04:07
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年6月6日21:50:36
|
||||
-->
|
||||
|
||||
<template>
|
||||
<el-select ref="select" v-model="defaultLable" :size="size" :clearable="clearable" :multiple="multiple" :collapse-tags="collapseTags" :collapse-tags-tooltip="collapseTagsTooltip" :filterable="filterable" :placeholder="placeholder" :disabled="disabled" :filter-method="filterMethod" @remove-tag="removeTag" @visible-change="visibleChange" @clear="clear">
|
||||
<template #header>
|
||||
<slot name="header" :form="formData" :submit="formSubmit"></slot>
|
||||
</template>
|
||||
<template #empty>
|
||||
<div :style="{width: tableWidth+'px'}" v-loading="loading">
|
||||
<el-table ref="table" :data="tableData" :height="245" :highlight-current-row="!multiple" @row-click="click" @select="select" @select-all="selectAll">
|
||||
<el-table-column v-if="multiple" type="selection" width="45"></el-table-column>
|
||||
<el-table-column v-else type="index" width="45">
|
||||
<template #default="scope"><span>{{scope.$index+(currentPage - 1) * pageSize + 1}}</span></template>
|
||||
</el-table-column>
|
||||
<slot></slot>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-pagination small background layout="prev, pager, next" :total="total" :page-size="pageSize" v-model:currentPage="currentPage" @current-change="reload"></el-pagination>
|
||||
</template>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/tableSelect";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: null,
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
params: { type: Object, default: () => {} },
|
||||
placeholder: { type: String, default: "请选择" },
|
||||
size: { type: String, default: "default" },
|
||||
clearable: { type: Boolean, default: false },
|
||||
multiple: { type: Boolean, default: false },
|
||||
filterable: { type: Boolean, default: false },
|
||||
collapseTags: { type: Boolean, default: false },
|
||||
collapseTagsTooltip: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
tableWidth: {type: Number, default: 400},
|
||||
mode: { type: String, default: "popover" },
|
||||
props: { type: Object, default: () => {} }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
keyword: null,
|
||||
defaultValue: [],
|
||||
defaultLable: [],
|
||||
tableData: [],
|
||||
pageSize: config.pageSize,
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
defaultProps: {
|
||||
label: config.props.label,
|
||||
value: config.props.value,
|
||||
page: config.request.page,
|
||||
pageSize: config.request.pageSize,
|
||||
keyword: config.request.keyword
|
||||
},
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
watch: {
|
||||
modelValue:{
|
||||
handler(){
|
||||
this.defaultValue = this.modelValue
|
||||
this.autoCurrentLabel()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.defaultProps = Object.assign(this.defaultProps, this.props);
|
||||
this.defaultValue = this.modelValue
|
||||
this.autoCurrentLabel()
|
||||
},
|
||||
methods: {
|
||||
//表格显示隐藏回调
|
||||
visibleChange(visible){
|
||||
if(visible){
|
||||
this.currentPage = 1
|
||||
this.keyword = null
|
||||
this.formData = {}
|
||||
this.getData()
|
||||
}else{
|
||||
this.autoCurrentLabel()
|
||||
}
|
||||
},
|
||||
//获取表格数据
|
||||
async getData(){
|
||||
this.loading = true;
|
||||
var reqData = {
|
||||
[this.defaultProps.page]: this.currentPage,
|
||||
[this.defaultProps.pageSize]: this.pageSize,
|
||||
[this.defaultProps.keyword]: this.keyword
|
||||
}
|
||||
Object.assign(reqData, this.params, this.formData)
|
||||
var res = await this.apiObj.get(reqData);
|
||||
var parseData = config.parseData(res)
|
||||
this.tableData = parseData.rows;
|
||||
this.total = parseData.total;
|
||||
this.loading = false;
|
||||
//表格默认赋值
|
||||
this.$nextTick(() => {
|
||||
if(this.multiple){
|
||||
this.defaultValue.forEach(row => {
|
||||
var setrow = this.tableData.filter(item => item[this.defaultProps.value]===row[this.defaultProps.value] )
|
||||
if(setrow.length > 0){
|
||||
this.$refs.table.toggleRowSelection(setrow[0], true);
|
||||
}
|
||||
})
|
||||
}else{
|
||||
var setrow = this.tableData.filter(item => item[this.defaultProps.value]===this.defaultValue[this.defaultProps.value] )
|
||||
this.$refs.table.setCurrentRow(setrow[0]);
|
||||
}
|
||||
this.$refs.table.setScrollTop(0)
|
||||
})
|
||||
},
|
||||
//插糟表单提交
|
||||
formSubmit(){
|
||||
this.currentPage = 1
|
||||
this.keyword = null
|
||||
this.getData()
|
||||
},
|
||||
//分页刷新表格
|
||||
reload(){
|
||||
this.getData()
|
||||
},
|
||||
//自动模拟options赋值
|
||||
autoCurrentLabel(){
|
||||
this.$nextTick(() => {
|
||||
if(this.multiple){
|
||||
this.defaultValue.map(item => {
|
||||
this.defaultLable.push(item[this.props.label])
|
||||
})
|
||||
}else{
|
||||
this.defaultLable = this.defaultValue[this.defaultProps.label]
|
||||
}
|
||||
})
|
||||
},
|
||||
//表格勾选事件
|
||||
select(rows, row){
|
||||
var isSelect = rows.length && rows.indexOf(row) !== -1
|
||||
if(isSelect){
|
||||
this.defaultValue.push(row)
|
||||
}else{
|
||||
this.defaultValue.splice(this.defaultValue.findIndex(item => item[this.defaultProps.value] == row[this.defaultProps.value]), 1)
|
||||
}
|
||||
this.autoCurrentLabel()
|
||||
this.$emit('update:modelValue', this.defaultValue);
|
||||
this.$emit('change', this.defaultValue);
|
||||
},
|
||||
//表格全选事件
|
||||
selectAll(rows){
|
||||
var isAllSelect = rows.length > 0
|
||||
if(isAllSelect){
|
||||
rows.forEach(row => {
|
||||
var isHas = this.defaultValue.find(item => item[this.defaultProps.value] == row[this.defaultProps.value])
|
||||
if(!isHas){
|
||||
this.defaultValue.push(row)
|
||||
}
|
||||
})
|
||||
}else{
|
||||
this.tableData.forEach(row => {
|
||||
var isHas = this.defaultValue.find(item => item[this.defaultProps.value] == row[this.defaultProps.value])
|
||||
if(isHas){
|
||||
this.defaultValue.splice(this.defaultValue.findIndex(item => item[this.defaultProps.value] == row[this.defaultProps.value]), 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
this.autoCurrentLabel()
|
||||
this.$emit('update:modelValue', this.defaultValue);
|
||||
this.$emit('change', this.defaultValue);
|
||||
},
|
||||
click(row){
|
||||
if(this.multiple){
|
||||
//处理多选点击行
|
||||
}else{
|
||||
this.defaultValue = row
|
||||
this.$refs.select.blur()
|
||||
this.autoCurrentLabel()
|
||||
this.$emit('update:modelValue', this.defaultValue);
|
||||
this.$emit('change', this.defaultValue);
|
||||
}
|
||||
},
|
||||
//tags删除后回调
|
||||
removeTag(tag){
|
||||
var row = this.findRowByKey(tag[this.defaultProps.value])
|
||||
this.$refs.table.toggleRowSelection(row, false);
|
||||
this.$emit('update:modelValue', this.defaultValue);
|
||||
},
|
||||
//清空后的回调
|
||||
clear(){
|
||||
this.$emit('update:modelValue', this.defaultValue);
|
||||
},
|
||||
// 关键值查询表格数据行
|
||||
findRowByKey (value) {
|
||||
return this.tableData.find(item => item[this.defaultProps.value] === value)
|
||||
},
|
||||
filterMethod(keyword){
|
||||
if(!keyword){
|
||||
this.keyword = null;
|
||||
return false;
|
||||
}
|
||||
this.keyword = keyword;
|
||||
this.getData()
|
||||
},
|
||||
// 触发select隐藏
|
||||
blur(){
|
||||
this.$refs.select.blur();
|
||||
},
|
||||
// 触发select显示
|
||||
focus(){
|
||||
this.$refs.select.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div class="sc-title">
|
||||
<span class="title">{{title}}</span>
|
||||
<span><slot name="right"></slot></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: { type: String, required: true, default: "" },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-title {border-bottom: 1px solid #eee;padding: 5px; margin: 15px 0 5px 0; display: flex;justify-content: space-between;}
|
||||
.sc-title span.title{font-size: 14px; color: #3c4a54;font-weight: bold;}
|
||||
</style>
|
||||
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="sc-upload-file">
|
||||
<el-upload
|
||||
:disabled="disabled"
|
||||
:auto-upload="autoUpload"
|
||||
:action="action"
|
||||
:name="name"
|
||||
:data="data"
|
||||
:http-request="request"
|
||||
v-model:file-list="defaultFileList"
|
||||
:show-file-list="showFileList"
|
||||
:drag="drag"
|
||||
:accept="accept"
|
||||
:multiple="multiple"
|
||||
:limit="limit"
|
||||
:before-upload="before"
|
||||
:on-success="success"
|
||||
:on-error="error"
|
||||
:on-preview="handlePreview"
|
||||
:on-exceed="handleExceed">
|
||||
<slot>
|
||||
<el-button type="primary" :disabled="disabled">点击上传</el-button>
|
||||
</slot>
|
||||
<template #tip>
|
||||
<div v-if="tip" class="el-upload__tip">{{tip}}</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<span style="display:none!important"><el-input v-model="value"></el-input></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/upload"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: [String, Array], default: "" },
|
||||
tip: { type: String, default: "" },
|
||||
action: { type: String, default: "" },
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
name: { type: String, default: config.filename },
|
||||
data: { type: Object, default: () => {} },
|
||||
accept: { type: String, default: "" },
|
||||
maxSize: { type: Number, default: config.maxSizeFile },
|
||||
limit: { type: Number, default: 0 },
|
||||
autoUpload: { type: Boolean, default: true },
|
||||
showFileList: { type: Boolean, default: true },
|
||||
drag: { type: Boolean, default: false },
|
||||
multiple: { type: Boolean, default: true },
|
||||
disabled: { type: Boolean, default: false },
|
||||
onSuccess: { type: Function, default: () => { return true } }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: "",
|
||||
defaultFileList: []
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(val){
|
||||
if(Array.isArray(val)){
|
||||
if (JSON.stringify(val) != JSON.stringify(this.formatArr(this.defaultFileList))) {
|
||||
this.defaultFileList = val
|
||||
this.value = val
|
||||
}
|
||||
}else{
|
||||
if (val != this.toStr(this.defaultFileList)) {
|
||||
this.defaultFileList = this.toArr(val)
|
||||
this.value = val
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultFileList: {
|
||||
handler(val){
|
||||
this.$emit('update:modelValue', Array.isArray(this.modelValue) ? this.formatArr(val) : this.toStr(val))
|
||||
this.value = this.toStr(val)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.defaultFileList = Array.isArray(this.modelValue) ? this.modelValue : this.toArr(this.modelValue)
|
||||
this.value = this.modelValue
|
||||
},
|
||||
methods: {
|
||||
//默认值转换为数组
|
||||
toArr(str){
|
||||
if(!str){
|
||||
return []
|
||||
}
|
||||
var _arr = []
|
||||
var arr = str.split(",")
|
||||
arr.forEach(item => {
|
||||
if(item){
|
||||
var urlArr = item.split('/');
|
||||
var fileName = urlArr[urlArr.length - 1]
|
||||
_arr.push({
|
||||
name: fileName,
|
||||
url: item
|
||||
})
|
||||
}
|
||||
})
|
||||
return _arr
|
||||
},
|
||||
//数组转换为原始值
|
||||
toStr(arr){
|
||||
return arr.map(v => v.url).join(",")
|
||||
},
|
||||
//格式化数组值
|
||||
formatArr(arr){
|
||||
var _arr = []
|
||||
arr.forEach(item => {
|
||||
if(item){
|
||||
_arr.push({
|
||||
name: item.name,
|
||||
url: item.url
|
||||
})
|
||||
}
|
||||
})
|
||||
return _arr
|
||||
},
|
||||
before(file){
|
||||
const maxSize = file.size / 1024 / 1024 < this.maxSize;
|
||||
if (!maxSize) {
|
||||
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
success(res, file){
|
||||
var os = this.onSuccess(res, file)
|
||||
if(os!=undefined && os==false){
|
||||
return false
|
||||
}
|
||||
var response = config.parseData(res)
|
||||
file.name = response.fileName
|
||||
file.url = response.src
|
||||
},
|
||||
error(err){
|
||||
this.$notify.error({
|
||||
title: '上传文件未成功',
|
||||
message: err
|
||||
})
|
||||
},
|
||||
beforeRemove(uploadFile){
|
||||
return this.$confirm(`是否移除 ${uploadFile.name} ?`, '提示', {
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
return true
|
||||
}).catch(() => {
|
||||
return false
|
||||
})
|
||||
},
|
||||
handleExceed(){
|
||||
this.$message.warning(`当前设置最多上传 ${this.limit} 个文件,请移除后上传!`)
|
||||
},
|
||||
handlePreview(uploadFile){
|
||||
window.open(uploadFile.url)
|
||||
},
|
||||
request(param){
|
||||
var apiObj = config.apiObjFile;
|
||||
if(this.apiObj){
|
||||
apiObj = this.apiObj;
|
||||
}
|
||||
const data = new FormData();
|
||||
data.append(param.filename, param.file);
|
||||
for (const key in param.data) {
|
||||
data.append(key, param.data[key]);
|
||||
}
|
||||
apiObj.post(data, {
|
||||
onUploadProgress: e => {
|
||||
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
|
||||
param.onProgress({percent: complete})
|
||||
}
|
||||
}).then(res => {
|
||||
var response = config.parseData(res);
|
||||
if(response.code == config.successCode){
|
||||
param.onSuccess(res)
|
||||
}else{
|
||||
param.onError(response.msg || "未知错误")
|
||||
}
|
||||
}).catch(err => {
|
||||
param.onError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form-item.is-error .sc-upload-file:deep(.el-upload-dragger) {border-color: var(--el-color-danger);}
|
||||
.sc-upload-file {width: 100%;}
|
||||
.sc-upload-file:deep(.el-upload-list__item) {transition: none !important;}
|
||||
</style>
|
||||
@@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<div class="sc-upload" :class="{'sc-upload-round':round}" :style="style">
|
||||
<div v-if="file && file.status != 'success'" class="sc-upload__uploading">
|
||||
<div class="sc-upload__progress">
|
||||
<el-progress :percentage="file.percentage" :text-inside="true" :stroke-width="16"/>
|
||||
</div>
|
||||
<el-image class="image" :src="file.tempFile" fit="cover"></el-image>
|
||||
</div>
|
||||
<div v-if="file && file.status=='success'" class="sc-upload__img">
|
||||
<el-image class="image" :src="file.url" :preview-src-list="[file.url]" fit="cover" hide-on-click-modal append-to-body :z-index="9999">
|
||||
<template #placeholder>
|
||||
<div class="sc-upload__img-slot">
|
||||
Loading...
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="sc-upload__img-actions" v-if="!disabled">
|
||||
<span class="del" @click="handleRemove()"><el-icon><el-icon-delete /></el-icon></span>
|
||||
</div>
|
||||
</div>
|
||||
<el-upload v-if="!file" class="uploader" ref="uploader"
|
||||
:auto-upload="cropper?false:autoUpload"
|
||||
:disabled="disabled"
|
||||
:show-file-list="showFileList"
|
||||
:action="action"
|
||||
:name="name"
|
||||
:data="data"
|
||||
:accept="accept"
|
||||
:limit="1"
|
||||
:http-request="request"
|
||||
:on-change="change"
|
||||
:before-upload="before"
|
||||
:on-success="success"
|
||||
:on-error="error"
|
||||
:on-exceed="handleExceed"
|
||||
drag>
|
||||
<slot>
|
||||
<div class="el-upload--picture-card">
|
||||
<div class="file-empty">
|
||||
<el-icon><component :is="icon" /></el-icon>
|
||||
<h4 v-if="title">{{title}}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</el-upload>
|
||||
<span style="display:none!important"><el-input v-model="value"></el-input></span>
|
||||
<el-dialog title="剪裁" draggable v-model="cropperDialogVisible" :width="680" @closed="cropperClosed" destroy-on-close>
|
||||
<sc-cropper :src="cropperFile.tempCropperFile" :compress="compress" :aspectRatio="aspectRatio" ref="cropper"></sc-cropper>
|
||||
<template #footer>
|
||||
<el-button @click="cropperDialogVisible=false" >取 消</el-button>
|
||||
<el-button type="primary" @click="cropperSave">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { genFileId } from 'element-plus'
|
||||
const scCropper = defineAsyncComponent(() => import('@/components/scCropper'))
|
||||
import config from "@/config/upload"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: String, default: "" },
|
||||
height: {type: Number, default: 148},
|
||||
width: {type: Number, default: 148},
|
||||
title: { type: String, default: "" },
|
||||
icon: { type: String, default: "el-icon-plus" },
|
||||
action: { type: String, default: "" },
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
name: { type: String, default: config.filename },
|
||||
data: { type: Object, default: () => {} },
|
||||
accept: { type: String, default: "image/gif, image/jpeg, image/png" },
|
||||
maxSize: { type: Number, default: config.maxSizeFile },
|
||||
limit: { type: Number, default: 1 },
|
||||
autoUpload: { type: Boolean, default: true },
|
||||
showFileList: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
round: { type: Boolean, default: false },
|
||||
onSuccess: { type: Function, default: () => { return true } },
|
||||
|
||||
cropper: { type: Boolean, default: false },
|
||||
compress: {type: Number, default: 1},
|
||||
aspectRatio: {type: Number, default: NaN}
|
||||
},
|
||||
components: {
|
||||
scCropper
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: "",
|
||||
file: null,
|
||||
style: {
|
||||
width: this.width + "px",
|
||||
height: this.height + "px"
|
||||
},
|
||||
cropperDialogVisible: false,
|
||||
cropperFile: null
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(val){
|
||||
this.value = val
|
||||
this.newFile(val)
|
||||
},
|
||||
value(val){
|
||||
this.$emit('update:modelValue', val)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.value = this.modelValue
|
||||
this.newFile(this.modelValue)
|
||||
},
|
||||
methods: {
|
||||
newFile(url){
|
||||
if(url){
|
||||
this.file = {
|
||||
status: "success",
|
||||
url: url
|
||||
}
|
||||
}else{
|
||||
this.file = null
|
||||
}
|
||||
},
|
||||
cropperSave(){
|
||||
this.$refs.cropper.getCropFile(file => {
|
||||
|
||||
file.uid = this.cropperFile.uid
|
||||
this.cropperFile.raw = file
|
||||
|
||||
this.file = this.cropperFile
|
||||
this.file.tempFile = URL.createObjectURL(this.file.raw)
|
||||
this.$refs.uploader.submit()
|
||||
|
||||
}, this.cropperFile.name, this.cropperFile.type)
|
||||
this.cropperDialogVisible = false
|
||||
},
|
||||
cropperClosed(){
|
||||
URL.revokeObjectURL(this.cropperFile.tempCropperFile)
|
||||
delete this.cropperFile.tempCropperFile
|
||||
},
|
||||
handleRemove(){
|
||||
this.clearFiles()
|
||||
},
|
||||
clearFiles(){
|
||||
URL.revokeObjectURL(this.file.tempFile)
|
||||
this.value = ""
|
||||
this.file = null
|
||||
this.$nextTick(()=>{
|
||||
this.$refs.uploader.clearFiles()
|
||||
})
|
||||
},
|
||||
change(file,files){
|
||||
if(files.length > 1){
|
||||
files.splice(0, 1)
|
||||
}
|
||||
if(this.cropper && file.status=='ready'){
|
||||
const acceptIncludes = ["image/gif", "image/jpeg", "image/png"].includes(file.raw.type)
|
||||
if(!acceptIncludes){
|
||||
this.$notify.warning({
|
||||
title: '上传文件警告',
|
||||
message: '选择的文件非图像类文件'
|
||||
})
|
||||
return false
|
||||
}
|
||||
this.cropperFile = file
|
||||
this.cropperFile.tempCropperFile = URL.createObjectURL(file.raw)
|
||||
this.cropperDialogVisible = true
|
||||
return false
|
||||
}
|
||||
this.file = file
|
||||
if(file.status=='ready'){
|
||||
file.tempFile = URL.createObjectURL(file.raw)
|
||||
}
|
||||
},
|
||||
before(file){
|
||||
const acceptIncludes = this.accept.replace(/\s/g,"").split(",").includes(file.type)
|
||||
if(!acceptIncludes){
|
||||
this.$notify.warning({
|
||||
title: '上传文件警告',
|
||||
message: '选择的文件非图像类文件'
|
||||
})
|
||||
this.clearFiles()
|
||||
return false
|
||||
}
|
||||
const maxSize = file.size / 1024 / 1024 < this.maxSize;
|
||||
if (!maxSize) {
|
||||
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
|
||||
this.clearFiles()
|
||||
return false
|
||||
}
|
||||
},
|
||||
handleExceed(files){
|
||||
const file = files[0]
|
||||
file.uid = genFileId()
|
||||
this.$refs.uploader.handleStart(file)
|
||||
},
|
||||
success(res, file){
|
||||
//释放内存删除blob
|
||||
URL.revokeObjectURL(file.tempFile)
|
||||
delete file.tempFile
|
||||
var os = this.onSuccess(res, file)
|
||||
if(os!=undefined && os==false){
|
||||
this.$nextTick(() => {
|
||||
this.file = null
|
||||
this.value = ""
|
||||
})
|
||||
return false
|
||||
}
|
||||
var response = config.parseData(res)
|
||||
file.url = response.src
|
||||
this.value = file.url
|
||||
},
|
||||
error(err){
|
||||
this.$nextTick(()=>{
|
||||
this.clearFiles()
|
||||
})
|
||||
this.$notify.error({
|
||||
title: '上传文件未成功',
|
||||
message: err
|
||||
})
|
||||
},
|
||||
request(param){
|
||||
var apiObj = config.apiObj;
|
||||
if(this.apiObj){
|
||||
apiObj = this.apiObj;
|
||||
}
|
||||
const data = new FormData();
|
||||
data.append(param.filename, param.file);
|
||||
for (const key in param.data) {
|
||||
data.append(key, param.data[key]);
|
||||
}
|
||||
apiObj.post(data, {
|
||||
onUploadProgress: e => {
|
||||
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
|
||||
param.onProgress({percent: complete})
|
||||
}
|
||||
}).then(res => {
|
||||
var response = config.parseData(res);
|
||||
if(response.code == config.successCode){
|
||||
param.onSuccess(res)
|
||||
}else{
|
||||
param.onError(response.msg || "未知错误")
|
||||
}
|
||||
}).catch(err => {
|
||||
param.onError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form-item.is-error .sc-upload .el-upload--picture-card {border-color: var(--el-color-danger);}
|
||||
.sc-upload .el-upload--picture-card {border-radius: 0;}
|
||||
|
||||
.sc-upload .uploader,.sc-upload:deep(.el-upload) {width: 100%;height: 100%;}
|
||||
|
||||
.sc-upload__img {width: 100%;height: 100%;position: relative;}
|
||||
.sc-upload__img .image {width: 100%;height: 100%;}
|
||||
.sc-upload__img-actions {position: absolute;top:0;right: 0;display: none;}
|
||||
.sc-upload__img-actions span {display: flex;justify-content: center;align-items: center;width: 25px;height:25px;cursor: pointer;color: #fff;}
|
||||
.sc-upload__img-actions span i {font-size: 12px;}
|
||||
.sc-upload__img-actions .del {background: #F56C6C;}
|
||||
.sc-upload__img:hover .sc-upload__img-actions {display: block;}
|
||||
.sc-upload__img-slot {display: flex;justify-content: center;align-items: center;width: 100%;height: 100%;font-size: 12px;background-color: var(--el-fill-color-lighter);}
|
||||
|
||||
.sc-upload__uploading {width: 100%;height: 100%;position: relative;}
|
||||
.sc-upload__progress {position: absolute;width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;background-color: var(--el-overlay-color-lighter);z-index: 1;padding:10px;}
|
||||
.sc-upload__progress .el-progress {width: 100%;}
|
||||
.sc-upload__uploading .image {width: 100%;height: 100%;}
|
||||
|
||||
.sc-upload .file-empty {width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;flex-direction: column;}
|
||||
.sc-upload .file-empty i {font-size: 28px;}
|
||||
.sc-upload .file-empty h4 {font-size: 12px;font-weight: normal;color: #8c939d;margin-top: 8px;}
|
||||
|
||||
.sc-upload.sc-upload-round {border-radius: 50%;overflow: hidden;}
|
||||
.sc-upload.sc-upload-round .el-upload--picture-card {border-radius: 50%;}
|
||||
.sc-upload.sc-upload-round .sc-upload__img-actions {top: auto;left: 0;right: 0;bottom: 0;}
|
||||
.sc-upload.sc-upload-round .sc-upload__img-actions span {width: 100%;}
|
||||
</style>
|
||||
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div class="sc-upload-multiple">
|
||||
<el-upload ref="uploader" list-type="picture-card"
|
||||
:auto-upload="autoUpload"
|
||||
:disabled="disabled"
|
||||
:action="action"
|
||||
:name="name"
|
||||
:data="data"
|
||||
:http-request="request"
|
||||
v-model:file-list="defaultFileList"
|
||||
:show-file-list="showFileList"
|
||||
:accept="accept"
|
||||
:multiple="multiple"
|
||||
:limit="limit"
|
||||
:before-upload="before"
|
||||
:on-success="success"
|
||||
:on-error="error"
|
||||
:on-preview="handlePreview"
|
||||
:on-exceed="handleExceed">
|
||||
<slot>
|
||||
<el-icon><el-icon-plus/></el-icon>
|
||||
</slot>
|
||||
<template #tip>
|
||||
<div v-if="tip" class="el-upload__tip">{{tip}}</div>
|
||||
</template>
|
||||
<template #file="{ file }">
|
||||
<div class="sc-upload-list-item">
|
||||
<el-image class="el-upload-list__item-thumbnail" :src="file.url" fit="cover" :preview-src-list="preview" :initial-index="preview.findIndex(n=>n==file.url)" hide-on-click-modal append-to-body :z-index="9999">
|
||||
<template #placeholder>
|
||||
<div class="sc-upload-multiple-image-slot">
|
||||
Loading...
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div v-if="!disabled && file.status=='success'" class="sc-upload__item-actions">
|
||||
<span class="del" @click="handleRemove(file)"><el-icon><el-icon-delete /></el-icon></span>
|
||||
</div>
|
||||
<div v-if="file.status=='ready' || file.status=='uploading'" class="sc-upload__item-progress">
|
||||
<el-progress :percentage="file.percentage" :text-inside="true" :stroke-width="16"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<span style="display:none!important"><el-input v-model="value"></el-input></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/upload"
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: [String, Array], default: "" },
|
||||
tip: { type: String, default: "" },
|
||||
action: { type: String, default: "" },
|
||||
apiObj: { type: Object, default: () => {} },
|
||||
name: { type: String, default: config.filename },
|
||||
data: { type: Object, default: () => {} },
|
||||
accept: { type: String, default: "image/gif, image/jpeg, image/png" },
|
||||
maxSize: { type: Number, default: config.maxSizeFile },
|
||||
limit: { type: Number, default: 0 },
|
||||
autoUpload: { type: Boolean, default: true },
|
||||
showFileList: { type: Boolean, default: true },
|
||||
multiple: { type: Boolean, default: true },
|
||||
disabled: { type: Boolean, default: false },
|
||||
draggable: { type: Boolean, default: false },
|
||||
onSuccess: { type: Function, default: () => { return true } }
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
value: "",
|
||||
defaultFileList: []
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(val){
|
||||
if(Array.isArray(val)){
|
||||
if (JSON.stringify(val) != JSON.stringify(this.formatArr(this.defaultFileList))) {
|
||||
this.defaultFileList = val
|
||||
this.value = val
|
||||
}
|
||||
}else{
|
||||
if (val != this.toStr(this.defaultFileList)) {
|
||||
this.defaultFileList = this.toArr(val)
|
||||
this.value = val
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultFileList: {
|
||||
handler(val){
|
||||
this.$emit('update:modelValue', Array.isArray(this.modelValue) ? this.formatArr(val) : this.toStr(val))
|
||||
this.value = this.toStr(val)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
preview(){
|
||||
return this.defaultFileList.map(v => v.url)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.defaultFileList = Array.isArray(this.modelValue) ? this.modelValue : this.toArr(this.modelValue)
|
||||
this.value = this.modelValue
|
||||
if(!this.disabled && this.draggable){
|
||||
this.rowDrop()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//默认值转换为数组
|
||||
toArr(str){
|
||||
var _arr = [];
|
||||
var arr = str.split(",");
|
||||
arr.forEach(item => {
|
||||
if(item){
|
||||
var urlArr = item.split('/');
|
||||
var fileName = urlArr[urlArr.length - 1]
|
||||
_arr.push({
|
||||
name: fileName,
|
||||
url: item
|
||||
})
|
||||
}
|
||||
})
|
||||
return _arr;
|
||||
},
|
||||
//数组转换为原始值
|
||||
toStr(arr){
|
||||
return arr.map(v => v.url).join(",")
|
||||
},
|
||||
//格式化数组值
|
||||
formatArr(arr){
|
||||
var _arr = []
|
||||
arr.forEach(item => {
|
||||
if(item){
|
||||
_arr.push({
|
||||
name: item.name,
|
||||
url: item.url
|
||||
})
|
||||
}
|
||||
})
|
||||
return _arr
|
||||
},
|
||||
//拖拽
|
||||
rowDrop(){
|
||||
const _this = this
|
||||
const itemBox = this.$refs.uploader.$el.querySelector('.el-upload-list')
|
||||
Sortable.create(itemBox, {
|
||||
handle: ".el-upload-list__item",
|
||||
animation: 200,
|
||||
ghostClass: "ghost",
|
||||
onEnd({ newIndex, oldIndex }) {
|
||||
const tableData = _this.defaultFileList
|
||||
const currRow = tableData.splice(oldIndex, 1)[0]
|
||||
tableData.splice(newIndex, 0, currRow)
|
||||
}
|
||||
})
|
||||
},
|
||||
before(file){
|
||||
if(!['image/jpeg','image/png','image/gif'].includes(file.type)){
|
||||
this.$message.warning(`选择的文件类型 ${file.type} 非图像类文件`);
|
||||
return false;
|
||||
}
|
||||
const maxSize = file.size / 1024 / 1024 < this.maxSize;
|
||||
if (!maxSize) {
|
||||
this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
success(res, file){
|
||||
var os = this.onSuccess(res, file)
|
||||
if(os!=undefined && os==false){
|
||||
return false
|
||||
}
|
||||
var response = config.parseData(res)
|
||||
file.name = response.fileName
|
||||
file.url = response.src
|
||||
},
|
||||
error(err){
|
||||
this.$notify.error({
|
||||
title: '上传文件未成功',
|
||||
message: err
|
||||
})
|
||||
},
|
||||
beforeRemove(uploadFile){
|
||||
return this.$confirm(`是否移除 ${uploadFile.name} ?`, '提示', {
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
return true
|
||||
}).catch(() => {
|
||||
return false
|
||||
})
|
||||
},
|
||||
handleRemove(file){
|
||||
this.$refs.uploader.handleRemove(file)
|
||||
//this.defaultFileList.splice(this.defaultFileList.findIndex(item => item.uid===file.uid), 1)
|
||||
},
|
||||
handleExceed(){
|
||||
this.$message.warning(`当前设置最多上传 ${this.limit} 个文件,请移除后上传!`)
|
||||
},
|
||||
handlePreview(uploadFile){
|
||||
window.open(uploadFile.url)
|
||||
},
|
||||
request(param){
|
||||
var apiObj = config.apiObj;
|
||||
if(this.apiObj){
|
||||
apiObj = this.apiObj;
|
||||
}
|
||||
const data = new FormData();
|
||||
data.append(param.filename, param.file);
|
||||
for (const key in param.data) {
|
||||
data.append(key, param.data[key]);
|
||||
}
|
||||
apiObj.post(data, {
|
||||
onUploadProgress: e => {
|
||||
const complete = parseInt(((e.loaded / e.total) * 100) | 0, 10)
|
||||
param.onProgress({percent: complete})
|
||||
}
|
||||
}).then(res => {
|
||||
var response = config.parseData(res);
|
||||
if(response.code == config.successCode){
|
||||
param.onSuccess(res)
|
||||
}else{
|
||||
param.onError(response.msg || "未知错误")
|
||||
}
|
||||
}).catch(err => {
|
||||
param.onError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form-item.is-error .sc-upload-multiple:deep(.el-upload--picture-card) {border-color: var(--el-color-danger);}
|
||||
:deep(.el-upload-list__item) {transition:none;border-radius: 0;}
|
||||
.sc-upload-multiple:deep(.el-upload-list__item.el-list-leave-active) {position: static!important;}
|
||||
.sc-upload-multiple:deep(.el-upload--picture-card) {border-radius: 0;}
|
||||
.sc-upload-list-item {width: 100%;height: 100%;position: relative;}
|
||||
.sc-upload-multiple .el-image {display: block;}
|
||||
.sc-upload-multiple .el-image:deep(img) {-webkit-user-drag: none;}
|
||||
.sc-upload-multiple-image-slot {display: flex;justify-content: center;align-items: center;width: 100%;height: 100%;font-size: 12px;}
|
||||
.sc-upload-multiple .el-upload-list__item:hover .sc-upload__item-actions{display: block;}
|
||||
.sc-upload__item-actions {position: absolute;top:0;right: 0;display: none;}
|
||||
.sc-upload__item-actions span {display: flex;justify-content: center;align-items: center;;width: 25px;height:25px;cursor: pointer;color: #fff;}
|
||||
.sc-upload__item-actions span i {font-size: 12px;}
|
||||
.sc-upload__item-actions .del {background: #F56C6C;}
|
||||
.sc-upload__item-progress {position: absolute;width: 100%;height: 100%;top: 0;left: 0;background-color: var(--el-overlay-color-lighter);}
|
||||
</style>
|
||||
@@ -0,0 +1,265 @@
|
||||
<!--
|
||||
* @Descripttion: 表格选择器组件
|
||||
* @version: 1.2
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年6月10日10:04:07
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年2月28日09:39:03
|
||||
-->
|
||||
|
||||
<template>
|
||||
<el-select ref="select" v-model="defaultValue" clearable :multiple="multiple" filterable :placeholder="placeholder" :disabled="disabled" :filter-method="filterMethod" @remove-tag="removeTag" @visible-change="visibleChange" @clear="clear">
|
||||
<template #empty>
|
||||
<div class="sc-table-select__table" :style="{width: tableWidth+'px'}" v-loading="loading">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<el-form :inline="true" :model="formData">
|
||||
<el-form-item>
|
||||
<el-input v-model="formData.name" placeholder="姓名" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="formSubmit">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-header>
|
||||
<el-container>
|
||||
<el-aside width="210px">
|
||||
<div style="height:300px">
|
||||
<el-tree :props="departmentProps" :data="department" node-key="id" @node-click="groupClick"/>
|
||||
</div>
|
||||
</el-aside>
|
||||
<el-main>
|
||||
<el-table ref="table" :data="tableData" :height="245" :highlight-current-row="!multiple" @row-click="click" @select="select" @select-all="selectAll">
|
||||
<el-table-column v-if="multiple" type="selection" width="45"></el-table-column>
|
||||
<el-table-column prop="uid" label="ID" width="80"></el-table-column>
|
||||
<el-table-column prop="username" label="用户名" width="150"></el-table-column>
|
||||
<el-table-column prop="nickname" label="姓名" width="150"></el-table-column>
|
||||
</el-table>
|
||||
<div class="sc-table-select__page">
|
||||
<el-pagination small background layout="prev, pager, next" :total="total" :page-size="pageSize" :page-sizes="[100, 200, 300, 400]" v-model:currentPage="currentPage" @current-change="reload"></el-pagination>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/tableSelect";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: null,
|
||||
params: { type: Object, default: () => {} },
|
||||
placeholder: { type: String, default: "请选择" },
|
||||
multiple: { type: Boolean, default: true },
|
||||
disabled: { type: Boolean, default: false },
|
||||
tableWidth: {type: Number, default: 620},
|
||||
mode: { type: String, default: "popover" },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
apiObj: this.$API.user.list,
|
||||
props: {
|
||||
label: 'nickname',
|
||||
value: 'uid',
|
||||
keyword: "name"
|
||||
},
|
||||
//所需数据选项
|
||||
department: [],
|
||||
departmentProps: {
|
||||
value: "id",
|
||||
label: "title"
|
||||
},
|
||||
loading: false,
|
||||
keyword: null,
|
||||
defaultValue: [],
|
||||
tableData: [],
|
||||
pageSize: config.pageSize,
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
defaultProps: {
|
||||
label: config.props.label,
|
||||
value: config.props.value,
|
||||
page: config.request.page,
|
||||
pageSize: config.request.pageSize,
|
||||
keyword: config.request.keyword
|
||||
},
|
||||
formData: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
watch: {
|
||||
modelValue:{
|
||||
handler(){
|
||||
this.defaultValue = this.modelValue
|
||||
this.autoCurrentLabel()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.defaultProps = Object.assign(this.defaultProps, this.props);
|
||||
this.defaultValue = this.modelValue
|
||||
this.autoCurrentLabel()
|
||||
this.getDepartment();
|
||||
},
|
||||
methods: {
|
||||
async getDepartment(){
|
||||
var res = await this.$API.user.department.list.get();
|
||||
this.department = res.data;
|
||||
},
|
||||
groupClick(data){
|
||||
this.formData.department_id = data.id;
|
||||
this.getData();
|
||||
},
|
||||
//表格显示隐藏回调
|
||||
visibleChange(visible){
|
||||
if(visible){
|
||||
this.currentPage = 1
|
||||
this.keyword = null
|
||||
this.formData = {}
|
||||
this.getData()
|
||||
}else{
|
||||
this.autoCurrentLabel()
|
||||
}
|
||||
},
|
||||
//获取表格数据
|
||||
async getData(){
|
||||
this.loading = true;
|
||||
var reqData = {
|
||||
[this.defaultProps.page]: this.currentPage,
|
||||
[this.defaultProps.pageSize]: this.pageSize,
|
||||
[this.defaultProps.keyword]: this.keyword
|
||||
}
|
||||
Object.assign(reqData, this.params, this.formData)
|
||||
var res = await this.apiObj.get(reqData);
|
||||
var parseData = config.parseData(res)
|
||||
this.tableData = parseData.rows;
|
||||
this.total = parseData.total;
|
||||
this.loading = false;
|
||||
//表格默认赋值
|
||||
this.$nextTick(() => {
|
||||
if(this.multiple){
|
||||
this.defaultValue.forEach(row => {
|
||||
var setrow = this.tableData.filter(item => item[this.defaultProps.value]===row[this.defaultProps.value] )
|
||||
if(setrow.length > 0){
|
||||
this.$refs.table.toggleRowSelection(setrow[0], true);
|
||||
}
|
||||
})
|
||||
}else{
|
||||
var setrow = this.tableData.filter(item => item[this.defaultProps.value]===this.defaultValue[this.defaultProps.value] )
|
||||
this.$refs.table.setCurrentRow(setrow[0]);
|
||||
}
|
||||
this.$refs.table.$el.querySelector('.el-table__body-wrapper').scrollTop = 0
|
||||
})
|
||||
},
|
||||
//插糟表单提交
|
||||
formSubmit(){
|
||||
this.currentPage = 1
|
||||
this.keyword = null
|
||||
this.getData()
|
||||
},
|
||||
//分页刷新表格
|
||||
reload(){
|
||||
this.getData()
|
||||
},
|
||||
//自动模拟options赋值
|
||||
autoCurrentLabel(){
|
||||
this.$nextTick(() => {
|
||||
if(this.multiple){
|
||||
this.$refs.select.selected.forEach(item => {
|
||||
item.currentLabel = item.value[this.defaultProps.label]
|
||||
})
|
||||
}else{
|
||||
this.$refs.select.selectedLabel = this.defaultValue[this.defaultProps.label]
|
||||
}
|
||||
})
|
||||
},
|
||||
//表格勾选事件
|
||||
select(rows, row){
|
||||
var isSelect = rows.length && rows.indexOf(row) !== -1
|
||||
if(isSelect){
|
||||
this.defaultValue.push(row)
|
||||
}else{
|
||||
this.defaultValue.splice(this.defaultValue.findIndex(item => item[this.defaultProps.value] == row[this.defaultProps.value]), 1)
|
||||
}
|
||||
this.autoCurrentLabel()
|
||||
this.$emit('update:modelValue', this.defaultValue);
|
||||
this.$emit('change', this.defaultValue);
|
||||
},
|
||||
//表格全选事件
|
||||
selectAll(rows){
|
||||
var isAllSelect = rows.length > 0
|
||||
if(isAllSelect){
|
||||
rows.forEach(row => {
|
||||
var isHas = this.defaultValue.find(item => item[this.defaultProps.value] == row[this.defaultProps.value])
|
||||
if(!isHas){
|
||||
this.defaultValue.push(row)
|
||||
}
|
||||
})
|
||||
}else{
|
||||
this.tableData.forEach(row => {
|
||||
var isHas = this.defaultValue.find(item => item[this.defaultProps.value] == row[this.defaultProps.value])
|
||||
if(isHas){
|
||||
this.defaultValue.splice(this.defaultValue.findIndex(item => item[this.defaultProps.value] == row[this.defaultProps.value]), 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
this.autoCurrentLabel()
|
||||
this.$emit('update:modelValue', this.defaultValue);
|
||||
this.$emit('change', this.defaultValue);
|
||||
},
|
||||
click(row){
|
||||
if(this.multiple){
|
||||
//处理多选点击行
|
||||
}else{
|
||||
this.defaultValue = row
|
||||
this.$refs.select.blur()
|
||||
this.autoCurrentLabel()
|
||||
this.$emit('update:modelValue', this.defaultValue);
|
||||
this.$emit('change', this.defaultValue);
|
||||
}
|
||||
},
|
||||
//tags删除后回调
|
||||
removeTag(tag){
|
||||
var row = this.findRowByKey(tag[this.defaultProps.value])
|
||||
this.$refs.table.toggleRowSelection(row, false);
|
||||
this.$emit('update:modelValue', this.defaultValue);
|
||||
},
|
||||
//清空后的回调
|
||||
clear(){
|
||||
this.$emit('update:modelValue', this.defaultValue);
|
||||
},
|
||||
// 关键值查询表格数据行
|
||||
findRowByKey (value) {
|
||||
return this.tableData.find(item => item[this.defaultProps.value] === value)
|
||||
},
|
||||
filterMethod(keyword){
|
||||
if(!keyword){
|
||||
this.keyword = null;
|
||||
return false;
|
||||
}
|
||||
this.keyword = keyword;
|
||||
this.getData()
|
||||
},
|
||||
// 触发select隐藏
|
||||
blur(){
|
||||
this.$refs.select.blur();
|
||||
},
|
||||
// 触发select显示
|
||||
focus(){
|
||||
this.$refs.select.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-table-select__table {padding:12px;}
|
||||
.sc-table-select__page {padding-top: 12px;}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<!--
|
||||
* @Descripttion: xgplayer二次封装
|
||||
* @version: 1.1
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年11月29日12:10:06
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年5月30日21:02:50
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-video" ref="scVideo"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Player from 'xgplayer'
|
||||
import HlsPlayer from 'xgplayer-hls'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
src: { type: String, required: true, default: "" },
|
||||
autoplay: { type: Boolean, default: false },
|
||||
controls: { type: Boolean, default: true },
|
||||
loop: { type: Boolean, default: false },
|
||||
isLive: { type: Boolean, default: false },
|
||||
options: { type: Object, default: () => {} }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
player: null
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
src(val){
|
||||
if(this.player.hasStart){
|
||||
this.player.src = val
|
||||
}else{
|
||||
this.player.start(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if(this.isLive){
|
||||
this.initHls()
|
||||
}else{
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(){
|
||||
this.player = new Player({
|
||||
el: this.$refs.scVideo,
|
||||
url: this.src,
|
||||
autoplay: this.autoplay,
|
||||
loop: this.loop,
|
||||
controls: this.controls,
|
||||
fluid: true,
|
||||
lang: 'zh-cn',
|
||||
...this.options
|
||||
})
|
||||
},
|
||||
initHls(){
|
||||
this.player = new HlsPlayer({
|
||||
el: this.$refs.scVideo,
|
||||
url: this.src,
|
||||
autoplay: this.autoplay,
|
||||
loop: this.loop,
|
||||
controls: this.controls,
|
||||
fluid: true,
|
||||
isLive: true,
|
||||
ignores: ['time','progress'],
|
||||
lang: 'zh-cn',
|
||||
...this.options
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-video:deep(.danmu) > * {color: #fff;font-size:20px;font-weight:bold;text-shadow:1px 1px 0 #000,-1px -1px 0 #000,-1px 1px 0 #000,1px -1px 0 #000;}
|
||||
.sc-video:deep(.xgplayer-controls) {background-image: linear-gradient(180deg, transparent, rgba(0,0,0,0.3));}
|
||||
.sc-video:deep(.xgplayer-progress-tip) {border:0;color: #fff;background: rgba(0,0,0,.5);line-height: 25px;padding: 0 10px;border-radius: 25px;}
|
||||
.sc-video:deep(.xgplayer-enter-spinner) {width: 50px;height: 50px;}
|
||||
</style>
|
||||
@@ -0,0 +1,66 @@
|
||||
<!--
|
||||
* @Descripttion: 局部水印组件
|
||||
* @version: 1.1
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年12月18日12:16:16
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年1月5日09:52:59
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-water-mark" ref="scWaterMark">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
text: { type: String, required: true, default: "" },
|
||||
subtext: { type: String, default: "" },
|
||||
color: { type: String, default: "rgba(128,128,128,0.2)" }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.create()
|
||||
},
|
||||
methods: {
|
||||
create(){
|
||||
this.clear()
|
||||
//创建画板
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = 150
|
||||
canvas.height = 150
|
||||
canvas.style.display = 'none'
|
||||
//绘制文字
|
||||
var text = canvas.getContext('2d')
|
||||
text.rotate(-45 * Math.PI / 180)
|
||||
text.translate(-75, 25)
|
||||
text.fillStyle = this.color
|
||||
text.font = "bold 20px SimHei"
|
||||
text.textAlign = "center"
|
||||
text.fillText(this.text, canvas.width / 2, canvas.height / 2)
|
||||
text.font = "14px Microsoft YaHei"
|
||||
text.fillText(this.subtext, canvas.width / 2, canvas.height / 2 + 20)
|
||||
//创建水印容器
|
||||
var watermark = document.createElement('div')
|
||||
watermark.setAttribute('class', 'watermark')
|
||||
const styleStr = `position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;pointer-events:none;background-repeat:repeat;background-image:url('${canvas.toDataURL("image/png")}');`
|
||||
watermark.setAttribute('style', styleStr);
|
||||
this.$refs.scWaterMark.appendChild(watermark)
|
||||
},
|
||||
clear(){
|
||||
var wmDom = this.$refs.scWaterMark.querySelector('.watermark')
|
||||
wmDom && wmDom.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-water-mark {position: relative;display: inherit;width: 100%;height: 100%;}
|
||||
</style>
|
||||
@@ -0,0 +1,154 @@
|
||||
<!--
|
||||
* @Descripttion: 仿钉钉流程设计器
|
||||
* @version: 1.3
|
||||
* @Author: sakuya
|
||||
* @Date: 2021年9月14日08:38:35
|
||||
* @LastEditors: sakuya
|
||||
* @LastEditTime: 2022年5月14日19:43:46
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="sc-workflow-design">
|
||||
<div class="box-scale">
|
||||
<node-wrap v-if="nodeConfig" v-model="nodeConfig"></node-wrap>
|
||||
<div class="end-node">
|
||||
<div class="end-node-circle"></div>
|
||||
<div class="end-node-text">流程结束</div>
|
||||
</div>
|
||||
</div>
|
||||
<use-select v-if="selectVisible" ref="useselect" @closed="selectVisible=false"></use-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import nodeWrap from './nodeWrap'
|
||||
import useSelect from './select'
|
||||
|
||||
export default {
|
||||
provide(){
|
||||
return {
|
||||
select: this.selectHandle
|
||||
}
|
||||
},
|
||||
props: {
|
||||
modelValue: { type: Object, default: () => {} }
|
||||
},
|
||||
components: {
|
||||
nodeWrap,
|
||||
useSelect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nodeConfig: this.modelValue,
|
||||
selectVisible: false
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(val){
|
||||
this.nodeConfig = val
|
||||
},
|
||||
nodeConfig(val){
|
||||
this.$emit("update:modelValue", val)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
selectHandle(type, data){
|
||||
this.selectVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.useselect.open(type, data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sc-workflow-design {width: 100%;}
|
||||
.sc-workflow-design .box-scale {display: inline-block;position: relative;width: 100%;padding: 54.5px 0px;align-items: flex-start;justify-content: center;flex-wrap: wrap;min-width: min-content;}
|
||||
|
||||
.sc-workflow-design {
|
||||
.node-wrap {display: inline-flex;width: 100%;flex-flow: column wrap;justify-content: flex-start;align-items: center;padding: 0px 50px;position: relative;z-index: 1;}
|
||||
.node-wrap-box {display: inline-flex;flex-direction: column;position: relative;width: 220px;min-height: 72px;flex-shrink: 0;background: rgb(255, 255, 255);border-radius: 4px;cursor: pointer;box-shadow: 0 2px 5px 0 rgba(0,0,0,.1);}
|
||||
.node-wrap-box::before {content: "";position: absolute;top: -12px;left: 50%;transform: translateX(-50%);width: 0px;border-style: solid;border-width: 8px 6px 4px;border-color: rgb(202, 202, 202) transparent transparent;background: #f6f8f9;}
|
||||
.node-wrap-box.start-node:before {content: none}
|
||||
.node-wrap-box .title {height:24px;line-height: 24px;color: #fff;padding-left: 16px;padding-right: 30px;border-radius: 4px 4px 0 0;position: relative;display: flex;align-items: center;}
|
||||
.node-wrap-box .title .icon {margin-right: 5px;}
|
||||
.node-wrap-box .title .close {font-size: 15px;position: absolute;top:50%;transform: translateY(-50%);right:10px;display: none;}
|
||||
.node-wrap-box .content {position: relative;padding: 15px;}
|
||||
.node-wrap-box .content .placeholder {color: #999;}
|
||||
.node-wrap-box:hover .close {display: block;}
|
||||
.add-node-btn-box {width: 240px;display: inline-flex;flex-shrink: 0;position: relative;z-index: 1;}
|
||||
.add-node-btn-box:before {content: "";position: absolute;top: 0px;left: 0px;right: 0px;bottom: 0px;z-index: -1;margin: auto;width: 2px;height: 100%;background-color: rgb(202, 202, 202);}
|
||||
.add-node-btn {user-select: none;width: 240px;padding: 20px 0px 32px;display: flex;justify-content: center;flex-shrink: 0;flex-grow: 1;}
|
||||
.add-node-btn span {}
|
||||
.add-branch {justify-content: center;padding: 0px 10px;position: absolute;top: -16px;left: 50%;transform: translateX(-50%);transform-origin: center center;z-index: 1;display: inline-flex;align-items: center;}
|
||||
.branch-wrap {display: inline-flex;width: 100%;}
|
||||
.branch-box-wrap {display: flex;flex-flow: column wrap;align-items: center;min-height: 270px;width: 100%;flex-shrink: 0;}
|
||||
.col-box {display: inline-flex;flex-direction: column;align-items: center;position: relative;background: #f6f8f9;}
|
||||
.branch-box {display: flex;overflow: visible;min-height: 180px;height: auto;border-bottom: 2px solid #ccc;border-top: 2px solid #ccc;position: relative;margin-top: 15px;}
|
||||
.branch-box .col-box::before {content: "";position: absolute;top: 0px;left: 0px;right: 0px;bottom: 0px;z-index: 0;margin: auto;width: 2px;height: 100%;background-color: rgb(202, 202, 202);}
|
||||
.condition-node {display: inline-flex;flex-direction: column;min-height: 220px;}
|
||||
.condition-node-box {padding-top: 30px;padding-right: 50px;padding-left: 50px;justify-content: center;align-items: center;flex-grow: 1;position: relative;display: inline-flex;flex-direction: column;}
|
||||
.condition-node-box::before {content: "";position: absolute;top: 0px;left: 0px;right: 0px;bottom: 0px;margin: auto;width: 2px;height: 100%;background-color: rgb(202, 202, 202);}
|
||||
.auto-judge {position: relative;width: 220px;min-height: 72px;background: rgb(255, 255, 255);border-radius: 4px;padding: 15px 15px;cursor: pointer;box-shadow: 0 2px 5px 0 rgba(0,0,0,.1);}
|
||||
.auto-judge::before {content: "";position: absolute;top: -12px;left: 50%;transform: translateX(-50%);width: 0px;border-style: solid;border-width: 8px 6px 4px;border-color: rgb(202, 202, 202) transparent transparent;background: rgb(245, 245, 247);}
|
||||
.auto-judge .title {line-height: 16px;}
|
||||
.auto-judge .title .node-title {color: #15BC83;}
|
||||
.auto-judge .title .close {font-size: 15px;position: absolute;top:15px;right:15px;color: #999;display: none;}
|
||||
.auto-judge .title .priority-title {position: absolute;top:15px;right:15px;color: #999;}
|
||||
.auto-judge .content {position: relative;padding-top: 15px;}
|
||||
.auto-judge .content .placeholder {color: #999;}
|
||||
.auto-judge:hover {
|
||||
.close {display: block;}
|
||||
.priority-title {display: none;}
|
||||
}
|
||||
.top-left-cover-line, .top-right-cover-line {position: absolute;height: 3px;width: 50%;background-color: #f6f8f9;top: -2px;}
|
||||
.bottom-left-cover-line, .bottom-right-cover-line {position: absolute;height: 3px;width: 50%;background-color: #f6f8f9;bottom: -2px;}
|
||||
.top-left-cover-line {left: -1px;}
|
||||
.top-right-cover-line {right: -1px;}
|
||||
.bottom-left-cover-line {left: -1px;}
|
||||
.bottom-right-cover-line {right: -1px;}
|
||||
.end-node {border-radius: 50%;font-size: 14px;color: rgba(25,31,37,.4);text-align: left;}
|
||||
.end-node-circle {width: 10px;height: 10px;margin: auto;border-radius: 50%;background: #dbdcdc;}
|
||||
.end-node-text {margin-top: 5px;text-align: center;}
|
||||
.auto-judge:hover {
|
||||
.sort-left {display: flex;}
|
||||
.sort-right {display: flex;}
|
||||
}
|
||||
.auto-judge .sort-left {position: absolute;top: 0;bottom: 0;z-index: 1;left: 0;display: none;justify-content: center;align-items: center;flex-direction: column;}
|
||||
.auto-judge .sort-right {position: absolute;top: 0;bottom: 0;z-index: 1;right: 0;display: none;justify-content: center;align-items: center;flex-direction: column;}
|
||||
.auto-judge .sort-left:hover, .auto-judge .sort-right:hover {background: #eee;}
|
||||
.auto-judge:after {pointer-events: none;content: "";position: absolute;top:0;bottom:0;left:0;right:0;z-index: 2;border-radius: 4px;transition: all .1s;}
|
||||
.auto-judge:hover:after {border: 1px solid #3296fa;box-shadow: 0 0 6px 0 rgba(50,150,250,.3);}
|
||||
.node-wrap-box:after {pointer-events: none;content: "";position: absolute;top:0;bottom:0;left:0;right:0;z-index: 2;border-radius: 4px;transition: all .1s;}
|
||||
.node-wrap-box:hover:after {border: 1px solid #3296fa;box-shadow: 0 0 6px 0 rgba(50,150,250,.3);}
|
||||
}
|
||||
|
||||
.tags-list {margin-top: 15px;width: 100%;}
|
||||
.add-node-popover-body {}
|
||||
.add-node-popover-body li {display: inline-block;width: 80px;text-align: center;padding:10px 0;}
|
||||
.add-node-popover-body li i {border: 1px solid var(--el-border-color-light);width:40px;height:40px;border-radius: 50%;text-align: center;line-height: 38px;font-size: 18px;cursor: pointer;}
|
||||
.add-node-popover-body li i:hover {border: 1px solid #3296fa;background: #3296fa;color: #fff!important;}
|
||||
.add-node-popover-body li p {font-size: 12px;margin-top: 5px;}
|
||||
.node-wrap-drawer__title {padding-right:40px;}
|
||||
.node-wrap-drawer__title label {cursor: pointer;}
|
||||
.node-wrap-drawer__title label:hover {border-bottom: 1px dashed #409eff;}
|
||||
.node-wrap-drawer__title .node-wrap-drawer__title-edit {color: #409eff;margin-left: 10px;vertical-align: middle;}
|
||||
|
||||
.dark .sc-workflow-design {
|
||||
.node-wrap-box,.auto-judge {background: #2b2b2b;}
|
||||
.col-box {background: var(--el-bg-color);}
|
||||
.top-left-cover-line,
|
||||
.top-right-cover-line,
|
||||
.bottom-left-cover-line,
|
||||
.bottom-right-cover-line {background-color: var(--el-bg-color);}
|
||||
.node-wrap-box::before,.auto-judge::before {background-color: var(--el-bg-color);}
|
||||
.branch-box .add-branch {background: var(--el-bg-color);}
|
||||
.end-node .end-node-text {color: #d0d0d0;}
|
||||
.auto-judge .sort-left:hover, .auto-judge .sort-right:hover {background: var(--el-bg-color);}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<promoter v-if="nodeConfig.type==0" v-model="nodeConfig"></promoter>
|
||||
|
||||
<approver v-if="nodeConfig.type==1" v-model="nodeConfig"></approver>
|
||||
|
||||
<send v-if="nodeConfig.type==2" v-model="nodeConfig"></send>
|
||||
|
||||
<branch v-if="nodeConfig.type==4" v-model="nodeConfig">
|
||||
<template v-slot="slot">
|
||||
<node-wrap v-if="slot.node" v-model="slot.node.childNode"></node-wrap>
|
||||
</template>
|
||||
</branch>
|
||||
|
||||
<node-wrap v-if="nodeConfig.childNode" v-model="nodeConfig.childNode"></node-wrap>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import approver from './nodes/approver'
|
||||
import promoter from './nodes/promoter'
|
||||
import branch from './nodes/branch'
|
||||
import send from './nodes/send'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: Object, default: () => {} }
|
||||
},
|
||||
components: {
|
||||
approver,
|
||||
promoter,
|
||||
branch,
|
||||
send
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nodeConfig: {},
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(val){
|
||||
this.nodeConfig = val
|
||||
},
|
||||
nodeConfig(val){
|
||||
this.$emit("update:modelValue", val)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.nodeConfig = this.modelValue
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div class="add-node-btn-box">
|
||||
<div class="add-node-btn">
|
||||
<el-popover placement="right-start" :width="270" trigger="click" :hide-after="0" :show-after="0">
|
||||
<template #reference>
|
||||
<el-button type="primary" icon="el-icon-plus" circle></el-button>
|
||||
</template>
|
||||
<div class="add-node-popover-body">
|
||||
<ul>
|
||||
<li>
|
||||
<el-icon style="color: #ff943e;" @click="addType(1)"><el-icon-user-filled /></el-icon>
|
||||
<p>审批节点</p>
|
||||
</li>
|
||||
<li>
|
||||
<el-icon style="color: #3296fa;" @click="addType(2)"><el-icon-promotion /></el-icon>
|
||||
<p>抄送节点</p>
|
||||
</li>
|
||||
<li>
|
||||
<el-icon style="color: #15BC83;" @click="addType(4)"><el-icon-share /></el-icon>
|
||||
<p>条件分支</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: Object, default: () => {} }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
addType(type){
|
||||
var node = {}
|
||||
if (type == 1) {
|
||||
node = {
|
||||
nodeName: "审核人",
|
||||
type: 1, //节点类型
|
||||
setType: 1, //审核人类型
|
||||
nodeUserList: [], //审核人成员
|
||||
nodeRoleList: [], //审核角色
|
||||
examineLevel: 1, //指定主管层级
|
||||
directorLevel: 1, //自定义连续主管审批层级
|
||||
selectMode: 1, //发起人自选类型
|
||||
termAuto: false, //审批期限超时自动审批
|
||||
term: 0, //审批期限
|
||||
termMode: 1, //审批期限超时后执行类型
|
||||
examineMode: 1, //多人审批时审批方式
|
||||
directorMode: 0, //连续主管审批方式
|
||||
childNode: this.modelValue
|
||||
}
|
||||
}else if(type == 2){
|
||||
node = {
|
||||
nodeName: "抄送人",
|
||||
type: 2,
|
||||
userSelectFlag: true,
|
||||
nodeUserList: [],
|
||||
childNode: this.modelValue
|
||||
}
|
||||
|
||||
}else if(type == 4){
|
||||
node = {
|
||||
nodeName: "条件路由",
|
||||
type: 4,
|
||||
conditionNodes: [
|
||||
{
|
||||
nodeName: "条件1",
|
||||
type: 3,
|
||||
priorityLevel: 1,
|
||||
conditionMode: 1,
|
||||
conditionList: []
|
||||
},
|
||||
{
|
||||
nodeName: "条件2",
|
||||
type: 3,
|
||||
priorityLevel: 2,
|
||||
conditionMode: 1,
|
||||
conditionList: []
|
||||
}
|
||||
],
|
||||
childNode: this.modelValue
|
||||
}
|
||||
|
||||
}
|
||||
this.$emit("update:modelValue", node)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="node-wrap">
|
||||
<div class="node-wrap-box" @click="show">
|
||||
<div class="title" style="background: #ff943e;">
|
||||
<el-icon class="icon"><el-icon-user-filled /></el-icon>
|
||||
<span>{{ nodeConfig.nodeName }}</span>
|
||||
<el-icon class="close" @click.stop="delNode()"><el-icon-close /></el-icon>
|
||||
</div>
|
||||
<div class="content">
|
||||
<span v-if="toText(nodeConfig)">{{ toText(nodeConfig) }}</span>
|
||||
<span v-else class="placeholder">请选择</span>
|
||||
</div>
|
||||
</div>
|
||||
<add-node v-model="nodeConfig.childNode"></add-node>
|
||||
<el-drawer title="审批人设置" v-model="drawer" destroy-on-close append-to-body :size="500">
|
||||
<template #header>
|
||||
<div class="node-wrap-drawer__title">
|
||||
<label @click="editTitle" v-if="!isEditTitle">{{form.nodeName}}<el-icon class="node-wrap-drawer__title-edit"><el-icon-edit /></el-icon></label>
|
||||
<el-input v-if="isEditTitle" ref="nodeTitle" v-model="form.nodeName" clearable @blur="saveTitle" @keyup.enter="saveTitle"></el-input>
|
||||
</div>
|
||||
</template>
|
||||
<el-container>
|
||||
<el-main style="padding:0 20px 20px 20px">
|
||||
<el-form label-position="top">
|
||||
|
||||
<el-form-item label="审批人员类型">
|
||||
<el-select v-model="form.setType">
|
||||
<el-option :value="1" label="指定成员"></el-option>
|
||||
<el-option :value="2" label="主管"></el-option>
|
||||
<el-option :value="3" label="角色"></el-option>
|
||||
<el-option :value="4" label="发起人自选"></el-option>
|
||||
<el-option :value="5" label="发起人自己"></el-option>
|
||||
<el-option :value="7" label="连续多级主管"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.setType==1" label="选择成员">
|
||||
<el-button type="primary" icon="el-icon-plus" round @click="selectHandle(1, form.nodeUserList)">选择人员</el-button>
|
||||
<div class="tags-list">
|
||||
<el-tag v-for="(user, index) in form.nodeUserList" :key="user.id" closable @close="delUser(index)">{{user.name}}</el-tag>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.setType==2" label="指定主管">
|
||||
发起人的第 <el-input-number v-model="form.examineLevel" :min="1"/> 级主管
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.setType==3" label="选择角色">
|
||||
<el-button type="primary" icon="el-icon-plus" round @click="selectHandle(2, form.nodeRoleList)">选择角色</el-button>
|
||||
<div class="tags-list">
|
||||
<el-tag v-for="(role, index) in form.nodeRoleList" :key="role.id" type="info" closable @close="delRole(index)">{{role.name}}</el-tag>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.setType==4" label="发起人自选">
|
||||
<el-radio-group v-model="form.selectMode">
|
||||
<el-radio :label="1">自选一个人</el-radio>
|
||||
<el-radio :label="2">自选多个人</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="form.setType==7" label="连续主管审批终点">
|
||||
<el-radio-group v-model="form.directorMode">
|
||||
<el-radio :label="0">直到最上层主管</el-radio>
|
||||
<el-radio :label="1">自定义审批终点</el-radio>
|
||||
</el-radio-group>
|
||||
<p v-if="form.directorMode==1">直到发起人的第 <el-input-number v-model="form.directorLevel" :min="1"/> 级主管</p>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider></el-divider>
|
||||
<el-form-item label="">
|
||||
<el-checkbox v-model="form.termAuto" label="超时自动审批"></el-checkbox>
|
||||
</el-form-item>
|
||||
<template v-if="form.termAuto">
|
||||
<el-form-item label="审批期限(为 0 则不生效)">
|
||||
<el-input-number v-model="form.term" :min="0"/> 小时
|
||||
</el-form-item>
|
||||
<el-form-item label="审批期限超时后执行">
|
||||
<el-radio-group v-model="form.termMode">
|
||||
<el-radio :label="0">自动通过</el-radio>
|
||||
<el-radio :label="1">自动拒绝</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-divider></el-divider>
|
||||
<el-form-item label="多人审批时审批方式">
|
||||
<el-radio-group v-model="form.examineMode">
|
||||
<p style="width: 100%;"><el-radio :label="1">按顺序依次审批</el-radio></p>
|
||||
<p style="width: 100%;"><el-radio :label="2">会签 (可同时审批,每个人必须审批通过)</el-radio></p>
|
||||
<p style="width: 100%;"><el-radio :label="3">或签 (有一人审批通过即可)</el-radio></p>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button @click="drawer=false">取消</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addNode from './addNode'
|
||||
|
||||
export default {
|
||||
inject: ['select'],
|
||||
props: {
|
||||
modelValue: { type: Object, default: () => {} }
|
||||
},
|
||||
components: {
|
||||
addNode
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nodeConfig: {},
|
||||
drawer: false,
|
||||
isEditTitle: false,
|
||||
form: {}
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(){
|
||||
this.nodeConfig = this.modelValue
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.nodeConfig = this.modelValue
|
||||
},
|
||||
methods: {
|
||||
show(){
|
||||
this.form = {}
|
||||
this.form = JSON.parse(JSON.stringify(this.nodeConfig))
|
||||
this.drawer = true
|
||||
},
|
||||
editTitle(){
|
||||
this.isEditTitle = true
|
||||
this.$nextTick(()=>{
|
||||
this.$refs.nodeTitle.focus()
|
||||
})
|
||||
},
|
||||
saveTitle(){
|
||||
this.isEditTitle = false
|
||||
},
|
||||
save(){
|
||||
this.$emit("update:modelValue", this.form)
|
||||
this.drawer = false
|
||||
},
|
||||
delNode(){
|
||||
this.$emit("update:modelValue", this.nodeConfig.childNode)
|
||||
},
|
||||
delUser(index){
|
||||
this.form.nodeUserList.splice(index, 1)
|
||||
},
|
||||
delRole(index){
|
||||
this.form.nodeRoleList.splice(index, 1)
|
||||
},
|
||||
selectHandle(type, data){
|
||||
this.select(type, data)
|
||||
},
|
||||
toText(nodeConfig){
|
||||
if(nodeConfig.setType == 1){
|
||||
if (nodeConfig.nodeUserList && nodeConfig.nodeUserList.length>0) {
|
||||
const users = nodeConfig.nodeUserList.map(item=>item.name).join("、")
|
||||
return users
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}else if (nodeConfig.setType == 2) {
|
||||
return nodeConfig.examineLevel == 1 ? '直接主管' : `发起人的第${nodeConfig.examineLevel}级主管`
|
||||
}else if (nodeConfig.setType == 3) {
|
||||
if (nodeConfig.nodeRoleList && nodeConfig.nodeRoleList.length>0) {
|
||||
const roles = nodeConfig.nodeRoleList.map(item=>item.name).join("、")
|
||||
return '角色-' + roles
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}else if (nodeConfig.setType == 4) {
|
||||
return "发起人自选"
|
||||
}else if (nodeConfig.setType == 5) {
|
||||
return "发起人自己"
|
||||
}else if (nodeConfig.setType == 7) {
|
||||
return "连续多级主管"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<div class="branch-wrap">
|
||||
<div class="branch-box-wrap">
|
||||
<div class="branch-box">
|
||||
<el-button class="add-branch" type="success" plain round @click="addTerm">添加条件</el-button>
|
||||
<div class="col-box" v-for="(item,index) in nodeConfig.conditionNodes" :key="index">
|
||||
<div class="condition-node">
|
||||
<div class="condition-node-box">
|
||||
<div class="auto-judge" @click="show(index)">
|
||||
<div class="sort-left" v-if="index!=0" @click.stop="arrTransfer(index,-1)">
|
||||
<el-icon><el-icon-arrow-left /></el-icon>
|
||||
</div>
|
||||
<div class="title">
|
||||
<span class="node-title">{{ item.nodeName }}</span>
|
||||
<span class="priority-title">优先级{{item.priorityLevel}}</span>
|
||||
<el-icon class="close" @click.stop="delTerm(index)"><el-icon-close /></el-icon>
|
||||
</div>
|
||||
<div class="content">
|
||||
<span v-if="toText(nodeConfig, index)">{{ toText(nodeConfig, index) }}</span>
|
||||
<span v-else class="placeholder">请设置条件</span>
|
||||
</div>
|
||||
<div class="sort-right" v-if="index!=nodeConfig.conditionNodes.length-1" @click.stop="arrTransfer(index)">
|
||||
<el-icon><el-icon-arrow-right /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<add-node v-model="item.childNode"></add-node>
|
||||
</div>
|
||||
</div>
|
||||
<slot v-if="item.childNode" :node="item"></slot>
|
||||
<div class="top-left-cover-line" v-if="index==0"></div>
|
||||
<div class="bottom-left-cover-line" v-if="index==0"></div>
|
||||
<div class="top-right-cover-line" v-if="index==nodeConfig.conditionNodes.length-1"></div>
|
||||
<div class="bottom-right-cover-line" v-if="index==nodeConfig.conditionNodes.length-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
<add-node v-model="nodeConfig.childNode"></add-node>
|
||||
</div>
|
||||
<el-drawer title="条件设置" v-model="drawer" destroy-on-close append-to-body :size="600">
|
||||
<template #header>
|
||||
<div class="node-wrap-drawer__title">
|
||||
<label @click="editTitle" v-if="!isEditTitle">{{form.nodeName}}<el-icon class="node-wrap-drawer__title-edit"><el-icon-edit /></el-icon></label>
|
||||
<el-input v-if="isEditTitle" ref="nodeTitle" v-model="form.nodeName" clearable @blur="saveTitle" @keyup.enter="saveTitle"></el-input>
|
||||
</div>
|
||||
</template>
|
||||
<el-container>
|
||||
<el-main style="padding:0 20px 20px 20px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="条件关系">
|
||||
<el-radio-group v-model="form.conditionMode">
|
||||
<el-radio :label="1">且</el-radio>
|
||||
<el-radio :label="2">或</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-divider></el-divider>
|
||||
<el-form-item>
|
||||
<el-table :data="form.conditionList">
|
||||
<el-table-column prop="label" label="描述">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.label" placeholder="描述"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="field" label="条件字段" width="130">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.field" placeholder="条件字段"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operator" label="运算符" width="130">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.operator" placeholder="Select">
|
||||
<el-option label="等于" value="="></el-option>
|
||||
<el-option label="不等于" value="!="></el-option>
|
||||
<el-option label="大于" value=">"></el-option>
|
||||
<el-option label="大于等于" value=">="></el-option>
|
||||
<el-option label="小于" value="<"></el-option>
|
||||
<el-option label="小于等于" value="<="></el-option>
|
||||
<el-option label="包含" value="include"></el-option>
|
||||
<el-option label="不包含" value="notinclude"></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="value" label="值" width="100">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.value" placeholder="值"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="value" label="移除" width="55">
|
||||
<template #default="scope">
|
||||
<el-link type="danger" :underline="false" @click="deleteConditionList(scope.$index)">移除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
<p><el-button type="primary" icon="el-icon-plus" round @click="addConditionList">增加条件</el-button></p>
|
||||
</el-form>
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button @click="drawer=false">取消</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addNode from './addNode'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: Object, default: () => {} }
|
||||
},
|
||||
components: {
|
||||
addNode
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nodeConfig: {},
|
||||
drawer: false,
|
||||
isEditTitle: false,
|
||||
index: 0,
|
||||
form: {}
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(){
|
||||
this.nodeConfig = this.modelValue
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.nodeConfig = this.modelValue
|
||||
},
|
||||
methods: {
|
||||
show(index){
|
||||
this.index = index
|
||||
this.form = {}
|
||||
this.form = JSON.parse(JSON.stringify(this.nodeConfig.conditionNodes[index]))
|
||||
this.drawer = true
|
||||
},
|
||||
editTitle(){
|
||||
this.isEditTitle = true
|
||||
this.$nextTick(()=>{
|
||||
this.$refs.nodeTitle.focus()
|
||||
})
|
||||
},
|
||||
saveTitle(){
|
||||
this.isEditTitle = false
|
||||
},
|
||||
save(){
|
||||
this.nodeConfig.conditionNodes[this.index] = this.form
|
||||
this.$emit("update:modelValue", this.nodeConfig)
|
||||
this.drawer = false
|
||||
},
|
||||
addTerm(){
|
||||
let len = this.nodeConfig.conditionNodes.length + 1
|
||||
this.nodeConfig.conditionNodes.push({
|
||||
nodeName: "条件" + len,
|
||||
type: 3,
|
||||
priorityLevel: len,
|
||||
conditionMode: 1,
|
||||
conditionList: []
|
||||
})
|
||||
},
|
||||
delTerm(index){
|
||||
this.nodeConfig.conditionNodes.splice(index, 1)
|
||||
if (this.nodeConfig.conditionNodes.length == 1) {
|
||||
if (this.nodeConfig.childNode) {
|
||||
if (this.nodeConfig.conditionNodes[0].childNode) {
|
||||
this.reData(this.nodeConfig.conditionNodes[0].childNode, this.nodeConfig.childNode)
|
||||
}else{
|
||||
this.nodeConfig.conditionNodes[0].childNode = this.nodeConfig.childNode
|
||||
}
|
||||
}
|
||||
this.$emit("update:modelValue", this.nodeConfig.conditionNodes[0].childNode);
|
||||
}
|
||||
},
|
||||
reData(data, addData) {
|
||||
if (!data.childNode) {
|
||||
data.childNode = addData
|
||||
} else {
|
||||
this.reData(data.childNode, addData)
|
||||
}
|
||||
},
|
||||
arrTransfer(index, type = 1){
|
||||
this.nodeConfig.conditionNodes[index] = this.nodeConfig.conditionNodes.splice(index + type, 1, this.nodeConfig.conditionNodes[index])[0]
|
||||
this.nodeConfig.conditionNodes.map((item, index) => {
|
||||
item.priorityLevel = index + 1
|
||||
})
|
||||
this.$emit("update:modelValue", this.nodeConfig)
|
||||
},
|
||||
addConditionList(){
|
||||
this.form.conditionList.push({
|
||||
label: '',
|
||||
field: '',
|
||||
operator: '=',
|
||||
value: ''
|
||||
})
|
||||
},
|
||||
deleteConditionList(index){
|
||||
this.form.conditionList.splice(index, 1)
|
||||
},
|
||||
toText(nodeConfig, index){
|
||||
var { conditionList } = nodeConfig.conditionNodes[index]
|
||||
if (conditionList && conditionList.length == 1) {
|
||||
const text = conditionList.map(item => `${item.label}${item.operator}${item.value}`).join(" 和 ")
|
||||
return text
|
||||
}else if(conditionList && conditionList.length > 1){
|
||||
const conditionModeText = nodeConfig.conditionNodes[index].conditionMode==1?'且行':'或行'
|
||||
return conditionList.length + "个条件," + conditionModeText
|
||||
}else{
|
||||
if(index == nodeConfig.conditionNodes.length - 1){
|
||||
return "其他条件进入此流程"
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="node-wrap">
|
||||
<div class="node-wrap-box start-node" @click="show">
|
||||
<div class="title" style="background: #576a95;">
|
||||
<el-icon class="icon"><el-icon-user-filled /></el-icon>
|
||||
<span>{{ nodeConfig.nodeName }}</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<span>{{ toText(nodeConfig) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<add-node v-model="nodeConfig.childNode"></add-node>
|
||||
<el-drawer title="发起人" v-model="drawer" destroy-on-close append-to-body :size="500">
|
||||
<template #header>
|
||||
<div class="node-wrap-drawer__title">
|
||||
<label @click="editTitle" v-if="!isEditTitle">{{form.nodeName}}<el-icon class="node-wrap-drawer__title-edit"><el-icon-edit /></el-icon></label>
|
||||
<el-input v-if="isEditTitle" ref="nodeTitle" v-model="form.nodeName" clearable @blur="saveTitle" @keyup.enter="saveTitle"></el-input>
|
||||
</div>
|
||||
</template>
|
||||
<el-container>
|
||||
<el-main style="padding:0 20px 20px 20px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="谁可以发起此审批">
|
||||
<el-button type="primary" icon="el-icon-plus" round @click="selectHandle(2, form.nodeRoleList)">选择角色</el-button>
|
||||
<div class="tags-list">
|
||||
<el-tag v-for="(role, index) in form.nodeRoleList" :key="role.id" type="info" closable @close="delRole(index)">{{role.name}}</el-tag>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-alert v-if="form.nodeRoleList.length==0" title="不指定则默认所有人都可发起此审批" type="info" :closable="false"/>
|
||||
</el-form>
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button @click="drawer=false">取消</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addNode from './addNode'
|
||||
|
||||
export default {
|
||||
inject: ['select'],
|
||||
props: {
|
||||
modelValue: { type: Object, default: () => {} }
|
||||
},
|
||||
components: {
|
||||
addNode
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nodeConfig: {},
|
||||
drawer: false,
|
||||
isEditTitle: false,
|
||||
form: {}
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(){
|
||||
this.nodeConfig = this.modelValue
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.nodeConfig = this.modelValue
|
||||
},
|
||||
methods: {
|
||||
show(){
|
||||
this.form = {}
|
||||
this.form = JSON.parse(JSON.stringify(this.nodeConfig))
|
||||
this.isEditTitle = false
|
||||
this.drawer = true
|
||||
},
|
||||
editTitle(){
|
||||
this.isEditTitle = true
|
||||
this.$nextTick(()=>{
|
||||
this.$refs.nodeTitle.focus()
|
||||
})
|
||||
},
|
||||
saveTitle(){
|
||||
this.isEditTitle = false
|
||||
},
|
||||
selectHandle(type, data){
|
||||
this.select(type, data)
|
||||
},
|
||||
delRole(index){
|
||||
this.form.nodeRoleList.splice(index, 1)
|
||||
},
|
||||
save(){
|
||||
this.$emit("update:modelValue", this.form)
|
||||
this.drawer = false
|
||||
},
|
||||
toText(nodeConfig){
|
||||
if(nodeConfig.nodeRoleList && nodeConfig.nodeRoleList.length > 0){
|
||||
return nodeConfig.nodeRoleList.map(item=>item.name).join("、")
|
||||
}else{
|
||||
return "所有人"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="node-wrap">
|
||||
<div class="node-wrap-box" @click="show">
|
||||
<div class="title" style="background: #3296fa;">
|
||||
<el-icon class="icon"><el-icon-promotion /></el-icon>
|
||||
<span>{{ nodeConfig.nodeName }}</span>
|
||||
<el-icon class="close" @click.stop="delNode()"><el-icon-close /></el-icon>
|
||||
</div>
|
||||
<div class="content">
|
||||
<span v-if="toText(nodeConfig)">{{ toText(nodeConfig) }}</span>
|
||||
<span v-else class="placeholder">请选择人员</span>
|
||||
</div>
|
||||
</div>
|
||||
<add-node v-model="nodeConfig.childNode"></add-node>
|
||||
<el-drawer title="抄送人设置" v-model="drawer" destroy-on-close append-to-body :size="500">
|
||||
<template #header>
|
||||
<div class="node-wrap-drawer__title">
|
||||
<label @click="editTitle" v-if="!isEditTitle">{{form.nodeName}}<el-icon class="node-wrap-drawer__title-edit"><el-icon-edit /></el-icon></label>
|
||||
<el-input v-if="isEditTitle" ref="nodeTitle" v-model="form.nodeName" clearable @blur="saveTitle" @keyup.enter="saveTitle"></el-input>
|
||||
</div>
|
||||
</template>
|
||||
<el-container>
|
||||
<el-main style="padding:0 20px 20px 20px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="选择要抄送的人员">
|
||||
<el-button type="primary" icon="el-icon-plus" round @click="selectHandle(1, form.nodeUserList)">选择人员</el-button>
|
||||
<div class="tags-list">
|
||||
<el-tag v-for="(user, index) in form.nodeUserList" :key="user.id" closable @close="delUser(index)">{{user.name}}</el-tag>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="">
|
||||
<el-checkbox v-model="form.userSelectFlag" label="允许发起人自选抄送人"></el-checkbox>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button @click="drawer=false">取消</el-button>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addNode from './addNode'
|
||||
|
||||
export default {
|
||||
inject: ['select'],
|
||||
props: {
|
||||
modelValue: { type: Object, default: () => {} }
|
||||
},
|
||||
components: {
|
||||
addNode
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nodeConfig: {},
|
||||
drawer: false,
|
||||
isEditTitle: false,
|
||||
form: {}
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
modelValue(){
|
||||
this.nodeConfig = this.modelValue
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.nodeConfig = this.modelValue
|
||||
},
|
||||
methods: {
|
||||
show(){
|
||||
this.form = {}
|
||||
this.form = JSON.parse(JSON.stringify(this.nodeConfig))
|
||||
this.drawer = true
|
||||
},
|
||||
editTitle(){
|
||||
this.isEditTitle = true
|
||||
this.$nextTick(()=>{
|
||||
this.$refs.nodeTitle.focus()
|
||||
})
|
||||
},
|
||||
saveTitle(){
|
||||
this.isEditTitle = false
|
||||
},
|
||||
save(){
|
||||
this.$emit("update:modelValue", this.form)
|
||||
this.drawer = false
|
||||
},
|
||||
delNode(){
|
||||
this.$emit("update:modelValue", this.nodeConfig.childNode)
|
||||
},
|
||||
delUser(index){
|
||||
this.form.nodeUserList.splice(index, 1)
|
||||
},
|
||||
selectHandle(type, data){
|
||||
this.select(type, data)
|
||||
},
|
||||
toText(nodeConfig){
|
||||
if (nodeConfig.nodeUserList && nodeConfig.nodeUserList.length>0) {
|
||||
const users = nodeConfig.nodeUserList.map(item=>item.name).join("、")
|
||||
return users
|
||||
}else{
|
||||
if(nodeConfig.userSelectFlag){
|
||||
return "发起人自选"
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" :title="titleMap[type-1]" :width="type==1?680:460" destroy-on-close append-to-body @closed="$emit('closed')">
|
||||
|
||||
<template v-if="type==1">
|
||||
<div class="sc-user-select">
|
||||
<div class="sc-user-select__left">
|
||||
<div class="sc-user-select__search">
|
||||
<el-input v-model="keyword" prefix-icon="el-icon-search" placeholder="搜索成员">
|
||||
<template #append>
|
||||
<el-button icon="el-icon-search" @click="search"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="sc-user-select__select">
|
||||
<div class="sc-user-select__tree" v-loading="showGrouploading">
|
||||
<el-scrollbar>
|
||||
<el-tree class="menu" ref="groupTree" :data="group" :node-key="groupProps.key" :props="groupProps" highlight-current :expand-on-click-node="false" :current-node-key="groupId" @node-click="groupClick"/>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="sc-user-select__user" v-loading="showUserloading">
|
||||
<div class="sc-user-select__user__list">
|
||||
<el-scrollbar ref="userScrollbar">
|
||||
<el-tree class="menu" ref="userTree" :data="user" :node-key="userProps.key" :props="userProps" :default-checked-keys="selectedIds" show-checkbox check-on-click-node @check-change="userClick"></el-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<footer>
|
||||
<el-pagination background layout="prev,next" small :total="total" :page-size="pageSize" v-model:currentPage="currentPage" @current-change="paginationChange"></el-pagination>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sc-user-select__toicon"><el-icon><el-icon-arrow-right /></el-icon></div>
|
||||
<div class="sc-user-select__selected">
|
||||
<header>已选 ({{selected.length}})</header>
|
||||
<ul>
|
||||
<el-scrollbar>
|
||||
<li v-for="(item, index) in selected" :key="item.id">
|
||||
<span class="name">
|
||||
<el-avatar>{{item.name.substring(0,1)}}</el-avatar>
|
||||
<label>{{item.name}}</label>
|
||||
</span>
|
||||
<span class="delete">
|
||||
<el-button type="danger" icon="el-icon-delete" circle @click="deleteSelected(index)"></el-button>
|
||||
</span>
|
||||
</li>
|
||||
</el-scrollbar>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="type==2">
|
||||
<div class="sc-user-select sc-user-select-role">
|
||||
<div class="sc-user-select__left">
|
||||
<div class="sc-user-select__select">
|
||||
<div class="sc-user-select__tree" v-loading="showGrouploading">
|
||||
<el-scrollbar>
|
||||
<el-tree class="menu" ref="groupTree" :data="role" :node-key="roleProps.key" :props="roleProps" show-checkbox check-strictly check-on-click-node :expand-on-click-node="false" :default-checked-keys="selectedIds" @check-change="roleClick"/>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sc-user-select__toicon"><el-icon><el-icon-arrow-right /></el-icon></div>
|
||||
<div class="sc-user-select__selected">
|
||||
<header>已选 ({{selected.length}})</header>
|
||||
<ul>
|
||||
<el-scrollbar>
|
||||
<li v-for="(item, index) in selected" :key="item.id">
|
||||
<span class="name">
|
||||
<label>{{item.name}}</label>
|
||||
</span>
|
||||
<span class="delete">
|
||||
<el-button type="danger" icon="el-icon-delete" circle @click="deleteSelected(index)"></el-button>
|
||||
</span>
|
||||
</li>
|
||||
</el-scrollbar>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="save">确 认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/workflow";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
groupProps: config.group.props,
|
||||
userProps: config.user.props,
|
||||
roleProps: config.role.props,
|
||||
|
||||
titleMap: ['人员选择', '角色选择'],
|
||||
dialogVisible: false,
|
||||
showGrouploading: false,
|
||||
showUserloading: false,
|
||||
keyword: '',
|
||||
groupId: '',
|
||||
pageSize: config.user.pageSize,
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
group: [],
|
||||
user: [],
|
||||
role: [],
|
||||
type: 1,
|
||||
selected: [],
|
||||
value: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedIds(){
|
||||
return this.selected.map(t => t.id)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
//打开赋值
|
||||
open(type, data){
|
||||
this.type = type
|
||||
this.value = data||[]
|
||||
this.selected = JSON.parse(JSON.stringify(data||[]))
|
||||
this.dialogVisible = true
|
||||
|
||||
if(this.type==1){
|
||||
this.getGroup()
|
||||
this.getUser()
|
||||
}else if(this.type==2){
|
||||
this.getRole()
|
||||
}
|
||||
|
||||
},
|
||||
//获取组织
|
||||
async getGroup(){
|
||||
this.showGrouploading = true;
|
||||
var res = await config.group.apiObj.get();
|
||||
this.showGrouploading = false;
|
||||
var allNode = {[config.group.props.key]: '', [config.group.props.label]: '所有'}
|
||||
res.data.unshift(allNode);
|
||||
this.group = config.group.parseData(res).rows
|
||||
},
|
||||
//获取用户
|
||||
async getUser(){
|
||||
this.showUserloading = true;
|
||||
var params = {
|
||||
[config.user.request.keyword]: this.keyword || null,
|
||||
[config.user.request.groupId]: this.groupId || null,
|
||||
[config.user.request.page]: this.currentPage,
|
||||
[config.user.request.pageSize]: this.pageSize
|
||||
}
|
||||
var res = await config.user.apiObj.get(params);
|
||||
this.showUserloading = false;
|
||||
this.user = config.user.parseData(res).rows;
|
||||
this.total = config.user.parseData(res).total || 0;
|
||||
this.$refs.userScrollbar.setScrollTop(0)
|
||||
},
|
||||
//获取角色
|
||||
async getRole(){
|
||||
this.showGrouploading = true;
|
||||
var res = await config.role.apiObj.get();
|
||||
this.showGrouploading = false;
|
||||
this.role = config.role.parseData(res).rows
|
||||
},
|
||||
//组织点击
|
||||
groupClick(data){
|
||||
this.keyword = ''
|
||||
this.currentPage = 1
|
||||
this.groupId = data[config.group.props.key]
|
||||
this.getUser()
|
||||
},
|
||||
//用户点击
|
||||
userClick(data, checked){
|
||||
if(checked){
|
||||
this.selected.push({
|
||||
id: data[config.user.props.key],
|
||||
name: data[config.user.props.label]
|
||||
})
|
||||
}else{
|
||||
this.selected = this.selected.filter(item => item.id != data[config.user.props.key])
|
||||
}
|
||||
},
|
||||
//用户分页点击
|
||||
paginationChange(){
|
||||
this.getUser()
|
||||
},
|
||||
//用户搜索
|
||||
search(){
|
||||
this.groupId = ''
|
||||
this.$refs.groupTree.setCurrentKey(this.groupId)
|
||||
this.currentPage = 1
|
||||
this.getUser()
|
||||
},
|
||||
//删除已选
|
||||
deleteSelected(index){
|
||||
this.selected.splice(index,1);
|
||||
if(this.type==1){
|
||||
this.$refs.userTree.setCheckedKeys(this.selectedIds)
|
||||
}else if(this.type==2){
|
||||
this.$refs.groupTree.setCheckedKeys(this.selectedIds)
|
||||
}
|
||||
},
|
||||
//角色点击
|
||||
roleClick(data, checked){
|
||||
if(checked){
|
||||
this.selected.push({
|
||||
id: data[config.role.props.key],
|
||||
name: data[config.role.props.label]
|
||||
})
|
||||
}else{
|
||||
this.selected = this.selected.filter(item => item.id != data[config.role.props.key])
|
||||
}
|
||||
},
|
||||
//提交保存
|
||||
save(){
|
||||
this.value.splice(0,this.value.length);
|
||||
this.selected.map(item => {
|
||||
this.value.push(item)
|
||||
})
|
||||
this.dialogVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sc-user-select {display: flex;}
|
||||
.sc-user-select__left {width: 400px;}
|
||||
.sc-user-select__right {flex: 1;}
|
||||
|
||||
.sc-user-select__search {padding-bottom:10px;}
|
||||
|
||||
.sc-user-select__select {display: flex;border: 1px solid var(--el-border-color-light);background: var(--el-color-white);}
|
||||
.sc-user-select__tree {width: 200px;height:300px;border-right: 1px solid var(--el-border-color-light);}
|
||||
.sc-user-select__user {width: 200px;height:300px;display: flex;flex-direction: column;}
|
||||
.sc-user-select__user__list {flex: 1;overflow: auto;}
|
||||
.sc-user-select__user footer {height:36px;padding-top:5px;border-top: 1px solid var(--el-border-color-light);}
|
||||
|
||||
.sc-user-select__toicon {display: flex;justify-content: center;align-items: center;margin:0 10px;}
|
||||
.sc-user-select__toicon i {display: flex;justify-content: center;align-items: center;background: #ccc;width: 20px;height: 20px;text-align: center;line-height: 20px;border-radius:50%;color: #fff;}
|
||||
|
||||
.sc-user-select__selected {height:345px;width: 200px;border: 1px solid var(--el-border-color-light);background: var(--el-color-white);}
|
||||
.sc-user-select__selected header {height:43px;line-height: 43px;border-bottom: 1px solid var(--el-border-color-light);padding:0 15px;font-size: 12px;}
|
||||
.sc-user-select__selected ul {height:300px;overflow: auto;}
|
||||
.sc-user-select__selected li {display: flex;align-items: center;justify-content: space-between;padding:5px 5px 5px 15px;height:38px;}
|
||||
.sc-user-select__selected li .name {display: flex;align-items: center;}
|
||||
.sc-user-select__selected li .name .el-avatar {background: #409eff;margin-right: 10px;}
|
||||
.sc-user-select__selected li .name label {}
|
||||
.sc-user-select__selected li .delete {display: none;}
|
||||
.sc-user-select__selected li:hover {background: var(--el-color-primary-light-9);}
|
||||
.sc-user-select__selected li:hover .delete {display: inline-block;}
|
||||
|
||||
.sc-user-select-role .sc-user-select__left {width: 200px;}
|
||||
.sc-user-select-role .sc-user-select__tree {border: none;height: 343px;}
|
||||
.sc-user-select-role .sc-user-select__selected {}
|
||||
|
||||
[data-theme='dark'] .sc-user-select__selected li:hover {background: rgba(0, 0, 0, 0.2);}
|
||||
[data-theme='dark'] .sc-user-select__toicon i {background: #383838;}
|
||||
</style>
|
||||
@@ -0,0 +1,69 @@
|
||||
import API from "@/api";
|
||||
|
||||
//文件选择器配置
|
||||
|
||||
export default {
|
||||
apiObj: API.common.upload,
|
||||
menuApiObj: API.common.file.menu,
|
||||
listApiObj: API.common.file.list,
|
||||
successCode: 1,
|
||||
maxSize: 30,
|
||||
max: 99,
|
||||
uploadParseData: function (res) {
|
||||
return {
|
||||
id: res.data.id,
|
||||
fileName: res.data.fileName,
|
||||
url: res.data.src
|
||||
}
|
||||
},
|
||||
listParseData: function (res) {
|
||||
return {
|
||||
rows: res.data.data,
|
||||
total: res.data.total,
|
||||
msg: res.message,
|
||||
code: res.code
|
||||
}
|
||||
},
|
||||
request: {
|
||||
page: 'page',
|
||||
pageSize: 'pageSize',
|
||||
keyword: 'keyword',
|
||||
menuKey: 'groupId'
|
||||
},
|
||||
menuProps: {
|
||||
key: 'id',
|
||||
label: 'title',
|
||||
children: 'children'
|
||||
},
|
||||
fileProps: {
|
||||
key: 'id',
|
||||
fileName: 'fileName',
|
||||
url: 'url'
|
||||
},
|
||||
files: {
|
||||
doc: {
|
||||
icon: 'sc-icon-file-word-2-fill',
|
||||
color: '#409eff'
|
||||
},
|
||||
docx: {
|
||||
icon: 'sc-icon-file-word-2-fill',
|
||||
color: '#409eff'
|
||||
},
|
||||
xls: {
|
||||
icon: 'sc-icon-file-excel-2-fill',
|
||||
color: '#67C23A'
|
||||
},
|
||||
xlsx: {
|
||||
icon: 'sc-icon-file-excel-2-fill',
|
||||
color: '#67C23A'
|
||||
},
|
||||
ppt: {
|
||||
icon: 'sc-icon-file-ppt-2-fill',
|
||||
color: '#F56C6C'
|
||||
},
|
||||
pptx: {
|
||||
icon: 'sc-icon-file-ppt-2-fill',
|
||||
color: '#F56C6C'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
export default {
|
||||
//运算符
|
||||
operator: [
|
||||
{
|
||||
label: '等于',
|
||||
value: '=',
|
||||
},
|
||||
{
|
||||
label: '不等于',
|
||||
value: '!=',
|
||||
},
|
||||
{
|
||||
label: '大于',
|
||||
value: '>',
|
||||
},
|
||||
{
|
||||
label: '大于等于',
|
||||
value: '>=',
|
||||
},
|
||||
{
|
||||
label: '小于',
|
||||
value: '<',
|
||||
},
|
||||
{
|
||||
label: '小于等于',
|
||||
value: '<=',
|
||||
},
|
||||
{
|
||||
label: '包含',
|
||||
value: 'include',
|
||||
},
|
||||
{
|
||||
label: '不包含',
|
||||
value: 'notinclude',
|
||||
}
|
||||
],
|
||||
//过滤结果运算符的分隔符
|
||||
separator: '|',
|
||||
//获取我的常用
|
||||
getMy: function (name) {
|
||||
return new Promise((resolve) => {
|
||||
console.log(`这里可以根据${name}参数请求接口`)
|
||||
var list = []
|
||||
setTimeout(()=>{
|
||||
resolve(list)
|
||||
},500)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 常用保存处理 返回resolve后继续操作
|
||||
* @name scFilterBar组件的props->filterName
|
||||
* @obj 过滤项整理好的对象
|
||||
*/
|
||||
saveMy: function (name, obj) {
|
||||
return new Promise((resolve) => {
|
||||
console.log(name, obj)
|
||||
setTimeout(()=>{
|
||||
resolve(true)
|
||||
},500)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 常用删除处理 返回resolve后继续操作
|
||||
* @name scFilterBar组件的props->filterName
|
||||
*/
|
||||
delMy: function (name) {
|
||||
return new Promise((resolve) => {
|
||||
console.log(name)
|
||||
setTimeout(()=>{
|
||||
resolve(true)
|
||||
},500)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
//图标选择器配置
|
||||
export default {
|
||||
icons: [{
|
||||
name: '默认',
|
||||
icons: [
|
||||
"el-icon-add-location",
|
||||
"el-icon-aim",
|
||||
"el-icon-alarm-clock",
|
||||
"el-icon-apple",
|
||||
"el-icon-arrow-down",
|
||||
"el-icon-arrow-down-bold",
|
||||
"el-icon-arrow-left",
|
||||
"el-icon-arrow-left-bold",
|
||||
"el-icon-arrow-right",
|
||||
"el-icon-arrow-right-bold",
|
||||
"el-icon-arrow-up",
|
||||
"el-icon-arrow-up-bold",
|
||||
"el-icon-avatar",
|
||||
"el-icon-back",
|
||||
"el-icon-baseball",
|
||||
"el-icon-basketball",
|
||||
"el-icon-bell",
|
||||
"el-icon-bell-filled",
|
||||
"el-icon-bicycle",
|
||||
"el-icon-bottom",
|
||||
"el-icon-bottom-left",
|
||||
"el-icon-bottom-right",
|
||||
"el-icon-bowl",
|
||||
"el-icon-box",
|
||||
"el-icon-briefcase",
|
||||
"el-icon-brush",
|
||||
"el-icon-brush-filled",
|
||||
"el-icon-burger",
|
||||
"el-icon-calendar",
|
||||
"el-icon-camera",
|
||||
"el-icon-camera-filled",
|
||||
"el-icon-caret-bottom",
|
||||
"el-icon-caret-left",
|
||||
"el-icon-caret-right",
|
||||
"el-icon-caret-top",
|
||||
"el-icon-cellphone",
|
||||
"el-icon-chat-dot-round",
|
||||
"el-icon-chat-dot-square",
|
||||
"el-icon-chat-line-round",
|
||||
"el-icon-chat-line-square",
|
||||
"el-icon-chat-round",
|
||||
"el-icon-chat-square",
|
||||
"el-icon-check",
|
||||
"el-icon-checked",
|
||||
"el-icon-cherry",
|
||||
"el-icon-chicken",
|
||||
"el-icon-circle-check",
|
||||
"el-icon-circle-check-filled",
|
||||
"el-icon-circle-close",
|
||||
"el-icon-circle-close-filled",
|
||||
"el-icon-circle-plus",
|
||||
"el-icon-circle-plus-filled",
|
||||
"el-icon-clock",
|
||||
"el-icon-close",
|
||||
"el-icon-close-bold",
|
||||
"el-icon-cloudy",
|
||||
"el-icon-coffee",
|
||||
"el-icon-coffee-cup",
|
||||
"el-icon-coin",
|
||||
"el-icon-cold-drink",
|
||||
"el-icon-collection",
|
||||
"el-icon-collection-tag",
|
||||
"el-icon-comment",
|
||||
"el-icon-compass",
|
||||
"el-icon-connection",
|
||||
"el-icon-coordinate",
|
||||
"el-icon-copy-document",
|
||||
"el-icon-cpu",
|
||||
"el-icon-credit-card",
|
||||
"el-icon-crop",
|
||||
"el-icon-d-arrow-left",
|
||||
"el-icon-d-arrow-right",
|
||||
"el-icon-d-caret",
|
||||
"el-icon-data-analysis",
|
||||
"el-icon-data-board",
|
||||
"el-icon-data-line",
|
||||
"el-icon-delete",
|
||||
"el-icon-delete-filled",
|
||||
"el-icon-delete-location",
|
||||
"el-icon-dessert",
|
||||
"el-icon-discount",
|
||||
"el-icon-dish",
|
||||
"el-icon-dish-dot",
|
||||
"el-icon-document",
|
||||
"el-icon-document-add",
|
||||
"el-icon-document-checked",
|
||||
"el-icon-document-copy",
|
||||
"el-icon-document-delete",
|
||||
"el-icon-document-remove",
|
||||
"el-icon-download",
|
||||
"el-icon-drizzling",
|
||||
"el-icon-edit",
|
||||
"el-icon-edit-pen",
|
||||
"el-icon-eleme",
|
||||
"el-icon-eleme-filled",
|
||||
"el-icon-element-plus",
|
||||
"el-icon-expand",
|
||||
"el-icon-failed",
|
||||
"el-icon-female",
|
||||
"el-icon-files",
|
||||
"el-icon-film",
|
||||
"el-icon-filter",
|
||||
"el-icon-finished",
|
||||
"el-icon-first-aid-kit",
|
||||
"el-icon-flag",
|
||||
"el-icon-fold",
|
||||
"el-icon-folder",
|
||||
"el-icon-folder-add",
|
||||
"el-icon-folder-checked",
|
||||
"el-icon-folder-delete",
|
||||
"el-icon-folder-opened",
|
||||
"el-icon-folder-remove",
|
||||
"el-icon-food",
|
||||
"el-icon-football",
|
||||
"el-icon-fork-spoon",
|
||||
"el-icon-fries",
|
||||
"el-icon-full-screen",
|
||||
"el-icon-goblet",
|
||||
"el-icon-goblet-full",
|
||||
"el-icon-goblet-square",
|
||||
"el-icon-goblet-square-full",
|
||||
"el-icon-goods",
|
||||
"el-icon-goods-filled",
|
||||
"el-icon-grape",
|
||||
"el-icon-grid",
|
||||
"el-icon-guide",
|
||||
"el-icon-headset",
|
||||
"el-icon-help",
|
||||
"el-icon-help-filled",
|
||||
"el-icon-hide",
|
||||
"el-icon-histogram",
|
||||
"el-icon-home-filled",
|
||||
"el-icon-hot-water",
|
||||
"el-icon-house",
|
||||
"el-icon-ice-cream",
|
||||
"el-icon-ice-cream-round",
|
||||
"el-icon-ice-cream-square",
|
||||
"el-icon-ice-drink",
|
||||
"el-icon-ice-tea",
|
||||
"el-icon-info-filled",
|
||||
"el-icon-iphone",
|
||||
"el-icon-key",
|
||||
"el-icon-knife-fork",
|
||||
"el-icon-lightning",
|
||||
"el-icon-link",
|
||||
"el-icon-list",
|
||||
"el-icon-loading",
|
||||
"el-icon-location",
|
||||
"el-icon-location-filled",
|
||||
"el-icon-location-information",
|
||||
"el-icon-lock",
|
||||
"el-icon-lollipop",
|
||||
"el-icon-magic-stick",
|
||||
"el-icon-magnet",
|
||||
"el-icon-male",
|
||||
"el-icon-management",
|
||||
"el-icon-map-location",
|
||||
"el-icon-medal",
|
||||
"el-icon-menu",
|
||||
"el-icon-message",
|
||||
"el-icon-message-box",
|
||||
"el-icon-mic",
|
||||
"el-icon-microphone",
|
||||
"el-icon-milk-tea",
|
||||
"el-icon-minus",
|
||||
"el-icon-money",
|
||||
"el-icon-monitor",
|
||||
"el-icon-moon",
|
||||
"el-icon-moon-night",
|
||||
"el-icon-more",
|
||||
"el-icon-more-filled",
|
||||
"el-icon-mostly-cloudy",
|
||||
"el-icon-mouse",
|
||||
"el-icon-mug",
|
||||
"el-icon-mute",
|
||||
"el-icon-mute-notification",
|
||||
"el-icon-no-smoking",
|
||||
"el-icon-notebook",
|
||||
"el-icon-notification",
|
||||
"el-icon-odometer",
|
||||
"el-icon-office-building",
|
||||
"el-icon-open",
|
||||
"el-icon-operation",
|
||||
"el-icon-opportunity",
|
||||
"el-icon-orange",
|
||||
"el-icon-paperclip",
|
||||
"el-icon-partly-cloudy",
|
||||
"el-icon-pear",
|
||||
"el-icon-phone",
|
||||
"el-icon-phone-filled",
|
||||
"el-icon-picture",
|
||||
"el-icon-picture-filled",
|
||||
"el-icon-picture-rounded",
|
||||
"el-icon-pie-chart",
|
||||
"el-icon-place",
|
||||
"el-icon-platform",
|
||||
"el-icon-plus",
|
||||
"el-icon-pointer",
|
||||
"el-icon-position",
|
||||
"el-icon-postcard",
|
||||
"el-icon-pouring",
|
||||
"el-icon-present",
|
||||
"el-icon-price-tag",
|
||||
"el-icon-printer",
|
||||
"el-icon-promotion",
|
||||
"el-icon-question-filled",
|
||||
"el-icon-rank",
|
||||
"el-icon-reading",
|
||||
"el-icon-reading-lamp",
|
||||
"el-icon-refresh",
|
||||
"el-icon-refresh-left",
|
||||
"el-icon-refresh-right",
|
||||
"el-icon-refrigerator",
|
||||
"el-icon-remove",
|
||||
"el-icon-remove-filled",
|
||||
"el-icon-right",
|
||||
"el-icon-scale-to-original",
|
||||
"el-icon-school",
|
||||
"el-icon-scissor",
|
||||
"el-icon-search",
|
||||
"el-icon-select",
|
||||
"el-icon-sell",
|
||||
"el-icon-semi-select",
|
||||
"el-icon-service",
|
||||
"el-icon-set-up",
|
||||
"el-icon-setting",
|
||||
"el-icon-share",
|
||||
"el-icon-ship",
|
||||
"el-icon-shop",
|
||||
"el-icon-shopping-bag",
|
||||
"el-icon-shopping-cart",
|
||||
"el-icon-shopping-cart-full",
|
||||
"el-icon-smoking",
|
||||
"el-icon-soccer",
|
||||
"el-icon-sold-out",
|
||||
"el-icon-sort",
|
||||
"el-icon-sort-down",
|
||||
"el-icon-sort-up",
|
||||
"el-icon-stamp",
|
||||
"el-icon-star",
|
||||
"el-icon-star-filled",
|
||||
"el-icon-stopwatch",
|
||||
"el-icon-success-filled",
|
||||
"el-icon-sugar",
|
||||
"el-icon-suitcase",
|
||||
"el-icon-sunny",
|
||||
"el-icon-sunrise",
|
||||
"el-icon-sunset",
|
||||
"el-icon-switch",
|
||||
"el-icon-switch-button",
|
||||
"el-icon-takeaway-box",
|
||||
"el-icon-ticket",
|
||||
"el-icon-tickets",
|
||||
"el-icon-timer",
|
||||
"el-icon-toilet-paper",
|
||||
"el-icon-tools",
|
||||
"el-icon-top",
|
||||
"el-icon-top-left",
|
||||
"el-icon-top-right",
|
||||
"el-icon-trend-charts",
|
||||
"el-icon-trophy",
|
||||
"el-icon-turn-off",
|
||||
"el-icon-umbrella",
|
||||
"el-icon-unlock",
|
||||
"el-icon-upload",
|
||||
"el-icon-upload-filled",
|
||||
"el-icon-user",
|
||||
"el-icon-user-filled",
|
||||
"el-icon-van",
|
||||
"el-icon-video-camera",
|
||||
"el-icon-video-camera-filled",
|
||||
"el-icon-video-pause",
|
||||
"el-icon-video-play",
|
||||
"el-icon-view",
|
||||
"el-icon-wallet",
|
||||
"el-icon-wallet-filled",
|
||||
"el-icon-warning",
|
||||
"el-icon-warning-filled",
|
||||
"el-icon-watch",
|
||||
"el-icon-watermelon",
|
||||
"el-icon-wind-power",
|
||||
"el-icon-zoom-in",
|
||||
"el-icon-zoom-out"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '扩展',
|
||||
icons: [
|
||||
'sc-icon-vue',
|
||||
'sc-icon-code',
|
||||
'sc-icon-wechat',
|
||||
'sc-icon-bug-fill',
|
||||
'sc-icon-bug-line',
|
||||
'sc-icon-file-word',
|
||||
'sc-icon-file-excel',
|
||||
'sc-icon-file-ppt',
|
||||
'sc-icon-organization',
|
||||
'sc-icon-upload',
|
||||
'sc-icon-download'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
const DEFAULT_CONFIG = {
|
||||
//标题
|
||||
APP_NAME: 'SentOS',
|
||||
|
||||
//首页地址
|
||||
DASHBOARD_URL: "/dashboard",
|
||||
|
||||
//版本号
|
||||
APP_VER: "1.6.6",
|
||||
|
||||
//内核版本号
|
||||
CORE_VER: "1.6.6",
|
||||
|
||||
//接口地址
|
||||
API_URL: 'http://localhost:8000/admin/',
|
||||
|
||||
//请求超时
|
||||
TIMEOUT: 50000,
|
||||
|
||||
//TokenName
|
||||
TOKEN_NAME: "authorization",
|
||||
|
||||
//Token前缀,注意最后有个空格,如不需要需设置空字符串
|
||||
TOKEN_PREFIX: "Bearer ",
|
||||
|
||||
//追加其他头
|
||||
HEADERS: {},
|
||||
|
||||
//请求是否开启缓存
|
||||
REQUEST_CACHE: false,
|
||||
|
||||
//布局 默认:default | 通栏:header | 经典:menu | 功能坞:dock
|
||||
//dock将关闭标签和面包屑栏
|
||||
LAYOUT: 'default',
|
||||
|
||||
//菜单是否折叠
|
||||
MENU_IS_COLLAPSE: false,
|
||||
|
||||
//菜单是否启用手风琴效果
|
||||
MENU_UNIQUE_OPENED: false,
|
||||
|
||||
//是否开启多标签
|
||||
LAYOUT_TAGS: true,
|
||||
|
||||
//语言
|
||||
LANG: 'zh-cn',
|
||||
|
||||
//主题颜色
|
||||
COLOR: '',
|
||||
|
||||
//是否加密localStorage, 为空不加密,可填写AES(模式ECB,移位Pkcs7)加密
|
||||
LS_ENCRYPTION: '',
|
||||
|
||||
//localStorageAES加密秘钥,位数建议填写8的倍数
|
||||
LS_ENCRYPTION_key: '2XNN4K8LC0ELVWN4',
|
||||
|
||||
//控制台首页默认布局
|
||||
DEFAULT_GRID: {
|
||||
//默认分栏数量和宽度 例如 [24] [18,6] [8,8,8] [6,12,6]
|
||||
layout: [24, 12, 12],
|
||||
//小组件分布,com取值:pages/home/components 文件名
|
||||
copmsList: [
|
||||
['welcome'],
|
||||
['info'],
|
||||
['ver']
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
//合并业务配置
|
||||
import MY_CONFIG from "./myConfig"
|
||||
Object.assign(DEFAULT_CONFIG, MY_CONFIG)
|
||||
|
||||
// 如果生产模式,就合并动态的APP_CONFIG
|
||||
// public/config.js
|
||||
if(process.env.NODE_ENV === 'production'){
|
||||
Object.assign(DEFAULT_CONFIG, APP_CONFIG)
|
||||
}
|
||||
export default DEFAULT_CONFIG
|
||||
@@ -0,0 +1,21 @@
|
||||
//业务配置
|
||||
//会合并至this.$CONFIG
|
||||
//生产模式 public/config.js 同名key会覆盖这里的配置从而实现打包后的热更新
|
||||
//为避免和SCUI框架配置混淆建议添加前缀 MY_
|
||||
//全局可使用 this.$CONFIG.MY_KEY 访问
|
||||
|
||||
export default {
|
||||
//是否显示第三方授权登录
|
||||
MY_SHOW_LOGIN_OAUTH: false,
|
||||
|
||||
shopTemplateList: [
|
||||
{
|
||||
value: 'default',
|
||||
label: '默认模版',
|
||||
},
|
||||
{
|
||||
value: 'action',
|
||||
label: '活动模版'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 静态路由配置
|
||||
// 书写格式与动态路由格式一致,全部经由框架统一转换
|
||||
// 比较动态路由在meta中多加入了role角色权限,为数组类型。一个菜单是否有权限显示,取决于它以及后代菜单是否有权限。
|
||||
// routes 显示在左侧菜单中的路由(显示顺序在动态路由之前)
|
||||
// 示例如下
|
||||
|
||||
// const routes = [
|
||||
// {
|
||||
// name: "demo",
|
||||
// path: "/demo",
|
||||
// meta: {
|
||||
// icon: "el-icon-eleme-filled",
|
||||
// title: "演示",
|
||||
// role: ["SA"]
|
||||
// },
|
||||
// children: [{
|
||||
// name: "demopage",
|
||||
// path: "/demopage",
|
||||
// component: "test/autocode/index",
|
||||
// meta: {
|
||||
// icon: "el-icon-menu",
|
||||
// title: "演示页面",
|
||||
// role: ["SA"]
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
// ]
|
||||
|
||||
const routes = []
|
||||
|
||||
export default routes;
|
||||
@@ -0,0 +1,21 @@
|
||||
import API from "@/api";
|
||||
|
||||
//字典选择器配置
|
||||
|
||||
export default {
|
||||
dicApiObj: API.system.dictionary.list, //获取字典接口对象
|
||||
parseData: function (res) {
|
||||
return {
|
||||
data: res.data, //分析行数据字段结构
|
||||
msg: res.message, //分析描述字段结构
|
||||
code: res.code //分析状态字段结构
|
||||
}
|
||||
},
|
||||
request: {
|
||||
name: 'name' //规定搜索字段
|
||||
},
|
||||
props: {
|
||||
label: 'title', //映射label显示字段
|
||||
value: 'values', //映射value值字段
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//数据表格配置
|
||||
|
||||
import tool from '@/utils/tool'
|
||||
|
||||
export default {
|
||||
successCode: 1, //请求完成代码
|
||||
pageSize: 30, //表格每一页条数
|
||||
pageSizes: [30, 100, 200, 500], //表格可设置的一页条数
|
||||
paginationLayout: "total, sizes, prev, pager, next, jumper", //表格分页布局,可设置"total, sizes, prev, pager, next, jumper"
|
||||
parseData: function (res) { //数据分析
|
||||
return {
|
||||
data: res.data, //分析无分页的数据字段结构
|
||||
rows: res.data.data, //分析行数据字段结构
|
||||
total: res.data.total, //分析总数字段结构
|
||||
summary: res.data.summary, //分析合计行字段结构
|
||||
msg: res.message, //分析描述字段结构
|
||||
code: res.code //分析状态字段结构
|
||||
}
|
||||
},
|
||||
request: { //请求规定字段
|
||||
page: 'page', //规定当前分页字段
|
||||
pageSize: 'limit', //规定一页条数字段
|
||||
prop: 'prop', //规定排序字段名字段
|
||||
order: 'order' //规定排序规格字段
|
||||
},
|
||||
/**
|
||||
* 自定义列保存处理
|
||||
* @tableName scTable组件的props->tableName
|
||||
* @column 用户配置好的列
|
||||
*/
|
||||
columnSettingSave: function (tableName, column) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(()=>{
|
||||
//这里为了演示使用了session和setTimeout演示,开发时应用数据请求
|
||||
tool.session.set(tableName, column)
|
||||
resolve(true)
|
||||
},1000)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取自定义列
|
||||
* @tableName scTable组件的props->tableName
|
||||
* @column 组件接受到的props->column
|
||||
*/
|
||||
columnSettingGet: function (tableName, column) {
|
||||
return new Promise((resolve) => {
|
||||
//这里为了演示使用了session和setTimeout演示,开发时应用数据请求
|
||||
const userColumn = tool.session.get(tableName)
|
||||
if(userColumn){
|
||||
resolve(userColumn)
|
||||
}else{
|
||||
resolve(column)
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 重置自定义列
|
||||
* @tableName scTable组件的props->tableName
|
||||
* @column 组件接受到的props->column
|
||||
*/
|
||||
columnSettingReset: function (tableName, column) {
|
||||
return new Promise((resolve) => {
|
||||
//这里为了演示使用了session和setTimeout演示,开发时应用数据请求
|
||||
setTimeout(()=>{
|
||||
tool.session.remove(tableName)
|
||||
resolve(column)
|
||||
},1000)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//表格选择器配置
|
||||
|
||||
export default {
|
||||
pageSize: 100, //表格每一页条数
|
||||
parseData: function (res) {
|
||||
return {
|
||||
data: res.data,
|
||||
rows: res.data.data, //分析行数据字段结构
|
||||
total: res.data.total, //分析总数字段结构
|
||||
msg: res.message, //分析描述字段结构
|
||||
code: res.code //分析状态字段结构
|
||||
}
|
||||
},
|
||||
request: {
|
||||
page: 'page', //规定当前分页字段
|
||||
pageSize: 'pageSize', //规定一页条数字段
|
||||
keyword: 'keyword' //规定搜索字段
|
||||
},
|
||||
props: {
|
||||
label: 'name', //映射label显示字段
|
||||
value: 'id', //映射value值字段
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import API from "@/api";
|
||||
|
||||
//上传配置
|
||||
|
||||
export default {
|
||||
apiObj: API.common.upload, //上传请求API对象
|
||||
filename: "file", //form请求时文件的key
|
||||
successCode: 1, //请求完成代码
|
||||
maxSize: 10, //最大文件大小 默认10MB
|
||||
parseData: function (res) {
|
||||
return {
|
||||
code: res.code, //分析状态字段结构
|
||||
fileName: res.data.name,//分析文件名称
|
||||
src: res.data.url, //分析图片远程地址结构
|
||||
msg: res.message //分析描述字段结构
|
||||
}
|
||||
},
|
||||
apiObjFile: API.common.upload, //附件上传请求API对象
|
||||
maxSizeFile: 10 //最大文件大小 默认10MB
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import API from "@/api";
|
||||
|
||||
//审批工作流人员/组织选择器配置
|
||||
|
||||
export default {
|
||||
//配置接口正常返回代码
|
||||
successCode: 200,
|
||||
//配置组织
|
||||
group: {
|
||||
//请求接口对象
|
||||
apiObj: API.system.dept.list,
|
||||
//接受数据字段映射
|
||||
parseData: function (res) {
|
||||
return {
|
||||
rows: res.data,
|
||||
msg: res.message,
|
||||
code: res.code
|
||||
}
|
||||
},
|
||||
//显示数据字段映射
|
||||
props: {
|
||||
key: 'id',
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
}
|
||||
},
|
||||
//配置用户
|
||||
user: {
|
||||
apiObj: API.demo.page,
|
||||
pageSize: 20,
|
||||
parseData: function (res) {
|
||||
return {
|
||||
rows: res.data.rows,
|
||||
total: res.data.total,
|
||||
msg: res.message,
|
||||
code: res.code
|
||||
}
|
||||
},
|
||||
props: {
|
||||
key: 'id',
|
||||
label: 'user',
|
||||
},
|
||||
request: {
|
||||
page: 'page',
|
||||
pageSize: 'pageSize',
|
||||
groupId: 'groupId',
|
||||
keyword: 'keyword'
|
||||
}
|
||||
},
|
||||
//配置角色
|
||||
role: {
|
||||
//请求接口对象
|
||||
apiObj: API.system.dept.list,
|
||||
//接受数据字段映射
|
||||
parseData: function (res) {
|
||||
return {
|
||||
rows: res.data,
|
||||
msg: res.message,
|
||||
code: res.code
|
||||
}
|
||||
},
|
||||
//显示数据字段映射
|
||||
props: {
|
||||
key: 'id',
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { permission } from '@/utils/permission'
|
||||
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
const { value } = binding
|
||||
if(Array.isArray(value)){
|
||||
let ishas = false;
|
||||
value.forEach(item => {
|
||||
if(permission(item)){
|
||||
ishas = true;
|
||||
}
|
||||
})
|
||||
if (!ishas){
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
}else{
|
||||
if(!permission(value)){
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
el.$value = binding.value
|
||||
el.handler = () => {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.readOnly = 'readonly'
|
||||
textarea.style.position = 'absolute'
|
||||
textarea.style.left = '-9999px'
|
||||
textarea.value = el.$value
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
textarea.setSelectionRange(0, textarea.value.length)
|
||||
const result = document.execCommand('Copy')
|
||||
if (result) {
|
||||
ElMessage.success("复制成功")
|
||||
}
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
el.addEventListener('click', el.handler)
|
||||
},
|
||||
updated(el, binding){
|
||||
el.$value = binding.value
|
||||
},
|
||||
unmounted(el){
|
||||
el.removeEventListener('click', el.handler)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user