This commit is contained in:
2022-10-19 17:01:55 +08:00
commit 4f131cda36
466 changed files with 60382 additions and 0 deletions

19
src/App.vue Normal file
View File

@@ -0,0 +1,19 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
mounted() {
}
}
</script>
<style lang="scss" scoped>
#app {
background-color: #f2f4f9;
}
</style>

View File

@@ -0,0 +1,53 @@
/*
* @Author: lb
* @Date: 2022-07-13 08:40:14
* @LastEditors: lb
* @LastEditTime: 2022-07-13 08:40:14
* @Description: 数据分析-设备效率分析
*/
import request from '@/utils/request'
/**
* 获取产线列表
*/
export function getLineList() {
return request({
url: '/basic/production-line/list',
method: 'post',
data: {}
})
}
/**
* 获取工厂列表
*/
export function getFactoryList() {
return request({
url: '/basic/factory/list',
method: 'post',
data: {}
})
}
/**
* 请求设备效率分析数据
* @param {object} data
* @param {number} data.current
* @param {number} data.enabled
* @param {string} data.endTime
* @param {number} data.eqId
* @param {number} data.eqTypeId
* @param {number} data.ftId
* @param {number} data.productlines
* @param {number} data.size
* @param {string} data.startTime
* @param {number} data.type
* @param {number} data.wsId
*/
export function getOEE(data) {
return request({
url: '/analysis/equipment-analysis/oee',
method: 'post',
data
})
}

View File

@@ -0,0 +1,48 @@
/*
* @Author: lb
* @Date: 2022-07-13 08:40:14
* @LastEditors: lb
* @LastEditTime: 2022-07-13 08:40:14
* @Description: 数据分析-设备效率分析
*/
import request from '@/utils/request'
/**
* 获取产线列表
* @param {object} data
* @param {number} data.current
* @param {number} data.enabled
* @param {number} data.factoryId
* @param {string} data.key
* @param {number} data.size
*/
export function getLineList(data = {}) {
return request({
url: '/basic/production-line/list',
method: 'post',
data
})
}
/**
* 设备异常分析数据
* @param {object} data
* @param {string} data.startTime
* @param {string} data.endTime
* @param {number} data.productlines
* @param {number} data.type
* @param {number} [data.current]
* @param {number} [data.enabled]
* @param {number} [data.eqId]
* @param {number} [data.eqTypeId]
* @param {number} [data.ftId]
* @param {number} [data.size]
* @param {number} [data.wsId]
*/
export function getEquipmentExceptionAnalysis(data) {
return request({
url: '/analysis/equipment-analysis/mtbrAndMtbr',
method: 'post',
data
})
}

View File

@@ -0,0 +1,85 @@
/*
* @Author: lb
* @Date: 2022-07-22 08:40:14
* @LastEditors: lb
* @LastEditTime: 2022-07-22 08:40:14
* @Description: 数据分析-设备状态时序图
*/
import request from '@/utils/request'
/**
* 获取产线列表
* @param {object} data
* @param {number} data.current
* @param {number} data.enabled
* @param {number} data.factoryId
* @param {string} data.key
* @param {number} data.size
*/
export function getLineList(data = {}) {
return request({
url: '/basic/production-line/list',
method: 'post',
data
})
}
/**
* 获取工序列表
* @param {object} data
* @param {number} data.current
* @param {number} data.enabled
* @param {string} data.id
* @param {string} data.name
* @param {number} data.productionLineId
* @param {number} data.size
*/
export function getWorkSequenceList(data = {}) {
return request({
url: '/basic/workshop-section/list',
method: 'post',
data
})
}
/**
* 获取设备列表
* @param {object} data
* @param {string} data.code
* @param {number} data.current
* @param {number} data.enabled
* @param {string} data.fileName
* @param {string} data.name
* @param {number} data.size
* @param {number} data.workshopSectionId
*/
export function getEquipmentList(data = {}) {
return request({
url: '/basic/equipment/list',
method: 'post',
data
})
}
/**
* 获取设备状态时序数据
* @param {object} data
* @param {number} [data.current]
* @param {number} [data.size]
* @param {number} [data.enabled]
* @param {string} data.startTime
* @param {string} data.endTime
* @param {string} data.type
* @param {string} data.wsId
* @param {string} data.eqId
* @param {string} data.eqTypeId
* @param {string} data.ftId
* @param {string} data.productlines
*/
export function getStatus(data) {
return request({
url: '/analysis/equipment-analysis/time-Status',
method: 'post',
data
})
}

View File

@@ -0,0 +1,16 @@
/*
* @Author: zwq
* @Date: 2020-12-29 16:00:14
* @LastEditors: gtz
* @LastEditTime: 2022-05-07 16:32:34
* @Description:
*/
import request from '@/utils/request'
export function lineList(data) { // 获取产线列表
return request({
url: '/basic/production-line/page',
method: 'post',
data
})
}

41
src/api/article.js Normal file
View File

@@ -0,0 +1,41 @@
import request from '@/utils/request'
export function fetchList(query) {
return request({
url: '/vue-element-admin/article/list',
method: 'get',
params: query
})
}
export function fetchArticle(id) {
return request({
url: '/vue-element-admin/article/detail',
method: 'get',
params: { id }
})
}
export function fetchPv(pv) {
return request({
url: '/vue-element-admin/article/pv',
method: 'get',
params: { pv }
})
}
export function createArticle(data) {
return request({
url: '/vue-element-admin/article/create',
method: 'post',
data
})
}
export function updateArticle(data) {
return request({
url: '/vue-element-admin/article/update',
method: 'post',
data
})
}

13
src/api/basic.js Normal file
View File

@@ -0,0 +1,13 @@
/*
* @Date: 2021-01-21 17:30:30
* @LastEditors: gtz
* @LastEditTime: 2021-03-01 14:04:42
* @FilePath: \basic-admin\src\api\basic.js
* @Description: 基础功能接口定义
*/
// import request from '@/utils/request'
export const uploadPath = '/api/common/attachment/uploadFileFormData'
export const uploadOnePath = '/api/common/attachment/uploadFile'
export const cloudPath = 'http://mes.picaiba.com'

30
src/api/file.js Normal file
View File

@@ -0,0 +1,30 @@
/*
* @Author: gtz
* @Date: 2021-02-27 16:05:34
* @LastEditors: gtz
* @LastEditTime: 2021-03-11 20:10:43
* @Description: file content
*/
import request from '@/utils/request'
// 获取文件流
export function getUrl(params) {
return request({
url: '/common/attachment/downloadFile',
method: 'get',
responseType: 'blob',
params,
timeout: 60000
})
}
// 上传文件
export function UploadFile(data, params) {
return request({
url: '/common/attachment/uploadFile',
method: 'post',
data,
params: { typeCode: params }
})
}

80
src/api/menu.js Normal file
View File

@@ -0,0 +1,80 @@
/*
* @Date: 2020-12-28 20:46:53
* @LastEditors: zwq
* @LastEditTime: 2021-04-25 14:52:11
* @FilePath: \basic-admin\src\api\menu.js
* @Description: 菜单api接口定义
*/
import request from '@/utils/request'
// 获取菜单列表
export function getMenuList(data) {
return request({
url: '/upms/menu/list',
method: 'post',
data
})
}
// 获取菜单详情
export function getMenuDetail(id) {
return request({
url: '/upms/menu/get',
method: 'post',
data: { id }
})
}
// 新增菜单
export function addMenu(data) {
return request({
url: '/upms/menu/add',
method: 'post',
data
})
}
// 删除菜单
export function delMenu(id) {
return request({
url: '/upms/menu/delete',
method: 'post',
data: { id }
})
}
// 启停菜单
export function enableMenu(data) {
return request({
url: '/upms/menu/enabled',
method: 'post',
data
})
}
// 修改菜单
export function editMenu(data) {
return request({
url: '/upms/menu/update',
method: 'post',
data
})
}
// 菜单-角色关联授权
export function saveRoleMenu(data) {
return request({
url: '/upms/menu/saveRoleMenu',
method: 'post',
data
})
}
// 菜单-角色关联获取
export function listByRole(id) {
return request({
url: '/upms/menu/listByRole',
method: 'post',
data: { id }
})
}

60
src/api/org.js Normal file
View File

@@ -0,0 +1,60 @@
/*
* @Date: 2021-01-04 17:34:38
* @LastEditors: zwq
* @LastEditTime: 2021-04-12 14:23:30
* @FilePath: \basic-admin\src\api\org.js
* @Description: 框架基础模块——组织结构
*/
import request from '@/utils/request'
// 获取组织结构列表
export function getOrgList(data) {
return request({
url: '/upms/org/page',
method: 'post',
data
})
}
export function getOrgDetail(id) { // 获取组织结构单条数据
return request({
url: '/upms/org/get',
method: 'post',
data: { id }
})
}
// export function getOrgAllList(data) {
// return request({
// url: '/upms/org/list',
// method: 'post',
// data
// })
// }
// 添加组织结构
export function addOrg(data) {
return request({
url: '/upms/org/add',
method: 'post',
data
})
}
// 修改组织
export function editOrg(data) {
return request({
url: '/upms/org/update',
method: 'post',
data
})
}
// 删除组织结构
export function delOrg(id) {
return request({
url: '/upms/org/delete',
method: 'post',
data: { id }
})
}

8
src/api/qiniu.js Normal file
View File

@@ -0,0 +1,8 @@
import request from '@/utils/request'
export function getToken() {
return request({
url: '/qiniu/upload/token', // 假地址 自行替换
method: 'get'
})
}

17
src/api/remote-search.js Normal file
View File

@@ -0,0 +1,17 @@
import request from '@/utils/request'
export function searchUser(name) {
return request({
url: '/vue-element-admin/search/user',
method: 'get',
params: { name }
})
}
export function transactionList(query) {
return request({
url: '/vue-element-admin/transaction/list',
method: 'get',
params: query
})
}

View File

@@ -0,0 +1,56 @@
/*
* @Author: gtz
* @Date: 2021-04-01 09:24:20
* @LastEditors: gtz
* @LastEditTime: 2021-04-16 13:24:51
* @Description: file content
*/
import request from '@/utils/request'
export function page(data) {
return request({
url: '/report/report-sheet-category/page',
method: 'post',
data
})
}
export function list(data) {
return request({
url: '/report/report-sheet-category/list',
method: 'post',
data
})
}
export function del(data) {
return request({
url: '/report/report-sheet-category/delete',
method: 'post',
data
})
}
export function update(data) {
return request({
url: '/report/report-sheet-category/update',
method: 'post',
data
})
}
export function add(data) {
return request({
url: '/report/report-sheet-category/add',
method: 'post',
data
})
}
export function getData(data) {
return request({
url: '/report/report-sheet-category/get',
method: 'post',
data
})
}

View File

@@ -0,0 +1,49 @@
/*
* @Author: gtz
* @Date: 2021-03-07 18:39:03
* @LastEditors: gtz
* @LastEditTime: 2021-04-22 20:03:49
* @Description: file content
*/
import request from '@/utils/request'
export function page(data) {
return request({
url: '/report/report-sheet/page',
method: 'post',
data
})
}
export function del(data) {
return request({
url: '/report/report-sheet/delete',
method: 'post',
data
})
}
export function getInfo(data) {
return request({
url: '/report/report-sheet/get',
method: 'post',
data
})
}
export function update(data) {
return request({
url: '/report/report-sheet/update',
method: 'post',
data
})
}
// 报表分类列表
export function listCategory(data) {
return request({
url: '/report/report-sheet-category/list',
method: 'post',
data
})
}

87
src/api/role.js Normal file
View File

@@ -0,0 +1,87 @@
/*
* @Date: 2020-12-14 09:07:03
* @LastEditors: zwq
* @LastEditTime: 2021-07-21 15:48:05
* @FilePath: \basic-admin\src\api\role.js
* @Description: 角色权限api接口定义
*/
import request from '@/utils/request'
// 获取角色详细信息
export function getRoleInfo(data) {
return request({
url: `/upms/role/get`,
method: 'post',
data
})
}
export function RoleCode() { // 获取code
return request({
url: '/upms/role/get-code',
method: 'post'
})
}
// 获取角色列表
export function getRoleList(data) {
return request({
url: '/upms/role/list',
method: 'post',
data
})
}
// 添加角色
export function addRole(data) {
return request({
url: '/upms/role/add',
method: 'post',
data
})
}
// 修改角色
export function updateRole(data) {
return request({
url: '/upms/role/update',
method: 'post',
data
})
}
// 删除角色
export function deleteRole(data) {
return request({
url: `/upms/role/delete`,
method: 'post',
data
})
}
// 启停角色
export function enableRole(data) {
return request({
url: `/upms/role/enabled`,
method: 'post',
data
})
}
// 用户-角色关联授权
export function saveUserRole(data) {
return request({
url: '/upms/role/saveUserRole',
method: 'post',
data
})
}
// 用户-角色关联获取
export function listByUser(id) {
return request({
url: '/upms/role/listByUser',
method: 'post',
data: { id }
})
}

17
src/api/tree.js Normal file
View File

@@ -0,0 +1,17 @@
/*
* @Author: gtz
* @Date: 2021-04-25 14:31:11
* @LastEditors: gtz
* @LastEditTime: 2021-06-16 09:45:14
* @Description: file content
*/
import request from '@/utils/request'
// 获取树形结构
export function getTreeList(data) {
return request({
url: `/material/substrate-tree/getTree`,
method: 'post',
data
})
}

117
src/api/user.js Normal file
View File

@@ -0,0 +1,117 @@
/*
* @Date: 2020-12-14 09:07:03
* @LastEditors: guo
* @LastEditTime: 2021-01-11 09:40:09
* @FilePath: \basic-admin\src\api\user.js
* @Description: 用户管理 & 登录用户的相关api接口定义
*/
import request from '@/utils/request'
export function login(data) {
const dto = Object.assign(data, {
appType: 1,
userType: 1
})
return request({
url: '/passport/login',
method: 'post',
data: dto
})
}
export function logout() {
return request({
url: '/api/session/invalidateSessionByMobile',
method: 'post'
})
}
// 获取当前登录的用户信息
export function getUserInfo(data) {
return request({
url: '/passport/getLoginUser',
method: 'post',
data
})
}
// 修改当前用户密码
export function editUserPW(data) {
return request({
url: '/upms/user/modifyPassword',
method: 'post',
data
})
}
// 当前用户 找回密码
export function findUserPW(data) {
return request({
url: '/upms/user/findPassword',
method: 'post',
data
})
}
// 下面为:用户管理列表
// 获取用户列表 (根据需求可以考虑换成带分页的 /upms/user/page
export function getUserList(data) {
return request({
url: '/upms/user/page',
method: 'post',
data
})
}
// 新增用户
export function addUser(data) {
return request({
url: '/upms/user/add',
method: 'post',
data
})
}
// 删除用户
export function delUser(data) {
return request({
url: '/upms/user/delete',
method: 'post',
data
})
}
// 修改用户信息
export function editUser(data) {
return request({
url: '/upms/user/update',
method: 'post',
data
})
}
// 获取用户详细信息
export function getUserDetail(data) {
return request({
url: '/upms/user/get',
method: 'post',
data
})
}
// 管理员重置用户的密码
export function resetUserPW(data) {
return request({
url: '/upms/user/resetPassword',
method: 'post',
data
})
}
// 启停用户信息
export function enableUser(data) {
return request({
url: '/upms/user/enabled',
method: 'post',
data
})
}

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
src/assets/img/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

BIN
src/assets/img/back.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

BIN
src/assets/img/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
src/assets/img/back1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
src/assets/img/cnbm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
src/assets/img/empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
src/assets/img/head-w.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/img/head.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="30px" height="26px" viewBox="0 0 30 26" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 60 (88103) - https://sketch.com -->
<title>icon/流程箭头备份 5</title>
<desc>Created with Sketch.</desc>
<g id="3_工艺流程" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="3-自定义工艺流程" transform="translate(-633.000000, -397.000000)">
<g id="icon/流程箭头" transform="translate(630.000000, 392.000000)">
<g id="编组-2" opacity="0.50218564" stroke-width="1" fill-rule="evenodd" transform="translate(3.000000, 5.000000)">
<ellipse id="椭圆形" fill="#404040" cx="2.12742382" cy="13.0763435" rx="2.12742382" ry="2.21252078"></ellipse>
<ellipse id="椭圆形备份" fill="#404040" cx="8.51227147" cy="13.0763435" rx="2.12742382" ry="2.21252078"></ellipse>
<path d="M19.4593901,1.4429655 L21.8675258,3.61583195 C26.9580675,8.25644103 29.2570896,10.731031 29.7057398,12.459385 C29.748906,12.6160198 29.7763246,12.7796115 29.7855806,12.9482608 L29.7881771,13.0128311 L29.7890859,13.0763435 C29.7890859,13.2480738 29.7702733,13.4152344 29.7346744,13.5757179 C29.3077404,15.5566713 26.3837971,18.5124849 19.4593901,24.7097215 C18.5688126,25.5067745 17.2255693,25.4020797 16.4591722,24.4758791 C15.6927751,23.5496785 15.7934432,22.1527055 16.6840207,21.3556525 L18.3602818,19.843801 C20.3525013,18.0317598 21.9577562,16.511737 23.1725229,15.2878856 L14.8971191,15.2888643 C13.7221754,15.2888643 12.7696953,14.298285 12.7696953,13.0763435 C12.7696953,11.9075299 13.6411516,10.9503974 14.7451872,10.869378 L14.8971191,10.8638227 L23.171515,10.8637859 L22.3958746,10.0940454 C20.9337911,8.66258056 19.0284153,6.89523125 16.6840207,4.7970345 C15.7934432,3.9999815 15.6927751,2.60300846 16.4591722,1.67680788 C17.2255693,0.750607307 18.5688126,0.645912508 19.4593901,1.4429655 Z" id="形状结合" fill="#545454" fill-rule="nonzero"></path>
</g>
<rect id="矩形" x="0" y="0" width="36" height="36"></rect>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

BIN
src/assets/img/login.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

BIN
src/assets/img/login1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

BIN
src/assets/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
src/assets/img/status.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

View File

@@ -0,0 +1,155 @@
/* eslint-disable */
let CreatedOKLodop7766 = null;
let CLodopIsLocal;
//= ===判断是否需要 Web打印服务CLodop:===
//= ==(不支持插件的浏览器版本需要用它)===
export function needCLodop() {
try {
const ua = navigator.userAgent;
if (ua.match(/Windows\sPhone/i))
{ return true; }
if (ua.match(/iPhone|iPod|iPad/i))
{ return true; }
if (ua.match(/Android/i))
{ return true; }
if (ua.match(/Edge\D?\d+/i))
{ return true; }
const verTrident = ua.match(/Trident\D?\d+/i);
const verIE = ua.match(/MSIE\D?\d+/i);
let verOPR = ua.match(/OPR\D?\d+/i);
let verFF = ua.match(/Firefox\D?\d+/i);
const x64 = ua.match(/x64/i);
if ((!verTrident) && (!verIE) && (x64))
{ return true; }
if (verFF) {
verFF = verFF[0].match(/\d+/);
if ((verFF[0] >= 41) || (x64))
{ return true; }
} else if (verOPR) {
verOPR = verOPR[0].match(/\d+/);
if (verOPR[0] >= 32)
{ return true; }
} else if ((!verTrident) && (!verIE)) {
let verChrome = ua.match(/Chrome\D?\d+/i);
if (verChrome) {
verChrome = verChrome[0].match(/\d+/);
if (verChrome[0] >= 41)
{ return true; }
}
}
return false;
} catch (err) {
return true;
}
}
//= ===页面引用CLodop云打印必须的JS文件,用双端口(8000和18000避免其中某个被占用====
if (needCLodop()) {
const src1 = 'http://localhost:8000/CLodopfuncs.js?priority=1';
const src2 = 'http://localhost:18000/CLodopfuncs.js?priority=0';
const head = document.head || document.getElementsByTagName('head')[0] || document.documentElement;
let oscript = document.createElement('script');
oscript.src = src1;
head.insertBefore(oscript, head.firstChild);
oscript = document.createElement('script');
oscript.src = src2;
head.insertBefore(oscript, head.firstChild);
CLodopIsLocal = !!((src1 + src2).match(/\/\/localho|\/\/127.0.0./i));
}
//= ===获取LODOP对象的主过程====
export function getLodop(oOBJECT, oEMBED) {
// const strHtmInstall = "<br><font color='#FF00FF'>打印控件未安装!点击这里<a href='http://www.lodop.net/demolist/install_lodop32.exe' target='_self'>执行安装</a>,安装后请刷新页面或重新进入。</font>";
// const strHtmUpdate = "<br><font color='#FF00FF'>打印控件需要升级!点击这里<a href='http://www.lodop.net/demolist/install_lodop32.exe' target='_self'>执行升级</a>,升级后请重新进入。</font>";
// const strHtm64_Install = "<br><font color='#FF00FF'>打印控件未安装!点击这里<a href='http://www.lodop.net/demolist/install_lodop64.exe' target='_self'>执行安装</a>,安装后请刷新页面或重新进入。</font>";
// const strHtm64_Update = "<br><font color='#FF00FF'>打印控件需要升级!点击这里<a href='http://www.lodop.net/demolist/install_lodop64.exe' target='_self'>执行升级</a>,升级后请重新进入。</font>";
const strHtmInstall = "";
const strHtmUpdate = "";
const strHtm64_Install = "";
const strHtm64_Update = "";
const strHtmFireFox = "<br><br><font color='#FF00FF'>注意如曾安装过Lodop旧版附件npActiveXPLugin,请在【工具】->【附加组件】->【扩展】中先卸它)</font>";
const strHtmChrome = "<br><br><font color='#FF00FF'>(如果此前正常,仅因浏览器升级或重安装而出问题,需重新执行以上安装)</font>";
// const strCLodopInstall_1 = "<br><font color='#FF00FF'>Web打印服务CLodop未安装启动点击这里<a href='http://www.lodop.net/demolist/CLodop_Setup_for_Win32NT.exe' target='_self'>下载执行安装</a>";
const strCLodopInstall_1 = "";
const strCLodopInstall_2 = "<br>(若此前已安装过,可<a href='CLodop.protocol:setup' target='_self'>点这里直接再次启动</a>";
const strCLodopInstall_3 = ',成功后请刷新本页面。</font>';
// const strCLodopUpdate = "<br><font color='#FF00FF'>Web打印服务CLodop需升级!点击这里<a href='http://www.lodop.net/download/Lodop6.226_Clodop3.075.zip' target='_self'>执行升级</a>,升级后请刷新页面。</font>";
// const strCLodopUpdate = "<br><font color='#FF00FF'>Web打印服务需下载驱动!点击这里<a href='http://www.lodop.net/download/Lodop6.226_Clodop3.075.zip' target='_self'>下载安装</a>,升级后请刷新页面。</font>";
const strCLodopUpdate = "";
let LODOP;
try {
const ua = navigator.userAgent;
const isIE = !!(ua.match(/MSIE/i)) || !!(ua.match(/Trident/i));
if (needCLodop()) {
try {
LODOP = getCLodop();
} catch (err) {}
if (!LODOP && document.readyState !== 'complete') {
alert('网页还没下载完毕,请稍等一下再操作.');
return;
}
if (!LODOP) {
document.getElementById('lodopPrintTips').innerHTML = strCLodopUpdate + (CLodopIsLocal ? strCLodopInstall_2 : '')
+ strCLodopInstall_3;
return;
}
if (CLODOP.CVERSION < '3.0.7.5') {
document.getElementById('lodopPrintTips').innerHTML = strCLodopUpdate;
}
if (oEMBED && oEMBED.parentNode)
{ oEMBED.parentNode.removeChild(oEMBED); }
if (oOBJECT && oOBJECT.parentNode)
{ oOBJECT.parentNode.removeChild(oOBJECT); }
} else {
var is64IE = isIE && !!(ua.match(/x64/i));
//= ====如果页面有Lodop就直接使用没有则新建:==========
if (oOBJECT || oEMBED) {
if (isIE)
{ LODOP = oOBJECT; }
else
{ LODOP = oEMBED; }
} else if (!CreatedOKLodop7766) {
LODOP = document.createElement('object');
LODOP.setAttribute('width', 0);
LODOP.setAttribute('height', 0);
LODOP.setAttribute('style', 'position:absolute;left:0px;top:-100px;width:0px;height:0px;');
if (isIE)
{ LODOP.setAttribute('classid', 'clsid:2105C259-1E0C-4534-8141-A753534CB4CA'); }
else
{ LODOP.setAttribute('type', 'application/x-print-lodop'); }
document.documentElement.appendChild(LODOP);
CreatedOKLodop7766 = LODOP;
} else
{ LODOP = CreatedOKLodop7766; }
//= ====Lodop插件未安装时提示下载地址:==========
if ((!LODOP) || (!LODOP.VERSION)) {
if (ua.indexOf('Chrome') >= 0)
{ document.getElementById('lodopPrintTips').innerHTML = strHtmChrome;}
if (ua.indexOf('Firefox') >= 0)
{ document.getElementById('lodopPrintTips').innerHTML = strHtmFireFox; }
document.getElementById('lodopPrintTips').innerHTML = (is64IE ? strHtm64_Install : strHtmInstall);
return LODOP;
}
}
if (LODOP.VERSION < '6.2.2.6') {
if (!needCLodop())
{ document.getElementById('lodopPrintTips').innerHTML = (is64IE ? strHtm64_Update : strHtmUpdate); }
return LODOP;
}
//= ==如下空白位置适合调用统一功能(如注册语句、语言选择等):==
//LODOP.SET_LICENSES("","13528A153BAEE3A0254B9507DCDE2839","","");
LODOP.SET_LICENSES("中建材轻工业自动化研究所有限公司","9475EC59D4A0EDFD94E4CABC35371EF0962","中建材輕工業自動化研究所有限公司","BAE54D29718D6641CC2BB8B3517DE30BA31");
LODOP.SET_LICENSES("THIRD LICENSE","","China Building Materials Light Industry Automation Research Institute Co., Ltd.","D5EE28A4D38B9A43824992DBBAE2749494A");
//= ======================================================
return LODOP;
} catch (err) {
alert(`getLodop出错:${err}`);
}
}

BIN
src/assets/video/3d.mkv Normal file

Binary file not shown.

View File

@@ -0,0 +1,111 @@
<template>
<transition :name="transitionName">
<div v-show="visible" :style="customStyle" class="back-to-ceiling" @click="backToTop">
<svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height:16px;width:16px"><path d="M12.036 15.59a1 1 0 0 1-.997.995H5.032a.996.996 0 0 1-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29a1.003 1.003 0 0 1 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" /></svg>
</div>
</transition>
</template>
<script>
export default {
name: 'BackToTop',
props: {
visibilityHeight: {
type: Number,
default: 400
},
backPosition: {
type: Number,
default: 0
},
customStyle: {
type: Object,
default: function() {
return {
right: '50px',
bottom: '50px',
width: '40px',
height: '40px',
'border-radius': '4px',
'line-height': '45px',
background: '#e7eaf1'
}
}
},
transitionName: {
type: String,
default: 'fade'
}
},
data() {
return {
visible: false,
interval: null,
isMoving: false
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll)
if (this.interval) {
clearInterval(this.interval)
}
},
methods: {
handleScroll() {
this.visible = window.pageYOffset > this.visibilityHeight
},
backToTop() {
if (this.isMoving) return
const start = window.pageYOffset
let i = 0
this.isMoving = true
this.interval = setInterval(() => {
const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500))
if (next <= this.backPosition) {
window.scrollTo(0, this.backPosition)
clearInterval(this.interval)
this.isMoving = false
} else {
window.scrollTo(0, next)
}
i++
}, 16.7)
},
easeInOutQuad(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b
return -c / 2 * (--t * (t - 2) - 1) + b
}
}
}
</script>
<style scoped>
.back-to-ceiling {
position: fixed;
display: inline-block;
text-align: center;
cursor: pointer;
}
.back-to-ceiling:hover {
background: #d5dbe7;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0
}
.back-to-ceiling .Icon {
fill: #9aaabf;
background: none;
}
</style>

View File

@@ -0,0 +1,66 @@
<!--
* @Author: lb
* @Date: 2022-05-18 16:00:00
* @LastEditors: lb
* @LastEditTime: 2022-05-18 16:00:00
* @Description:
-->
<template>
<div :class="[className, { 'p-0': noPadding }]">
<slot />
</div>
</template>
<script>
export default {
props: {
size: {
// 取值范围: xl lg md sm
type: String,
default: 'de',
validator: function(val) {
return ['xl', 'lg', 'de', 'md', 'sm'].indexOf(val) !== -1
}
},
noPadding: {
type: Boolean,
default: false
}
},
computed: {
className: function() {
return `${this.size}-title`
}
}
}
</script>
<style lang="scss" scoped>
$pxls: (xl, 28px) (lg, 24px) (de, 20px) (md, 18px) (sm, 16px);
$mgr: 8px;
@each $size, $height in $pxls {
.#{$size}-title {
padding: 8px 0;
font-size: $height;
line-height: $height;
color: #000;
font-weight: 500;
font-family: '微软雅黑', 'Microsoft YaHei', Arial, Helvetica, sans-serif;
&::before {
content: '';
display: inline-block;
vertical-align: top;
width: 4px;
height: $height + 2px;
border-radius: 1px;
margin-right: $mgr;
background-color: #0b58ff;
}
}
}
.p-0 {
padding: 0;
}
</style>

View File

@@ -0,0 +1,262 @@
<!--
* @Date: 2020-12-14 09:07:03
* @LastEditors: gtz
* @LastEditTime: 2022-05-30 16:37:44
* @FilePath: \mt-bus-fe\src\components\BaseTable\index-compound.vue
* @Description:
-->
<template>
<div class="base-table-container">
<div class="setting">
<template v-if="topBtnConfig.length > 0">
<!-- table顶部操作按钮区 -->
<div class="action_btn">
<template v-for="(btn, index) in topBtnConfig">
<span
v-if="btn.type === 'add'"
:key="index"
style="display: inline-block;"
@click="clickTopButton(btn.type)"
>
<svg-icon style="width: 14px; height: 14px" class="item-icon" icon-class="table_add" />
<span class="add">{{ 'btn.add' | i18nFilter }}</span>
</span>
</template>
</div>
</template>
<!-- 设置table列的图标 -->
<el-popover placement="bottom-start" width="200" trigger="click">
<div class="setting-box">
<el-checkbox
v-for="(item, index) in tableConfig"
:key="'cb' + index"
v-model="selectedBox[index]"
:label="item.label"
/>
</div>
<i slot="reference" class="el-icon-s-tools" style="color:#0b58ff" />
</el-popover>
</div>
<el-table
v-loading="isLoading"
:header-cell-style="{ background:'#FAFAFA', color: '#000', height: '30px', padding: 0 }"
:data="renderData"
fit
highlight-current-row
style="width: 100%"
:span-method="spanMethod"
:max-height="height ? height : tableHeight(325)"
>
<el-table-column
v-if="page && limit"
v-bind="indexConfig"
prop="_pageIndex"
:label="'tableHeader.index' | i18nFilter"
width="70"
/>
<el-table-column
v-for="item in renderTableHeadList"
:key="item.prop || item.label"
v-bind="item"
:show-overflow-tooltip="item.showTip ? true : false"
>
<!-- <el-table-column
v-for="i in item.children"
v-if="item.children && item.children.length"
:key="i.prop"
v-bind="i"
:show-overflow-tooltip="i.showTip ? true : false"
>
<template slot-scope="scope">
<component
:is="i.subcomponent"
v-if="i.subcomponent"
:key="scope.row.id"
:inject-data="{ ...scope.row, ...i }"
@emitData="emitData"
/>
<span v-else>{{ scope.row[i.prop] | commonFilter(i.filter) }}</span>
</template>
</el-table-column>
<template v-else slot-scope="scope">
<h2>测试</h2>
<component
:is="item.subcomponent"
v-if="item.subcomponent"
:key="scope.row.id"
:inject-data="{ ...scope.row, ...item }"
@emitData="emitData"
/>
<span v-else>{{ scope.row[item.prop] | commonFilter(item.filter) }}</span>
</template>
-->
<template v-for="i in item.children">
<el-table-column
:key="i.prop || i.label"
v-bind="i"
:show-overflow-tooltip="i.showTip ? true : false"
>
<template slot-scope="scopeInner">
<component
:is="i.subcomponent"
v-if="i.subcomponent"
:key="scopeInner.row.id"
:inject-data="{ ...scopeInner.row, ...i }"
@emitData="emitData"
/>
<span v-else>{{ scopeInner.row[i.prop] | commonFilter(i.filter) }}</span>
</template>
</el-table-column>
</template>
<template v-if="!item.children" slot-scope="scope">
<component
:is="item.subcomponent"
v-if="item.subcomponent"
:key="scope.row.id"
:inject-data="{ ...scope.row, ...item }"
@emitData="emitData"
/>
<span v-else>{{ scope.row[item.prop] | commonFilter(item.filter) }}</span>
</template>
</el-table-column>
<slot name="content" />
<slot name="handleBtn" />
</el-table>
</div>
</template>
<script>
import { isObject /** , isString */ } from 'lodash'
import { tableHeight } from '@/utils/index'
export default {
name: 'BaseTableCompound',
filters: {
commonFilter: (source, filterType = a => a) => {
return filterType(source)
}
},
props: {
topBtnConfig: {
type: Array,
default: () => {
return []
}
},
tableData: {
type: Array,
required: true,
validator: val => val.filter(item => !isObject(item)).length === 0
},
tableConfig: {
type: Array,
required: true
// 解决tableConfig验证失败报错:
// validator: (val) => val.filter((item) => !isString(item.prop) || !isString(item.label)).length === 0
},
isLoading: {
type: Boolean,
required: false
},
page: {
type: Number,
required: false,
default: 0
},
limit: {
type: Number,
required: false,
default: 0
},
indexConfig: {
type: Object,
default: () => {
return { align: 'left' }
}
},
spanMethod: {
type: Function,
default: () => {
return function() {
return [0, 0]
}
}
},
height: {
type: Number,
required: false,
default: 0
}
},
data() {
return {
tableConfigBak: [],
selectedBox: new Array(20).fill(true),
tableHeight
}
},
computed: {
renderData() {
return this.tableData.map((item, index) => {
return {
...item,
_pageIndex: (this.page - 1) * this.limit + index + 1
}
})
},
renderTableHeadList() {
return this.tableConfig.filter((item, index) => {
return this.selectedBox[index]
})
}
},
beforeMount() {
this.selectedBox = new Array(20).fill(true)
},
// mounted() {
// this.tableConfigBak = cloneDeep(this.tableConfig).map(item => {
// return {
// ...item,
// selected: true
// }
// })
// },
methods: {
emitData(val) {
this.$emit('emitFun', val)
},
clickTopButton(val) {
this.$emit('clickTopBtn', val)
}
}
}
</script>
<style lang="scss">
@import '~@/styles/index.scss';
.base-table-container {
// .el-table {
// border-top: 1px solid #dfe6ec;
// }
}
.setting {
text-align: right;
padding: 0px 15px 4px;
.action_btn {
display: inline-block;
margin-right: 15px;
font-size: 14px;
@extend .pointer;
.add {
color: #0b58ff;
}
}
.setting-box {
width: 100px;
}
i {
color: #aaa;
@extend .pointer;
}
}
</style>

View File

@@ -0,0 +1,87 @@
<!--
* @Date: 2020-12-14 09:07:03
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-04-26 13:26:28
* @FilePath: \basic-admin\src\components\BaseTable\index.vue
* @Description:
-->
<template>
<div>
<el-table v-loading="isLoading" :data="renderData" :stripe="true" border fit highlight-current-row style="width: 100%">
<el-table-column v-if="page && limit" prop="_pageIndex" :label="'tableHeader.index' | i18nFilter" width="70" align="center" />
<el-table-column
v-for="item in tableConfig"
:key="item.prop"
v-bind="item"
>
<template slot-scope="scope">
<component :is="item.subcomponent" v-if="item.subcomponent" :inject-data="{...scope.row, ...item}" @emitData="emitData" />
<span v-else>{{ scope.row[item.prop] | commonFilter(item.filter) }}</span>
</template>
</el-table-column>
<slot name="content" />
<slot name="handleBtn" />
</el-table>
</div>
</template>
<script>
import { isObject, isString } from 'lodash'
export default {
name: 'BaseTable',
filters: {
commonFilter: (source, filterType = a => a) => {
return filterType(source)
}
},
props: {
tableData: {
type: Array,
required: true,
validator: val => val.filter(item => !isObject(item)).length === 0
},
tableConfig: {
type: Array,
required: true,
validator: val => val.filter(item => !isString(item.prop) || !isString(item.label)).length === 0
},
isLoading: {
type: Boolean,
required: false
},
page: {
type: Number,
required: false,
default: 0
},
limit: {
type: Number,
required: false,
default: 0
}
},
data() {
return {
}
},
computed: {
renderData() {
return this.tableData.map((item, index) => {
return {
...item,
_pageIndex: (this.page - 1) * this.limit + index + 1
}
})
}
},
methods: {
emitData(val) {
this.$emit('emitFun', val)
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,254 @@
<!--
* @Date: 2020-12-14 09:07:03
* @LastEditors: gtz
* @LastEditTime: 2022-06-13 08:59:21
* @FilePath: \mt-bus-fe\src\components\BaseTable\index.vue
* @Description:
-->
<template>
<div class="base-table-container">
<div class="setting">
<template v-if="topBtnConfig.length > 0">
<!-- table顶部操作按钮区 -->
<div class="action_btn">
<template v-for="(btn,index) in topBtnConfig">
<span v-if="btn.type==='add'" :key="index" style="display: inline-block;" @click="clickTopButton(btn.type)">
<svg-icon style="width: 14px; height: 14px" class="item-icon" icon-class="table_add" />
<span class="add">{{ 'btn.add' | i18nFilter }}</span>
</span>
</template>
</div>
</template>
<!-- 设置table列的图标 -->
<el-popover
placement="bottom-start"
width="200"
trigger="click"
>
<div class="setting-box">
<el-checkbox
v-for="(item, index) in tableConfig"
:key="'cb' + index"
v-model="selectedBox[index]"
:label="item.label"
/>
</div>
<i slot="reference" class="el-icon-s-tools" style="color:#0b58ff" />
</el-popover>
</div>
<el-table
v-loading="isLoading"
:header-cell-style="{background:'#FAFAFA',color:'#606266',height: '40px'}"
:data="renderData"
:show-header="showHeader"
:border="border"
fit
highlight-current-row
:high-index="highIndex"
:row-class-name="tableRowClassName"
:max-height="height ? height : tableHeight(325)"
style="width: 100%"
@row-click="handleRowClick"
@selection-change="handleSelectionChange"
>
<el-table-column v-if="sbox" type="selection" width="55" fixed />
<el-table-column v-if="page && limit && !toggleCustomIndex" prop="_pageIndex" :label="'tableHeader.index' | i18nFilter" width="70" align="center" fixed />
<el-table-column
v-for="item in renderTableHeadList"
:key="item.prop"
v-bind="item"
:align="item.align?item.align:'left'"
:min-width="item.minWidth?item.minWidth:150"
:fixed="item.isFixed ? true : false"
:show-overflow-tooltip="true"
>
<template slot-scope="scope">
<component :is="item.subcomponent" v-if="item.subcomponent" :key="scope.row.id" :inject-data="{...scope.row, ...item}" @emitData="emitData" />
<span v-else>{{ scope.row[item.prop] | commonFilter(item.filter) }}</span>
</template>
</el-table-column>
<slot name="content" />
<slot name="handleBtn" />
</el-table>
</div>
</template>
<script>
import { isObject, isString } from 'lodash'
import { tableHeight } from '@/utils/index'
export default {
name: 'BaseTable',
filters: {
commonFilter: (source, filterType = a => a) => {
return filterType(source)
}
},
props: {
toggleCustomIndex: {
type: Boolean,
default: false
},
topBtnConfig: {
type: Array,
default: () => {
return []
}
},
showHeader: {
type: Boolean,
default: true
},
border: {
type: Boolean,
default: false
},
height: {
type: Number,
required: false,
default: 0
},
tableData: {
type: Array,
required: true,
validator: val => val.filter(item => !isObject(item)).length === 0
},
tableConfig: {
type: Array,
required: true,
validator: val => val.filter(item => !isString(item.prop) || !isString(item.label)).length === 0
},
isLoading: {
type: Boolean,
required: false
},
page: {
type: Number,
required: false,
default: 0
},
limit: {
type: Number,
required: false,
default: 0
},
sbox: {
type: Boolean,
required: false
},
highIndex: { // 首行是否默认高亮
type: Boolean,
required: false
}
},
data() {
return {
tableConfigBak: [],
selectedBox: new Array(100).fill(true),
tableHeight,
tableRowIndex: null
}
},
computed: {
renderData() {
return this.tableData.map((item, index) => {
return {
...item,
_pageIndex: (this.page - 1) * this.limit + index + 1
}
})
},
renderTableHeadList() {
return this.tableConfig.filter((item, index) => {
return this.selectedBox[index]
})
}
},
watch: {
tableData: function(val) {
if (this.highIndex) { // 默认高亮行的,重置默认高亮行为第一行
this.tableRowIndex = 0
}
}
},
beforeMount() {
this.selectedBox = new Array(100).fill(true)
if (this.highIndex) {
this.tableRowIndex = 0
}
},
// mounted() {
// this.tableConfigBak = cloneDeep(this.tableConfig).map(item => {
// return {
// ...item,
// selected: true
// }
// })
// },
methods: {
emitData(val) {
this.$emit('emitFun', val)
},
clickTopButton(val) {
this.$emit('clickTopBtn', val)
},
handleSelectionChange(val) {
this.$emit('selectChangeFun', val)
},
handleRowClick(row) {
this.tableRowIndex = this.getArrayIndex(this.tableData, row) // 获取当前点击行下标
this.$emit('selectRow', row)
},
tableRowClassName({ row, rowIndex }) {
if (rowIndex === this.tableRowIndex) {
return 'success-row'
}
return ''
},
getArrayIndex(arr, obj) {
var i = arr.length
while (i--) {
if (arr[i].id === obj.id) {
return i
}
}
return -1
}
}
}
</script>
<style lang="scss">
@import "~@/styles/index.scss";
// .base-table-container {
// .el-table {
// border-top: none;
// border-left: none;
// border-right: none;
// }
// }
.setting {
text-align: right;
padding: 0px 15px 7px;
.action_btn {
display: inline-block;
margin-right: 15px;
font-size: 14px;
@extend .pointer;
.add {
color: #0b58ff;
}
}
.setting-box {
width: 100px;
}
i {
color: #aaa;
@extend .pointer;
}
}
.el-table .success-row {
background: #EAF1FC;
}
</style>

View File

@@ -0,0 +1,31 @@
<!--
* @Date: 2021-01-07 20:09:37
* @LastEditors: guo
* @LastEditTime: 2021-01-07 20:27:56
* @FilePath: \basic-admin\src\components\BaseTable\subcomponents\CheckDetail.vue
* @Description:
-->
<template>
<span>
<el-button type="text" size="small" @click="emitClick">{{ 'btn.detail' | i18nFilter }}</el-button>
</span>
</template>
<script>
export default {
props: {
injectData: {
type: Object,
default: () => ({})
}
},
methods: {
emitClick() {
console.log(this.injectData)
// if (url) {
// this.$createPicReview({ url })
// }
}
}
}
</script>

View File

@@ -0,0 +1,32 @@
<!--
* @Date: 2020-12-14 09:07:03
* @LastEditors: guo
* @LastEditTime: 2021-01-07 20:22:17
* @FilePath: \basic-admin\src\components\BaseTable\subcomponents\CheckPic.vue
* @Description: 修改查看图片组件的传参形式
-->
<template>
<span>
<el-button type="text" size="small" @click="checkPic(injectData)">{{ 'btn.see' | i18nFilter }}</el-button>
</span>
</template>
<script>
export default {
props: {
injectData: {
type: Object,
default: () => ({
url: ''
})
}
},
methods: {
checkPic(url) {
if (url) {
this.$createPicReview({ url })
}
}
}
}
</script>

View File

@@ -0,0 +1,36 @@
<!--
* @Date: 2021-02-20 10:45:21
* @LastEditors: guo
* @LastEditTime: 2021-03-24 11:05:35
* @FilePath: \basic-admin\src\components\BaseTable\subcomponents\ColorSqua.vue
* @Description:
-->
<template>
<div class="color-squa" :style="{'background-color': injectData.equipmentStatusColor}" @click="emitClick" />
</template>
<script>
export default {
props: {
injectData: {
type: Object,
default: () => ({})
}
},
methods: {
emitClick() {
console.log(this.injectData)
// if (url) {
// this.$createPicReview({ url })
// }
}
}
}
</script>
<style lang="scss">
.color-squa {
height: 15px;
width: 15px;
display: inline-block;
}
</style>

View File

@@ -0,0 +1,31 @@
<!--
* @Author: lb
* @Date: 2022-3-8 10:00:00
* @LastEditors: lb
* @LastEditTime: 2022-05-05 10:00:00
* @Description: 查看详情的更新版本更新日期2022.5.5
-->
<template>
<span>
<el-button style="padding-left: 0;" type="text" @click="emitClick">{{ injectData.buttonContent || '查看详情' }}</el-button>
</span>
</template>
<script>
export default {
props: {
injectData: {
type: Object,
default: () => ({})
}
},
methods: {
emitClick() {
this.$emit('emitData', {
action: this.injectData.actionName || 'view-detail-action',
data: this.injectData.emitFullData ? this.injectData : { id: this.injectData.id }
})
}
}
}
</script>

View File

@@ -0,0 +1,48 @@
<!--
* @Date: 2021-02-20 10:45:21
* @LastEditors: gtz
* @LastEditTime: 2021-04-13 15:07:05
* @FilePath: \basic-admin\src\components\BaseTable\subcomponents\ColorSqua.vue
* @Description:
-->
<template>
<span @click="emitClick">
{{ result }}
</span>
</template>
<script>
import { dictChange } from '@/utils'
export default {
props: {
injectData: {
type: Object,
default: () => ({})
}
},
data() {
return {
dict: {}
}
},
computed: {
result() {
return this.dict[this.injectData[this.injectData.prop]] || '-'
}
},
created() {
this.getDict()
},
methods: {
emitClick() {
console.log(this.injectData)
},
async getDict() {
if (this.injectData.filter) {
const result = await this.injectData.filter()
this.dict = dictChange(result, { key: 'id', value: 'name' })
}
}
}
}
</script>

View File

@@ -0,0 +1,149 @@
<!--
* @Date: 2020-12-14 09:07:03
* @LastEditors: gtz
* @LastEditTime: 2022-07-25 10:23:16
* @FilePath: \mt-bus-fe\src\components\BaseTable\subcomponents\MethodBtn.vue
* @Description: table右侧方法组件
-->
<template>
<el-table-column
v-if="methodList.length > 0"
slot="handleBtn"
prop="operation"
:label="'tableHeader.operation' | i18nFilter"
:width="trueWidth"
align="left"
:fixed="isFixed ? 'right' : false"
>
<template slot-scope="scope">
<span
v-for="(item, index) in methodList"
:key="'btn' + index"
>
<span v-show="item.showParam ? showFilter(item.showParam, scope.row) : (index === 0 ? false : true)" style="margin:0 3px;font-size: 18px;color:#E5E7EB">|</span>
<el-tooltip placement="top" :content="item.btnName | i18nFilter">
<el-button
v-show="item.showParam ? showFilter(item.showParam, scope.row) : true"
type="text"
:style="{color:(item.type === 'delete' ? '#FF5454' : '#0B58FF')}"
size="mini"
@click="clickButton({
data: scope.row,
type: item.type
})"
>
<!-- 此处的icon的名字命名为'table_'加上按钮的type -->
<svg-icon style="width: 18px; height: 18px" class="item-icon" :icon-class="'table_'+item.type" />
<!-- <span>{{ item.btnName | i18nFilter }}</span> -->
</el-button>
</el-tooltip>
</span>
</template>
</el-table-column>
</template>
<script>
/**
* 传入的组件prop格式
* methodList<MethodItem> = []
* Interface MethodItem = {
* btnName: string,
* type: string,
* callback: function (选用)
* }
*
*/
export default {
props: {
methodList: {
type: Array,
default: () => {
return []
}
},
width: {
type: Number,
default: () => 0
},
isFixed: {
type: Boolean,
default: true
}
},
computed: {
trueWidth() {
return this.width ? this.width : this.methodList.length * 80
}
},
methods: {
clickButton(raw) {
this.$emit('clickBtn', {
data: raw.data,
type: raw.type
})
},
showFilter(showParam, row) {
let showStatus = true
const showStatusArr = showParam.data.map(item => {
let showStatusItem = true
if (item.type === 'unequal') {
if (row[item.name] !== item.value) {
showStatusItem = true
} else {
showStatusItem = false
}
}
if (item.type === 'equal') {
if (row[item.name] === item.value) {
showStatusItem = true
} else {
showStatusItem = false
}
}
if (item.type === 'more') {
if (row[item.name] >= item.value) {
showStatusItem = true
} else {
showStatusItem = false
}
}
if (item.type === 'less') {
if (row[item.name] <= item.value) {
showStatusItem = true
} else {
showStatusItem = false
}
}
if (item.type === 'indexof') {
if (item.value.indexOf(row[item.name]) >= 0) {
showStatusItem = true
} else {
showStatusItem = false
}
}
if (item.type === 'unindexof') {
if (item.value.indexOf(row[item.name]) < 0) {
showStatusItem = true
} else {
showStatusItem = false
}
}
return showStatusItem
})
if (showStatusArr.indexOf(false) >= 0 && showParam.type === '&') {
showStatus = false
}
if (showStatusArr.indexOf(true) < 0 && showParam.type === '|') {
showStatus = false
}
return showStatus
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,77 @@
<!--
* @Author: lb
* @Date: 2022-3-8 10:00:00
* @LastEditors: lb
* @LastEditTime: 2022-3-8 10:00:00
* @Description: 启停某个区域的状态
-->
<template>
<el-switch v-model="state" type="text" size="small" :disabled="readonly" @change="changeHandler" />
</template>
<script>
export default {
props: {
injectData: {
type: Object,
default: () => ({})
}
},
data() {
return {
state: false,
payload: {}
}
},
computed: {
readonly() {
return !!this.injectData.readonly
}
},
mounted() {
this.mapToState()
},
// watch: {
// "injectData.enabled": {
// handler: function (val) {
// this.state = !!this.injectData.enabled;
// },
// deep: true,
// },
// "injectData.currentStatus": {
// handler: function (val) {
// this.state = !!(this.injectData.currentStatus === 'true');
// },
// deep: true,
// },
// },
methods: {
mapToState() {
if (this.injectData.prop === 'currentStatus') {
this.state = !!(this.injectData[this.injectData.prop].toString().trim() === 'true')
return
} else {
// enabled
this.state = !((this.injectData['enabled'] === 0 || this.injectData['enabled'] === '0'))
return
}
},
changeHandler() {
if (this.injectData.prop === 'enabled') {
// 启停区域/缓存区
this.payload.id = this.injectData.id
this.payload.enabled = this.state ? 1 : 0
} else {
// 启停其他实体-该else分支后期可能会被删除
this.payload.id = this.injectData.id
this.payload.code = this.injectData.code
this.payload.name = this.injectData.name
this.payload.currentStatus = this.state
}
this.$emit('emitData', { action: 'toggleEnabled', data: this.payload })
}
}
}
</script>

View File

@@ -0,0 +1,93 @@
<!--
* @Author: your name
* @Date: 2021-01-27 10:07:42
* @LastEditTime: 2021-01-28 16:26:15
* @LastEditors: gtz
* @Description: In User Settings Edit
* @FilePath: \mt-bus-fe\src\components\Breadcrumb\index.vue
-->
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="isredirect">{{ item.meta.title }}</span>
<span v-else class="no-redirect">{{ item.meta.title }}</span>
<!-- @click.prevent="handleLink(item)" -->
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
import pathToRegexp from 'path-to-regexp'
export default {
data() {
return {
levelList: null
}
},
watch: {
$route(route) {
// if you go to the redirect page, do not update the breadcrumbs
if (route.path.startsWith('/redirect/')) {
return
}
this.getBreadcrumb()
}
},
created() {
this.getBreadcrumb()
},
methods: {
getBreadcrumb() {
// only show routes with meta.title
const matched = this.$route.matched.filter(item => item.meta && item.meta.title)
// const first = matched[0]
// if (!this.isDashboard(first)) {
// matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
// }
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
},
isDashboard(route) {
const name = route && route.name
if (!name) {
return false
}
return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
},
pathCompile(path) {
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
const { params } = this.$route
var toPath = pathToRegexp.compile(path)
return toPath(params)
},
handleLink(item) {
const { redirect, path } = item
if (redirect) {
this.$router.push(redirect)
return
}
this.$router.push(this.pathCompile(path))
}
}
}
</script>
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 48px;
margin-left: 8px;
.isredirect {
color: rgba(0, 0, 0, 0.65);
}
.no-redirect {
color: #8C8C8C;
cursor: text;
}
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<div ref="chart" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from '../mixins/resize'
import { simpleChart } from '../option'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
},
sizeRatio: {
type: Number,
default: 1
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
testData() {
return [{
name: 'series1',
data: [{
name: '07/20',
value: 10
}, {
name: '07/21',
value: 11
}]
}, {
name: 'series2',
data: [{
name: '07/20',
value: 13
}, {
name: '07/21',
value: 15
}]
}]
},
initChart() {
this.chart = echarts.init(this.$refs.chart)
this.chart.setOption(simpleChart(this.testData(), 1, {
type: 'line',
yAxisName: '大小',
showLegend: true
}))
}
}
}
</script>

View File

@@ -0,0 +1,155 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
const xAxisData = []
const data = []
const data2 = []
for (let i = 0; i < 50; i++) {
xAxisData.push(i)
data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3)
}
this.chart.setOption({
backgroundColor: '#08263a',
grid: {
left: '5%',
right: '5%'
},
xAxis: [{
show: false,
data: xAxisData
}, {
show: false,
data: xAxisData
}],
visualMap: {
show: false,
min: 0,
max: 50,
dimension: 0,
inRange: {
color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
}
},
yAxis: {
axisLine: {
show: false
},
axisLabel: {
textStyle: {
color: '#4a657a'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#08263f'
}
},
axisTick: {
show: false
}
},
series: [{
name: 'back',
type: 'bar',
data: data2,
z: 1,
itemStyle: {
normal: {
opacity: 0.4,
barBorderRadius: 5,
shadowBlur: 3,
shadowColor: '#111'
}
}
}, {
name: 'Simulate Shadow',
type: 'line',
data,
z: 2,
showSymbol: false,
animationDelay: 0,
animationEasing: 'linear',
animationDuration: 1200,
lineStyle: {
normal: {
color: 'transparent'
}
},
areaStyle: {
normal: {
color: '#08263a',
shadowBlur: 50,
shadowColor: '#000'
}
}
}, {
name: 'front',
type: 'bar',
data,
xAxisIndex: 1,
z: 3,
itemStyle: {
normal: {
barBorderRadius: 5
}
}
}],
animationEasing: 'elasticOut',
animationEasingUpdate: 'elasticOut',
animationDelay(idx) {
return idx * 20
},
animationDelayUpdate(idx) {
return idx * 20
}
})
}
}
}
</script>

View File

@@ -0,0 +1,220 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
const colorList = ['#FFB61F', '#283D68', '#5AD8A6', '#E97466']
const shadowList = ['rgba(255, 179, 24, 0.5)', 'rgba(53, 66, 93, 0.5)', 'rgba(90, 216, 166, 0.6)', 'rgba(233, 116, 102, 0.5)']
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
},
chartData: {
type: Object,
default: () => {}
}
},
data() {
return {
chart: null
}
},
watch: {
chartData: {
handler: function(val) {
this.initChart()
}
// deep: true
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
this.chart.setOption({
// backgroundColor: '#394056',
tooltip: {
trigger: 'axis',
padding: 0,
backgroundColor: 'transparent',
renderMode: 'html',
textStyle: {
color: 'rgba(0, 0, 0, 0.85)',
fontSize: 12,
lineHeight: 14
},
formatter: function(param) {
var text = ''
console.log(param)
const paramItem = param.map(item => {
return `<div style="display: flex; min-width: 150px">
<span>
<span style="display: inline-block; width: 4px; height: 4px; border-radius: 4px; background-color: ${item.color}; position: relative; top: -3px"></span>
<span>${item.seriesName}</span>
</span>
<span style="flex: 1;text-align: right">${item.value}</span>
</div>`
})
text = `<div class="line-tooltip">
<p style="margin: 0">${param[0].name}</p>
${paramItem.join('')}
</div>`
return text
}
},
color: colorList,
legend: {
top: 0,
data: ['设备CT', '设备TT', '产线CT', '产线TT'],
right: '4%',
textStyle: {
fontSize: 12,
color: '#8C8C8C'
}
},
grid: {
top: 50,
left: '2%',
right: '2%',
bottom: '2%',
containLabel: true
},
xAxis: [{
type: 'category',
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#57617B'
}
},
data: this.chartData.timeArr
}],
yAxis: [{
type: 'value',
name: '',
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#57617B'
}
},
axisLabel: {
margin: 10,
textStyle: {
fontSize: 14
}
},
splitLine: {
lineStyle: {
color: 'rgba(0, 0, 0, .15)'
}
}
}],
series: [{
name: '设备CT',
type: 'line',
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 2,
shadowBlur: 4,
shadowColor: shadowList[0],
shadowOffsetY: 2
}
},
data: this.chartData.eqCT
}, {
name: '设备TT',
type: 'line',
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 2,
shadowBlur: 4,
shadowColor: shadowList[1],
shadowOffsetY: 2
}
},
data: this.chartData.eqTT
}, {
name: '产线CT',
type: 'line',
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 2,
shadowBlur: 4,
shadowColor: shadowList[2],
shadowOffsetY: 2
}
},
data: this.chartData.lineCT
}, {
name: '产线TT',
type: 'line',
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 2,
shadowBlur: 4,
shadowColor: shadowList[3],
shadowOffsetY: 2
}
},
data: this.chartData.lineTT
}]
})
}
}
}
</script>
<style lang="scss">
.line-tooltip{
background: #FFFFFF;
box-shadow: 0px 2px 6px 0px rgba(154, 170, 164, 0.5);
border-radius: 4px;
opacity: 0.85;
backdrop-filter: blur(23px);
padding: 8px;
}
</style>

View File

@@ -0,0 +1,227 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
this.chart.setOption({
backgroundColor: '#394056',
title: {
top: 20,
text: 'Requests',
textStyle: {
fontWeight: 'normal',
fontSize: 16,
color: '#F1F1F3'
},
left: '1%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
color: '#57617B'
}
}
},
legend: {
top: 20,
icon: 'rect',
itemWidth: 14,
itemHeight: 5,
itemGap: 13,
data: ['CMCC', 'CTCC', 'CUCC'],
right: '4%',
textStyle: {
fontSize: 12,
color: '#F1F1F3'
}
},
grid: {
top: 100,
left: '2%',
right: '2%',
bottom: '2%',
containLabel: true
},
xAxis: [{
type: 'category',
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#57617B'
}
},
data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
}],
yAxis: [{
type: 'value',
name: '(%)',
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#57617B'
}
},
axisLabel: {
margin: 10,
textStyle: {
fontSize: 14
}
},
splitLine: {
lineStyle: {
color: '#57617B'
}
}
}],
series: [{
name: 'CMCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(137, 189, 27, 0.3)'
}, {
offset: 0.8,
color: 'rgba(137, 189, 27, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(137,189,27)',
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}
},
data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
}, {
name: 'CTCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(0, 136, 212, 0.3)'
}, {
offset: 0.8,
color: 'rgba(0, 136, 212, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(0,136,212)',
borderColor: 'rgba(0,136,212,0.2)',
borderWidth: 12
}
},
data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
}, {
name: 'CUCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(219, 50, 51, 0.3)'
}, {
offset: 0.8,
color: 'rgba(219, 50, 51, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(219,50,51)',
borderColor: 'rgba(219,50,51,0.2)',
borderWidth: 12
}
},
data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
}]
})
}
}
}
</script>

View File

@@ -0,0 +1,271 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
const xData = (function() {
const data = []
for (let i = 1; i < 13; i++) {
data.push(i + 'month')
}
return data
}())
this.chart.setOption({
backgroundColor: '#344b58',
title: {
text: 'statistics',
x: '20',
top: '20',
textStyle: {
color: '#fff',
fontSize: '22'
},
subtextStyle: {
color: '#90979c',
fontSize: '16'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
textStyle: {
color: '#fff'
}
}
},
grid: {
left: '5%',
right: '5%',
borderWidth: 0,
top: 150,
bottom: 95,
textStyle: {
color: '#fff'
}
},
legend: {
x: '5%',
top: '10%',
textStyle: {
color: '#90979c'
},
data: ['female', 'male', 'average']
},
calculable: true,
xAxis: [{
type: 'category',
axisLine: {
lineStyle: {
color: '#90979c'
}
},
splitLine: {
show: false
},
axisTick: {
show: false
},
splitArea: {
show: false
},
axisLabel: {
interval: 0
},
data: xData
}],
yAxis: [{
type: 'value',
splitLine: {
show: false
},
axisLine: {
lineStyle: {
color: '#90979c'
}
},
axisTick: {
show: false
},
axisLabel: {
interval: 0
},
splitArea: {
show: false
}
}],
dataZoom: [{
show: true,
height: 30,
xAxisIndex: [
0
],
bottom: 30,
start: 10,
end: 80,
handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
handleSize: '110%',
handleStyle: {
color: '#d3dee5'
},
textStyle: {
color: '#fff' },
borderColor: '#90979c'
}, {
type: 'inside',
show: true,
height: 15,
start: 1,
end: 35
}],
series: [{
name: 'female',
type: 'bar',
stack: 'total',
barMaxWidth: 35,
barGap: '10%',
itemStyle: {
normal: {
color: 'rgba(255,144,128,1)',
label: {
show: true,
textStyle: {
color: '#fff'
},
position: 'insideTop',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
709,
1917,
2455,
2610,
1719,
1433,
1544,
3285,
5208,
3372,
2484,
4078
]
},
{
name: 'male',
type: 'bar',
stack: 'total',
itemStyle: {
normal: {
color: 'rgba(0,191,183,1)',
barBorderRadius: 0,
label: {
show: true,
position: 'top',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
327,
1776,
507,
1200,
800,
482,
204,
1390,
1001,
951,
381,
220
]
}, {
name: 'average',
type: 'line',
stack: 'total',
symbolSize: 10,
symbol: 'circle',
itemStyle: {
normal: {
color: 'rgba(252,230,48,1)',
barBorderRadius: 0,
label: {
show: true,
position: 'top',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
1036,
3693,
2962,
3810,
2519,
1915,
1748,
4675,
6209,
4323,
2865,
4298
]
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,56 @@
import { debounce } from '@/utils'
export default {
data() {
return {
$_sidebarElm: null,
$_resizeHandler: null
}
},
mounted() {
this.initListener()
},
activated() {
if (!this.$_resizeHandler) {
// avoid duplication init
this.initListener()
}
// when keep-alive chart activated, auto resize
this.resize()
},
beforeDestroy() {
this.destroyListener()
},
deactivated() {
this.destroyListener()
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.$_resizeHandler()
}
},
initListener() {
this.$_resizeHandler = debounce(() => {
this.resize()
}, 100)
window.addEventListener('resize', this.$_resizeHandler)
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
destroyListener() {
window.removeEventListener('resize', this.$_resizeHandler)
this.$_resizeHandler = null
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
},
resize() {
const { chart } = this
chart && chart.resize()
}
}
}

View File

@@ -0,0 +1,98 @@
/**
*
* @param {*} seriesData 传入的数据
* @param {*} ratio 放大倍率,用于调整图表中字体大小
* @param {*} option 附带的一些配置,可配置为柱状图或者折线图等等
*/
export function simpleChart(seriesData = {
name: '数据系列名',
data: [{
name: '类目名',
value: 123
}]
}, ratio = 1, option = {
type: 'bar',
showLegend: false,
yAxisName: '',
xAxisName: ''
}) {
const xAxisCateList = seriesData[0].data.map(item => item.name)
const seriesNameList = seriesData.map(item => item.name)
return {
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
// color: colorList,
legend: {
show: option.showLegend,
data: seriesNameList,
bottom: '-5',
textStyle: {
color: '#eee',
fontSize: 14 * ratio
},
itemWidth: 25 * ratio,
itemHeight: 14 * ratio
},
grid: {
top: '10%',
left: '6%',
right: '4%',
bottom: '8%',
containLabel: true
},
yAxis: {
type: 'value',
name: option.yAxisName,
nameGap: 15,
nameTextStyle: {
color: '#fff',
fontSize: 12 * ratio,
padding: [0, 30, 0, 0]
},
axisLine: {
show: false
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(255,255,255,0.4)'
}
},
axisLabel: {
margin: 15,
textStyle: {
color: '#eee',
fontSize: 14 * ratio
}
}
},
xAxis: {
type: 'category',
name: option.xAxisName,
axisLine: {
show: false
},
axisLabel: {
margin: 15,
textStyle: {
color: '#eee',
fontSize: 14 * ratio
}
},
data: xAxisCateList
},
series: seriesData.map(item => {
return {
name: item.name,
type: option.type,
data: item.data,
barMaxWidth: 30 * ratio
}
})
}
}

View File

@@ -0,0 +1,78 @@
<template>
<div v-if="errorLogs.length>0">
<el-badge :is-dot="true" style="line-height: 25px;margin-top: -5px;" @click.native="dialogTableVisible=true">
<el-button style="padding: 8px 10px;" size="small" type="danger">
<svg-icon icon-class="bug" />
</el-button>
</el-badge>
<el-dialog :visible.sync="dialogTableVisible" width="80%" append-to-body>
<div slot="title">
<span style="padding-right: 10px;">Error Log</span>
<el-button size="mini" type="primary" icon="el-icon-delete" @click="clearAll">Clear All</el-button>
</div>
<el-table :data="errorLogs" border>
<el-table-column label="Message">
<template slot-scope="{row}">
<div>
<span class="message-title">Msg:</span>
<el-tag type="danger">
{{ row.err.message }}
</el-tag>
</div>
<br>
<div>
<span class="message-title" style="padding-right: 10px;">Info: </span>
<el-tag type="warning">
{{ row.vm.$vnode.tag }} error in {{ row.info }}
</el-tag>
</div>
<br>
<div>
<span class="message-title" style="padding-right: 16px;">Url: </span>
<el-tag type="success">
{{ row.url }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="Stack">
<template slot-scope="scope">
{{ scope.row.err.stack }}
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'ErrorLog',
data() {
return {
dialogTableVisible: false
}
},
computed: {
errorLogs() {
return this.$store.getters.errorLogs
}
},
methods: {
clearAll() {
this.dialogTableVisible = false
this.$store.dispatch('errorLog/clearErrorLog')
}
}
}
</script>
<style scoped>
.message-title {
font-size: 16px;
color: #333;
font-weight: bold;
padding-right: 8px;
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<div style="padding: 5px 15px 5px 32px;" @click="toggleClick">
<svg-icon style="width: 24px; height: 24px" class="item-icon hamburger" :class="{'is-active':isActive}" icon-class="hamburgerBtn" />
</div>
</template>
<script>
export default {
name: 'Hamburger',
props: {
isActive: {
type: Boolean,
default: false
}
},
methods: {
toggleClick() {
this.$emit('toggleClick')
}
}
}
</script>
<style scoped>
.hamburger {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
}
.hamburger.is-active {
transform: rotate(180deg);
}
</style>

View File

@@ -0,0 +1,181 @@
<template>
<div :class="{'show':show}" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-select
ref="headerSearchSelect"
v-model="search"
:remote-method="querySearch"
filterable
default-first-option
clearable
remote
placeholder="Search"
class="header-search-select"
@change="change"
>
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" />
</el-select>
</div>
</template>
<script>
// fuse is a lightweight fuzzy-search module
// make search results more in line with expectations
import Fuse from 'fuse.js'
import path from 'path'
export default {
name: 'HeaderSearch',
data() {
return {
search: '',
options: [],
searchPool: [],
show: false,
fuse: undefined
}
},
computed: {
routes() {
return this.$store.getters.permission_routes
}
},
watch: {
routes() {
this.searchPool = this.generateRoutes(this.routes)
},
searchPool(list) {
this.initFuse(list)
},
show(value) {
if (value) {
document.body.addEventListener('click', this.close)
} else {
document.body.removeEventListener('click', this.close)
}
}
},
mounted() {
this.searchPool = this.generateRoutes(this.routes)
},
methods: {
click() {
this.show = !this.show
if (this.show) {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
}
},
close() {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
this.options = []
this.show = false
},
change(val) {
this.$router.push(val.path)
this.search = ''
this.options = []
this.$nextTick(() => {
this.show = false
})
},
initFuse(list) {
this.fuse = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
},
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
generateRoutes(routes, basePath = '/', prefixTitle = []) {
let res = []
for (const router of routes) {
// skip hidden router
if (router.hidden) { continue }
const data = {
path: path.resolve(basePath, router.path),
title: [...prefixTitle]
}
if (router.meta && router.meta.title) {
data.title = [...data.title, router.meta.title]
if (router.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data)
}
}
// recursive child routes
if (router.children) {
const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
}
}
return res
},
querySearch(query) {
if (query !== '') {
this.options = this.fuse.search(query)
} else {
this.options = []
}
}
}
}
</script>
<style lang="scss" scoped>
.header-search {
font-size: 0 !important;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
::v-deep .el-input__inner {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
/**
* database64文件格式转换为2进制
*
* @param {[String]} data dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了
* @param {[String]} mime [description]
* @return {[blob]} [description]
*/
export default function(data, mime) {
data = data.split(',')[1]
data = window.atob(data)
var ia = new Uint8Array(data.length)
for (var i = 0; i < data.length; i++) {
ia[i] = data.charCodeAt(i)
}
// canvas.toDataURL 返回的默认格式就是 image/png
return new Blob([ia], {
type: mime
})
}

View File

@@ -0,0 +1,39 @@
/**
* 点击波纹效果
*
* @param {[event]} e [description]
* @param {[Object]} arg_opts [description]
* @return {[bollean]} [description]
*/
export default function(e, arg_opts) {
var opts = Object.assign({
ele: e.target, // 波纹作用元素
type: 'hit', // hit点击位置扩散center中心点扩展
bgc: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
}, arg_opts)
var target = opts.ele
if (target) {
var rect = target.getBoundingClientRect()
var ripple = target.querySelector('.e-ripple')
if (!ripple) {
ripple = document.createElement('span')
ripple.className = 'e-ripple'
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
target.appendChild(ripple)
} else {
ripple.className = 'e-ripple'
}
switch (opts.type) {
case 'center':
ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
break
default:
ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px'
ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px'
}
ripple.style.backgroundColor = opts.bgc
ripple.className = 'e-ripple z-active'
return false
}
}

View File

@@ -0,0 +1,232 @@
export default {
zh: {
hint: '点击,或拖动图片至此处',
loading: '正在上传……',
noSupported: '浏览器不支持该功能请使用IE10以上或其他现在浏览器',
success: '上传成功',
fail: '图片上传失败',
preview: '头像预览',
btn: {
off: '取消',
close: '关闭',
back: '上一步',
save: '保存'
},
error: {
onlyImg: '仅限图片格式',
outOfSize: '单文件大小不能超过 ',
lowestPx: '图片最低像素为(宽*高):'
}
},
'zh-tw': {
hint: '點擊,或拖動圖片至此處',
loading: '正在上傳……',
noSupported: '瀏覽器不支持該功能請使用IE10以上或其他現代瀏覽器',
success: '上傳成功',
fail: '圖片上傳失敗',
preview: '頭像預覽',
btn: {
off: '取消',
close: '關閉',
back: '上一步',
save: '保存'
},
error: {
onlyImg: '僅限圖片格式',
outOfSize: '單文件大小不能超過 ',
lowestPx: '圖片最低像素為(寬*高):'
}
},
en: {
hint: 'Click or drag the file here to upload',
loading: 'Uploading…',
noSupported: 'Browser is not supported, please use IE10+ or other browsers',
success: 'Upload success',
fail: 'Upload failed',
preview: 'Preview',
btn: {
off: 'Cancel',
close: 'Close',
back: 'Back',
save: 'Save'
},
error: {
onlyImg: 'Image only',
outOfSize: 'Image exceeds size limit: ',
lowestPx: 'Image\'s size is too low. Expected at least: '
}
},
ro: {
hint: 'Atinge sau trage fișierul aici',
loading: 'Se încarcă',
noSupported: 'Browser-ul tău nu suportă acest feature. Te rugăm încearcă cu alt browser.',
success: 'S-a încărcat cu succes',
fail: 'A apărut o problemă la încărcare',
preview: 'Previzualizează',
btn: {
off: 'Anulează',
close: 'Închide',
back: 'Înapoi',
save: 'Salvează'
},
error: {
onlyImg: 'Doar imagini',
outOfSize: 'Imaginea depășește limita de: ',
loewstPx: 'Imaginea este prea mică; Minim: '
}
},
ru: {
hint: 'Нажмите, или перетащите файл в это окно',
loading: 'Загружаю……',
noSupported: 'Ваш браузер не поддерживается, пожалуйста, используйте IE10 + или другие браузеры',
success: 'Загрузка выполнена успешно',
fail: 'Ошибка загрузки',
preview: 'Предпросмотр',
btn: {
off: 'Отменить',
close: 'Закрыть',
back: 'Назад',
save: 'Сохранить'
},
error: {
onlyImg: 'Только изображения',
outOfSize: 'Изображение превышает предельный размер: ',
lowestPx: 'Минимальный размер изображения: '
}
},
'pt-br': {
hint: 'Clique ou arraste o arquivo aqui para carregar',
loading: 'Carregando…',
noSupported: 'Browser não suportado, use o IE10+ ou outro browser',
success: 'Sucesso ao carregar imagem',
fail: 'Falha ao carregar imagem',
preview: 'Pré-visualizar',
btn: {
off: 'Cancelar',
close: 'Fechar',
back: 'Voltar',
save: 'Salvar'
},
error: {
onlyImg: 'Apenas imagens',
outOfSize: 'A imagem excede o limite de tamanho: ',
lowestPx: 'O tamanho da imagem é muito pequeno. Tamanho mínimo: '
}
},
fr: {
hint: 'Cliquez ou glissez le fichier ici.',
loading: 'Téléchargement…',
noSupported: 'Votre navigateur n\'est pas supporté. Utilisez IE10 + ou un autre navigateur s\'il vous plaît.',
success: 'Téléchargement réussit',
fail: 'Téléchargement echoué',
preview: 'Aperçu',
btn: {
off: 'Annuler',
close: 'Fermer',
back: 'Retour',
save: 'Enregistrer'
},
error: {
onlyImg: 'Image uniquement',
outOfSize: 'L\'image sélectionnée dépasse la taille maximum: ',
lowestPx: 'L\'image sélectionnée est trop petite. Dimensions attendues: '
}
},
nl: {
hint: 'Klik hier of sleep een afbeelding in dit vlak',
loading: 'Uploaden…',
noSupported: 'Je browser wordt helaas niet ondersteund. Gebruik IE10+ of een andere browser.',
success: 'Upload succesvol',
fail: 'Upload mislukt',
preview: 'Voorbeeld',
btn: {
off: 'Annuleren',
close: 'Sluiten',
back: 'Terug',
save: 'Opslaan'
},
error: {
onlyImg: 'Alleen afbeeldingen',
outOfSize: 'De afbeelding is groter dan: ',
lowestPx: 'De afbeelding is te klein! Minimale afmetingen: '
}
},
tr: {
hint: 'Tıkla veya yüklemek istediğini buraya sürükle',
loading: 'Yükleniyor…',
noSupported: 'Tarayıcı desteklenmiyor, lütfen IE10+ veya farklı tarayıcı kullanın',
success: 'Yükleme başarılı',
fail: 'Yüklemede hata oluştu',
preview: 'Önizle',
btn: {
off: 'İptal',
close: 'Kapat',
back: 'Geri',
save: 'Kaydet'
},
error: {
onlyImg: 'Sadece resim',
outOfSize: 'Resim yükleme limitini aşıyor: ',
lowestPx: 'Resmin boyutu çok küçük. En az olması gereken: '
}
},
'es-MX': {
hint: 'Selecciona o arrastra una imagen',
loading: 'Subiendo...',
noSupported: 'Tu navegador no es soportado, porfavor usa IE10+ u otros navegadores mas recientes',
success: 'Subido exitosamente',
fail: 'Sucedió un error',
preview: 'Vista previa',
btn: {
off: 'Cancelar',
close: 'Cerrar',
back: 'Atras',
save: 'Guardar'
},
error: {
onlyImg: 'Unicamente imagenes',
outOfSize: 'La imagen excede el tamaño maximo:',
lowestPx: 'La imagen es demasiado pequeño. Se espera por lo menos:'
}
},
de: {
hint: 'Klick hier oder zieh eine Datei hier rein zum Hochladen',
loading: 'Hochladen…',
noSupported: 'Browser wird nicht unterstützt, bitte verwende IE10+ oder andere Browser',
success: 'Upload erfolgreich',
fail: 'Upload fehlgeschlagen',
preview: 'Vorschau',
btn: {
off: 'Abbrechen',
close: 'Schließen',
back: 'Zurück',
save: 'Speichern'
},
error: {
onlyImg: 'Nur Bilder',
outOfSize: 'Das Bild ist zu groß: ',
lowestPx: 'Das Bild ist zu klein. Mindestens: '
}
},
ja: {
hint: 'クリック・ドラッグしてファイルをアップロード',
loading: 'アップロード中...',
noSupported: 'このブラウザは対応されていません。IE10+かその他の主要ブラウザをお使いください。',
success: 'アップロード成功',
fail: 'アップロード失敗',
preview: 'プレビュー',
btn: {
off: 'キャンセル',
close: '閉じる',
back: '戻る',
save: '保存'
},
error: {
onlyImg: '画像のみ',
outOfSize: '画像サイズが上限を超えています。上限: ',
lowestPx: '画像が小さすぎます。最小サイズ: '
}
}
}

View File

@@ -0,0 +1,7 @@
export default {
'jpg': 'image/jpeg',
'png': 'image/png',
'gif': 'image/gif',
'svg': 'image/svg+xml',
'psd': 'image/photoshop'
}

View File

@@ -0,0 +1,55 @@
<!--
* @Author: your name
* @Date: 2021-01-27 10:07:42
* @LastEditTime: 2021-02-24 11:13:53
* @LastEditors: gtz
* @Description: In User Settings Edit
* @FilePath: \mt-bus-fe\src\components\LangSelect\index.vue
-->
<template>
<el-dropdown trigger="click" class="international" @command="handleSetLanguage">
<div>
<svg-icon style="width: 24px; height: 24px; vertical-align: -7px" class-name="international-icon" :icon-class="isShow ? 'language2' : 'language3'" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :disabled="language==='zh'" command="zh">
中文
</el-dropdown-item>
<el-dropdown-item :disabled="language==='en'" command="en">
English
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
props: {
isShow: {
type: Boolean,
default: () => {
return true
}
}
},
computed: {
language() {
return this.$store.getters.language
}
},
methods: {
handleSetLanguage(lang) {
// this.$i18n.locale = lang
// this.$store.dispatch('app/setLanguage', lang)
// this.$message({
// message: 'Switch Language Success',
// type: 'success',
// duration: 2000,
// onClose: () => {
// location.reload()
// }
// })
}
}
}
</script>

View File

@@ -0,0 +1,11 @@
import Vue from 'vue'
// 按照方法调用vue组件用完即销毁
const ListDialogConstrctor = Vue.extend(require('./main.vue').default)
// 定义原型方法
Vue.prototype.$createListReview = (options) => {
const instance = new ListDialogConstrctor({
data: options
})
instance.vm = instance.$mount()
document.getElementById('app').appendChild(instance.vm.$el)
}

View File

@@ -0,0 +1,38 @@
<!--
* @Date: 2020-12-14 09:07:03
* @LastEditors: guo
* @LastEditTime: 2020-12-16 15:10:51
* @FilePath: \basic-admin\src\components\ListDialog\main.vue
* @Description:
-->
<template>
<el-dialog :visible="visible" :destroy-on-close="true" :before-close="handleClose" title="查看子变量">
<el-table :data="list" border stripe>
<el-table-column prop="vt" label="数据类型" />
<el-table-column prop="val" label="数据值" />
</el-table>
</el-dialog>
</template>
<script>
export default {
name: 'ListDialog',
data() {
return {
visible: true,
list: []
}
},
methods: {
handleClose() {
this.$destroy(true)
this.$el.parentNode.removeChild(this.$el)
}
}
}
</script>
<style>
#root-pic {
display: none;
}
</style>

View File

@@ -0,0 +1,360 @@
<template>
<div :class="computedClasses" class="material-input__component">
<div :class="{iconClass:icon}">
<i v-if="icon" :class="['el-icon-' + icon]" class="el-input__icon material-input__icon" />
<input
v-if="type === 'email'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="email"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'url'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="url"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'number'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:step="step"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:max="max"
:min="min"
:minlength="minlength"
:maxlength="maxlength"
:required="required"
type="number"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'password'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:max="max"
:min="min"
:required="required"
type="password"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'tel'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="tel"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'text'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:minlength="minlength"
:maxlength="maxlength"
:required="required"
type="text"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<span class="material-input-bar" />
<label class="material-label">
<slot />
</label>
</div>
</div>
</template>
<script>
// source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue
export default {
name: 'MdInput',
props: {
/* eslint-disable */
icon: String,
name: String,
type: {
type: String,
default: 'text'
},
value: [String, Number],
placeholder: String,
readonly: Boolean,
disabled: Boolean,
min: String,
max: String,
step: String,
minlength: Number,
maxlength: Number,
required: {
type: Boolean,
default: true
},
autoComplete: {
type: String,
default: 'off'
},
validateEvent: {
type: Boolean,
default: true
}
},
data() {
return {
currentValue: this.value,
focus: false,
fillPlaceHolder: null
}
},
computed: {
computedClasses() {
return {
'material--active': this.focus,
'material--disabled': this.disabled,
'material--raised': Boolean(this.focus || this.currentValue) // has value
}
}
},
watch: {
value(newValue) {
this.currentValue = newValue
}
},
methods: {
handleModelInput(event) {
const value = event.target.value
this.$emit('input', value)
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.change', [value])
}
}
this.$emit('change', value)
},
handleMdFocus(event) {
this.focus = true
this.$emit('focus', event)
if (this.placeholder && this.placeholder !== '') {
this.fillPlaceHolder = this.placeholder
}
},
handleMdBlur(event) {
this.focus = false
this.$emit('blur', event)
this.fillPlaceHolder = null
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.blur', [this.currentValue])
}
}
}
}
}
</script>
<style lang="scss" scoped>
// Fonts:
$font-size-base: 16px;
$font-size-small: 18px;
$font-size-smallest: 12px;
$font-weight-normal: normal;
$font-weight-bold: bold;
$apixel: 1px;
// Utils
$spacer: 12px;
$transition: 0.2s ease all;
$index: 0px;
$index-has-icon: 30px;
// Theme:
$color-white: white;
$color-grey: #9E9E9E;
$color-grey-light: #E0E0E0;
$color-blue: #2196F3;
$color-red: #F44336;
$color-black: black;
// Base clases:
%base-bar-pseudo {
content: '';
height: 1px;
width: 0;
bottom: 0;
position: absolute;
transition: $transition;
}
// Mixins:
@mixin slided-top() {
top: - ($font-size-base + $spacer);
left: 0;
font-size: $font-size-base;
font-weight: $font-weight-bold;
}
// Component:
.material-input__component {
margin-top: 36px;
position: relative;
* {
box-sizing: border-box;
}
.iconClass {
.material-input__icon {
position: absolute;
left: 0;
line-height: $font-size-base;
color: $color-blue;
top: $spacer;
width: $index-has-icon;
height: $font-size-base;
font-size: $font-size-base;
font-weight: $font-weight-normal;
pointer-events: none;
}
.material-label {
left: $index-has-icon;
}
.material-input {
text-indent: $index-has-icon;
}
}
.material-input {
font-size: $font-size-base;
padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
display: block;
width: 100%;
border: none;
line-height: 1;
border-radius: 0;
&:focus {
outline: none;
border: none;
border-bottom: 1px solid transparent; // fixes the height issue
}
}
.material-label {
font-weight: $font-weight-normal;
position: absolute;
pointer-events: none;
left: $index;
top: 0;
transition: $transition;
font-size: $font-size-small;
}
.material-input-bar {
position: relative;
display: block;
width: 100%;
&:before {
@extend %base-bar-pseudo;
left: 50%;
}
&:after {
@extend %base-bar-pseudo;
right: 50%;
}
}
// Disabled state:
&.material--disabled {
.material-input {
border-bottom-style: dashed;
}
}
// Raised state:
&.material--raised {
.material-label {
@include slided-top();
}
}
// Active state:
&.material--active {
.material-input-bar {
&:before,
&:after {
width: 50%;
}
}
}
}
.material-input__component {
background: $color-white;
.material-input {
background: none;
color: $color-black;
text-indent: $index;
border-bottom: 1px solid $color-grey-light;
}
.material-label {
color: $color-grey;
}
.material-input-bar {
&:before,
&:after {
background: $color-blue;
}
}
// Active state:
&.material--active {
.material-label {
color: $color-blue;
}
}
// Errors:
&.material--has-errors {
&.material--active .material-label {
color: $color-red;
}
.material-input-bar {
&:before,
&:after {
background: transparent;
}
}
}
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<div :class="{'hidden':hidden}" class="pagination-container">
<el-pagination
small
:background="background"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:total="total"
v-bind="$attrs"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script>
import { scrollTo } from '@/utils/scroll-to'
export default {
name: 'Pagination',
props: {
total: {
required: true,
type: Number
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50]
}
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
},
computed: {
currentPage: {
get() {
return this.page
},
set(val) {
this.$emit('update:page', val)
}
},
pageSize: {
get() {
return this.limit
},
set(val) {
this.$emit('update:limit', val)
}
}
},
methods: {
handleSizeChange(val) {
this.$emit('pagination', { page: this.currentPage, limit: val })
if (this.autoScroll) {
scrollTo(0, 800)
}
},
handleCurrentChange(val) {
this.$emit('pagination', { current: val, limit: this.pageSize })
if (this.autoScroll) {
scrollTo(0, 800)
}
}
}
}
</script>
<style scoped>
.pagination-container {
background: #fff;
padding-top: 20px;
text-align: right;
border-radius: 4px;
}
.pagination-container.hidden {
display: none;
}
</style>

View File

@@ -0,0 +1,11 @@
import Vue from 'vue'
// 按照方法调用vue组件用完即销毁
const PicConstrctor = Vue.extend(require('./main.vue').default)
// 定义原型方法
Vue.prototype.$createPicReview = (options) => {
const instance = new PicConstrctor({
data: options
})
instance.vm = instance.$mount()
document.getElementById('app').appendChild(instance.vm.$el)
}

View File

@@ -0,0 +1,41 @@
<template>
<div>
<img id="root-pic" ref="targetImg" :src="url">
</div>
</template>
<script>
import Viewer from 'viewerjs'
import 'viewerjs/dist/viewer.css'
export default {
name: 'PicDialog',
data() {
return {
instance: null,
url: null
}
},
mounted() {
this.instance = new Viewer(this.$refs.targetImg, {
inline: false,
navbar: false,
// viewed() {
// this.instance.zoomTo(1)
// },
hidden: () => {
console.log('destroy')
this.instance.destroy()
this.$destroy(true)
this.$el.parentNode.removeChild(this.$el)
}
})
this.instance.show()
console.log(this.instance)
}
}
</script>
<style>
#root-pic {
display: none;
}
</style>

View File

@@ -0,0 +1,145 @@
<template>
<div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
<div class="rightPanel-background" />
<div class="rightPanel">
<div class="handle-button" :style="{'bottom':buttonBottom+'px','background-color':theme}" @click="show=!show">
<i :class="show?'el-icon-close':'el-icon-setting'" />
</div>
<div class="rightPanel-items">
<slot />
</div>
</div>
</div>
</template>
<script>
import { addClass, removeClass } from '@/utils'
export default {
name: 'RightPanel',
props: {
clickNotClose: {
default: false,
type: Boolean
},
buttonBottom: {
default: 55,
type: Number
}
},
data() {
return {
show: false
}
},
computed: {
theme() {
return this.$store.state.settings.theme
}
},
watch: {
show(value) {
if (value && !this.clickNotClose) {
this.addEventClick()
}
if (value) {
addClass(document.body, 'showRightPanel')
} else {
removeClass(document.body, 'showRightPanel')
}
}
},
mounted() {
this.insertToBody()
},
beforeDestroy() {
const elx = this.$refs.rightPanel
elx.remove()
},
methods: {
addEventClick() {
window.addEventListener('click', this.closeSidebar)
},
closeSidebar(evt) {
const parent = evt.target.closest('.rightPanel')
if (!parent) {
this.show = false
window.removeEventListener('click', this.closeSidebar)
}
},
insertToBody() {
const elx = this.$refs.rightPanel
const body = document.querySelector('body')
body.insertBefore(elx, body.firstChild)
}
}
}
</script>
<style>
.showRightPanel {
overflow: hidden;
position: relative;
width: calc(100% - 15px);
}
</style>
<style lang="scss" scoped>
.rightPanel-background {
position: fixed;
top: 0;
left: 0;
opacity: 0;
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
background: rgba(0, 0, 0, .2);
z-index: -1;
}
.rightPanel {
width: 100%;
max-width: 260px;
height: 100vh;
position: fixed;
top: 0;
right: 0;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
transition: all .25s cubic-bezier(.7, .3, .1, 1);
transform: translate(100%);
background: #fff;
z-index: 40000;
}
.show {
transition: all .3s cubic-bezier(.7, .3, .1, 1);
.rightPanel-background {
z-index: 20000;
opacity: 1;
width: 100%;
height: 100%;
}
.rightPanel {
transform: translate(0);
}
}
.handle-button {
width: 48px;
height: 48px;
position: absolute;
left: -48px;
text-align: center;
font-size: 24px;
border-radius: 6px 0 0 6px !important;
z-index: 0;
pointer-events: auto;
cursor: pointer;
color: #fff;
line-height: 48px;
i {
font-size: 24px;
line-height: 48px;
}
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div>
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
</div>
</template>
<script>
import screenfull from 'screenfull'
export default {
name: 'Screenfull',
data() {
return {
isFullscreen: false
}
},
mounted() {
this.init()
},
beforeDestroy() {
this.destroy()
},
methods: {
click() {
if (!screenfull.enabled) {
this.$message({
message: 'you browser can not work',
type: 'warning'
})
return false
}
screenfull.toggle()
},
change() {
this.isFullscreen = screenfull.isFullscreen
},
init() {
if (screenfull.enabled) {
screenfull.on('change', this.change)
}
},
destroy() {
if (screenfull.enabled) {
screenfull.off('change', this.change)
}
}
}
}
</script>
<style scoped>
.screenfull-svg {
display: inline-block;
cursor: pointer;
fill: #5a5e66;;
width: 20px;
height: 20px;
vertical-align: 10px;
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<div :style="{height:height+'px',zIndex:zIndex}">
<div
:class="className"
:style="{top:(isSticky ? stickyTop +'px' : ''),zIndex:zIndex,position:position,width:width,height:height+'px'}"
>
<slot>
<div>sticky</div>
</slot>
</div>
</div>
</template>
<script>
export default {
name: 'Sticky',
props: {
stickyTop: {
type: Number,
default: 0
},
zIndex: {
type: Number,
default: 1
},
className: {
type: String,
default: ''
}
},
data() {
return {
active: false,
position: '',
width: undefined,
height: undefined,
isSticky: false
}
},
mounted() {
this.height = this.$el.getBoundingClientRect().height
window.addEventListener('scroll', this.handleScroll)
window.addEventListener('resize', this.handleResize)
},
activated() {
this.handleScroll()
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleResize)
},
methods: {
sticky() {
if (this.active) {
return
}
this.position = 'fixed'
this.active = true
this.width = this.width + 'px'
this.isSticky = true
},
handleReset() {
if (!this.active) {
return
}
this.reset()
},
reset() {
this.position = ''
this.width = 'auto'
this.active = false
this.isSticky = false
},
handleScroll() {
const width = this.$el.getBoundingClientRect().width
this.width = width || 'auto'
const offsetTop = this.$el.getBoundingClientRect().top
if (offsetTop < this.stickyTop) {
this.sticky()
return
}
this.handleReset()
},
handleResize() {
if (this.isSticky) {
this.width = this.$el.getBoundingClientRect().width + 'px'
}
}
}
}
</script>

View File

@@ -0,0 +1,62 @@
<template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
import { isExternal } from '@/utils/validate'
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover!important;
display: inline-block;
}
</style>

View File

@@ -0,0 +1,113 @@
<template>
<a :class="className" class="link--mallki" href="#">
{{ text }}
<span :data-letters="text" />
<span :data-letters="text" />
</a>
</template>
<script>
export default {
props: {
className: {
type: String,
default: ''
},
text: {
type: String,
default: 'vue-element-admin'
}
}
}
</script>
<style>
/* Mallki */
.link--mallki {
font-weight: 800;
color: #4dd9d5;
font-family: 'Dosis', sans-serif;
-webkit-transition: color 0.5s 0.25s;
transition: color 0.5s 0.25s;
overflow: hidden;
position: relative;
display: inline-block;
line-height: 1;
outline: none;
text-decoration: none;
}
.link--mallki:hover {
-webkit-transition: none;
transition: none;
color: transparent;
}
.link--mallki::before {
content: '';
width: 100%;
height: 6px;
margin: -3px 0 0 0;
background: #3888fa;
position: absolute;
left: 0;
top: 50%;
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
-webkit-transition: -webkit-transform 0.4s;
transition: transform 0.4s;
-webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
}
.link--mallki:hover::before {
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);
}
.link--mallki span {
position: absolute;
height: 50%;
width: 100%;
left: 0;
top: 0;
overflow: hidden;
}
.link--mallki span::before {
content: attr(data-letters);
color: red;
position: absolute;
left: 0;
width: 100%;
color: #3888fa;
-webkit-transition: -webkit-transform 0.5s;
transition: transform 0.5s;
}
.link--mallki span:nth-child(2) {
top: 50%;
}
.link--mallki span:first-child::before {
top: 0;
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
.link--mallki span:nth-child(2)::before {
bottom: 0;
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
.link--mallki:hover span::before {
-webkit-transition-delay: 0.3s;
transition-delay: 0.3s;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
}
</style>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,176 @@
<template>
<el-color-picker
v-model="theme"
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
</template>
<script>
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
export default {
data() {
return {
chalk: '', // content of theme-chalk css
theme: ''
}
},
computed: {
defaultTheme() {
return this.$store.state.settings.theme
}
},
watch: {
defaultTheme: {
handler: function(val, oldVal) {
this.theme = val
},
immediate: true
},
async theme(val) {
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
console.log(themeCluster, originalCluster)
const $message = this.$message({
message: ' Compiling the theme',
customClass: 'theme-message',
type: 'success',
duration: 0,
iconClass: 'el-icon-loading'
})
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}
if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
// const url = require('element-ui/lib/theme-chalk/index.css')
await this.getCSSString(url, 'chalk')
}
const chalkHandler = getHandler('chalk', 'chalk-style')
chalkHandler()
const styles = [].slice.call(document.querySelectorAll('style'))
.filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})
this.$emit('change', val)
$message.close()
}
},
methods: {
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},
getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
if (tint === 0) { // when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
red += Math.round(tint * (255 - red))
green += Math.round(tint * (255 - green))
blue += Math.round(tint * (255 - blue))
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
}
const shadeColor = (color, shade) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
red = Math.round((1 - shade) * red)
green = Math.round((1 - shade) * green)
blue = Math.round((1 - shade) * blue)
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
const clusters = [theme]
for (let i = 0; i <= 9; i++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
}
}
}
</script>
<style>
.theme-message,
.theme-picker-dropdown {
z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
height: 26px !important;
width: 26px !important;
padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
}
</style>

View File

@@ -0,0 +1,116 @@
<template>
<div class="upload-container">
<el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">
{{ 'btn.uploadpic' | i18nFilter }}
</el-button>
<el-dialog :visible.sync="dialogVisible">
<el-upload
:multiple="true"
:file-list="fileList"
:data="dataObj"
:show-file-list="true"
:on-remove="handleRemove"
:on-success="handleSuccess"
:before-upload="beforeUpload"
class="editor-slide-upload"
:action="uploadPath"
list-type="picture-card"
>
<el-button size="small" type="primary">
{{ 'btn.upload' | i18nFilter }}
</el-button>
</el-upload>
<el-button @click="dialogVisible = false">
{{ 'btn.cancel' | i18nFilter }}
</el-button>
<el-button type="primary" @click="handleSubmit">
{{ 'btn.confirm' | i18nFilter }}
</el-button>
</el-dialog>
</div>
</template>
<script>
// import { getToken } from 'api/qiniu'
import { uploadPath } from '@/api/basic'
export default {
name: 'EditorSlideUpload',
props: {
color: {
type: String,
default: '#1890ff'
}
},
data() {
return {
dialogVisible: false,
listObj: {},
uploadPath,
fileList: [],
dataObj: { typeCode: 'file' }
}
},
methods: {
checkAllSuccess() {
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
},
handleSubmit() {
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
if (!this.checkAllSuccess()) {
this.$message('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!')
return
}
this.$emit('successCBK', arr)
this.listObj = {}
this.fileList = []
this.dialogVisible = false
},
handleSuccess(response, file) {
console.log(response)
console.log(file)
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
this.listObj[objKeyArr[i]].url = response.files.file
this.listObj[objKeyArr[i]].hasSuccess = true
return
}
}
},
handleRemove(file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
delete this.listObj[objKeyArr[i]]
return
}
}
},
beforeUpload(file) {
const _self = this
const _URL = window.URL || window.webkitURL
const fileName = file.uid
this.listObj[fileName] = {}
return new Promise((resolve, reject) => {
const img = new Image()
img.src = _URL.createObjectURL(file)
img.onload = function() {
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
}
resolve(true)
})
}
}
}
</script>
<style lang="scss" scoped>
.editor-slide-upload {
margin-bottom: 20px;
::v-deep .el-upload--picture-card {
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,59 @@
let callbacks = []
function loadedTinymce() {
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
// check is successfully downloaded script
return window.tinymce
}
const dynamicLoadScript = (src, callback) => {
const existingScript = document.getElementById(src)
const cb = callback || function() {}
if (!existingScript) {
const script = document.createElement('script')
script.src = src // src url for the third-party library being loaded.
script.id = src
document.body.appendChild(script)
callbacks.push(cb)
const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
onEnd(script)
}
if (existingScript && cb) {
if (loadedTinymce()) {
cb(null, existingScript)
} else {
callbacks.push(cb)
}
}
function stdOnEnd(script) {
script.onload = function() {
// this.onload = null here is necessary
// because even IE9 works not like others
this.onerror = this.onload = null
for (const cb of callbacks) {
cb(null, script)
}
callbacks = null
}
script.onerror = function() {
this.onerror = this.onload = null
cb(new Error('Failed to load ' + src), script)
}
}
function ieOnEnd(script) {
script.onreadystatechange = function() {
if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
this.onreadystatechange = null
for (const cb of callbacks) {
cb(null, script) // there is no way to catch loading errors in IE8
}
callbacks = null
}
}
}
export default dynamicLoadScript

View File

@@ -0,0 +1,243 @@
<template>
<div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
<textarea :id="tinymceId" class="tinymce-textarea" />
<div class="editor-custom-btn-container">
<editorImage v-show="false" color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
</div>
</div>
</template>
<script>
/**
* docs:
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
*/
import editorImage from './components/EditorImage'
import plugins from './plugins'
import toolbar from './toolbar'
import load from './dynamicLoadScript'
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
// const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
const tinymceCDN = './tinymce/tinymce.min.js' // updated at : 2022.5.20
// const tinymceCDN = 'https://cdn.bootcdn.net/ajax/libs/tinymce/6.0.2/tinymce.min.js' // updated at : 2022.5.20
export default {
name: 'Tinymce',
components: { editorImage },
props: {
id: {
type: String,
default: function() {
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
value: {
type: String,
default: ''
},
toolbar: {
type: Array,
required: false,
default() {
return []
}
},
menubar: {
type: String,
default: 'file edit insert view format table'
},
height: {
type: [Number, String],
required: false,
default: 360
},
width: {
type: [Number, String],
required: false,
default: 'auto'
}
},
data() {
return {
hasChange: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
'en': 'en',
'zh': 'zh_CN',
'es': 'es_MX',
'ja': 'ja'
}
}
},
computed: {
containerWidth() {
const width = this.width
if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
return `${width}px`
}
return width
}
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || ''))
}
}
},
mounted() {
this.init()
},
activated() {
if (window.tinymce) {
this.initTinymce()
}
},
deactivated() {
this.destroyTinymce()
},
destroyed() {
this.destroyTinymce()
},
methods: {
init() {
// dynamic load tinymce from cdn
load(tinymceCDN, (err) => {
if (err) {
this.$message.error(err.message)
return
}
this.initTinymce()
})
},
initTinymce() {
const _this = this
window.tinymce.init({
selector: `#${this.tinymceId}`,
language: this.languageTypeList['zh'],
height: this.height,
body_class: 'panel-body ',
object_resizing: false,
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
end_container_on_empty_block: true,
powerpaste_word_import: 'clean',
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: 'square',
advlist_number_styles: 'default',
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
default_link_target: '_blank',
link_title: false,
nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
init_instance_callback: editor => {
if (_this.value) {
editor.setContent(_this.value)
}
_this.hasInit = true
editor.on('NodeChange Change KeyUp SetContent', () => {
this.hasChange = true
this.$emit('input', editor.getContent())
})
},
setup(editor) {
editor.on('FullscreenStateChanged', (e) => {
_this.fullscreen = e.state
})
},
// it will try to keep these URLs intact
// https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
// https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
convert_urls: false
// 整合七牛上传
// images_dataimg_filter(img) {
// setTimeout(() => {
// const $image = $(img);
// $image.removeAttr('width');
// $image.removeAttr('height');
// if ($image[0].height && $image[0].width) {
// $image.attr('data-wscntype', 'image');
// $image.attr('data-wscnh', $image[0].height);
// $image.attr('data-wscnw', $image[0].width);
// $image.addClass('wscnph');
// }
// }, 0);
// return img
// },
// images_upload_handler(blobInfo, success, failure, progress) {
// progress(0);
// const token = _this.$store.getters.token;
// getToken(token).then(response => {
// const url = response.data.qiniu_url;
// const formData = new FormData();
// formData.append('token', response.data.qiniu_token);
// formData.append('key', response.data.qiniu_key);
// formData.append('file', blobInfo.blob(), url);
// upload(formData).then(() => {
// success(url);
// progress(100);
// })
// }).catch(err => {
// failure('出现未知问题,刷新页面,或者联系程序员')
// console.log(err);
// });
// },
})
},
destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId)
if (this.fullscreen) {
tinymce.execCommand('mceFullScreen')
}
if (tinymce) {
tinymce.destroy()
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value)
},
getContent() {
window.tinymce.get(this.tinymceId).getContent()
},
imageSuccessCBK(arr) {
const _this = this
arr.forEach(v => {
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)
})
}
}
}
</script>
<style scoped>
.tinymce-container {
position: relative;
line-height: normal;
}
.tinymce-container>>>.mce-fullscreen {
z-index: 10000;
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 4px;
top: 4px;
/*z-index: 2005;*/
}
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
}
.editor-upload-btn {
display: inline-block;
}
</style>

View File

@@ -0,0 +1,7 @@
// Any plugins you want to use has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/
const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
export default plugins

View File

@@ -0,0 +1,6 @@
// Here is a list of the toolbar
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
export default toolbar

Some files were not shown because too many files have changed in this diff Show More