This commit is contained in:
张成
2025-10-08 15:10:33 +08:00
commit 2e1cd65b07
161 changed files with 19936 additions and 0 deletions

26
.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# Dependencies
node_modules/
# Build output
dist/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# OS
.DS_Store
Thumbs.db

15
babel.config.js Normal file
View File

@@ -0,0 +1,15 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
modules: false,
targets: {
browsers: ['>1%', 'last 2 versions', 'not ie <= 8']
}
}
],
'@vue/babel-preset-jsx'
]
}

76
build.bat Normal file
View File

@@ -0,0 +1,76 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo ================================
echo Admin Framework 构建工具
echo ================================
echo.
REM 检查 Node.js 是否安装
where node >nul 2>nul
if %errorlevel% neq 0 (
echo ❌ 错误: 未检测到 Node.js请先安装 Node.js
pause
exit /b 1
)
for /f "tokens=*" %%i in ('node -v') do set NODE_VERSION=%%i
for /f "tokens=*" %%i in ('npm -v') do set NPM_VERSION=%%i
echo ✅ Node.js 版本: %NODE_VERSION%
echo ✅ NPM 版本: %NPM_VERSION%
echo.
REM 检查是否已安装依赖
if not exist "node_modules" (
echo 📦 正在安装依赖...
call npm install
if !errorlevel! neq 0 (
echo ❌ 依赖安装失败
pause
exit /b 1
)
echo ✅ 依赖安装成功
echo.
) else (
echo ✅ 依赖已安装
echo.
)
REM 执行打包
echo 🔨 正在打包框架...
call npm run build
if %errorlevel% equ 0 (
echo.
echo ================================
echo ✅ 打包成功!
echo ================================
echo.
echo 📦 输出文件: dist\admin-framework.js
REM 显示文件大小
if exist "dist\admin-framework.js" (
for %%A in (dist\admin-framework.js) do (
set SIZE=%%~zA
set /a SIZE_KB=!SIZE! / 1024
echo 📊 文件大小: !SIZE_KB! KB
)
)
echo.
echo 📚 下一步:
echo 1. 将 dist\admin-framework.js 复制到你的项目
echo 2. 查看 QUICK_START.md 了解如何使用
echo 3. 查看 USAGE_EXAMPLE.md 了解详细示例
echo.
) else (
echo.
echo ❌ 打包失败,请检查错误信息
pause
exit /b 1
)
pause

65
build.sh Normal file
View File

@@ -0,0 +1,65 @@
#!/bin/bash
# Admin Framework 构建脚本
echo "================================"
echo " Admin Framework 构建工具"
echo "================================"
echo ""
# 检查 Node.js 是否安装
if ! command -v node &> /dev/null
then
echo "❌ 错误: 未检测到 Node.js请先安装 Node.js"
exit 1
fi
echo "✅ Node.js 版本: $(node -v)"
echo "✅ NPM 版本: $(npm -v)"
echo ""
# 检查是否已安装依赖
if [ ! -d "node_modules" ]; then
echo "📦 正在安装依赖..."
npm install
if [ $? -ne 0 ]; then
echo "❌ 依赖安装失败"
exit 1
fi
echo "✅ 依赖安装成功"
echo ""
else
echo "✅ 依赖已安装"
echo ""
fi
# 执行打包
echo "🔨 正在打包框架..."
npm run build
if [ $? -eq 0 ]; then
echo ""
echo "================================"
echo " ✅ 打包成功!"
echo "================================"
echo ""
echo "📦 输出文件: dist/admin-framework.js"
# 显示文件大小
if [ -f "dist/admin-framework.js" ]; then
SIZE=$(du -h dist/admin-framework.js | cut -f1)
echo "📊 文件大小: $SIZE"
fi
echo ""
echo "📚 下一步:"
echo " 1. 将 dist/admin-framework.js 复制到你的项目"
echo " 2. 查看 QUICK_START.md 了解如何使用"
echo " 3. 查看 USAGE_EXAMPLE.md 了解详细示例"
echo ""
else
echo ""
echo "❌ 打包失败,请检查错误信息"
exit 1
fi

4861
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

49
package.json Normal file
View File

@@ -0,0 +1,49 @@
{
"name": "admin-framework",
"version": "1.0.0",
"description": "通用后台管理系统框架 - 包含系统功能、登录、路由管理、布局等",
"main": "dist/admin-framework.js",
"scripts": {
"build": "webpack --mode production",
"dev": "webpack --mode development --watch"
},
"keywords": [
"admin",
"framework",
"vue",
"management-system"
],
"author": "",
"license": "MIT",
"peerDependencies": {
"axios": "^0.21.0",
"view-design": "^4.0.0",
"vue": "^2.6.0",
"vue-router": "^3.0.0",
"vuex": "^3.0.0"
},
"dependencies": {
"@vue/babel-preset-jsx": "^1.4.0",
"brace": "^0.11.1",
"dayjs": "^1.10.0",
"js-cookie": "^2.2.1",
"vue2-ace-editor": "^0.0.15",
"vuex-persistedstate": "^4.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.0",
"@babel/preset-env": "^7.12.0",
"babel-loader": "^8.2.0",
"css-loader": "^5.0.0",
"file-loader": "^6.2.0",
"less": "^4.0.0",
"less-loader": "^7.0.0",
"style-loader": "^2.0.0",
"url-loader": "^4.1.0",
"vue-loader": "^15.9.0",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.6.0",
"webpack": "^5.0.0",
"webpack-cli": "^4.0.0"
}
}

View File

@@ -0,0 +1,15 @@
import http from "@/utils/http";
class FileServe {
async upload_oos_img(row) {
let res = await http.postFormData("/sys_file/upload_oos_img", row);
return res;
}
async upload_Img(row) {
let res = await http.postFormData("/file/upload_Img", row);
return res;
}
}
const fileServe = new FileServe();
export default fileServe;

View File

@@ -0,0 +1,51 @@
import http from '@/utils/http';
/**
* 热门城市二维码配置相关API
*/
// 获取配置列表
const getHotCityQrConfigList = (params) => {
return http.get('/wch_hot_city_qr_config/list', { params });
};
// 获取所有配置
const getAllHotCityQrConfig = () => {
return http.get('/wch_hot_city_qr_config/all');
};
// 获取配置详情
const getHotCityQrConfigDetail = (id) => {
return http.get(`/wch_hot_city_qr_config/detail/${id}`);
};
// 创建配置
const createHotCityQrConfig = (data) => {
return http.post('/wch_hot_city_qr_config/create', data);
};
// 更新配置
const updateHotCityQrConfig = (id, data) => {
return http.post(`/wch_hot_city_qr_config/update/${id}`, data);
};
// 删除配置
const deleteHotCityQrConfig = (id) => {
return http.post(`/wch_hot_city_qr_config/delete/${id}`);
};
// 批量删除配置
const batchDeleteHotCityQrConfig = (ids) => {
return http.post('/wch_hot_city_qr_config/batch_delete', { ids });
};
export default {
getHotCityQrConfigList,
getAllHotCityQrConfig,
getHotCityQrConfigDetail,
createHotCityQrConfig,
updateHotCityQrConfig,
deleteHotCityQrConfig,
batchDeleteHotCityQrConfig
};

17
src/api/system/index.js Normal file
View File

@@ -0,0 +1,17 @@
// 系统 API 统一导出
export { default as fileServe } from './fileServe'
export { default as hotCityQrConfigServer } from './hot_city_qr_config_server'
export { default as plaAccountServer } from './pla_account_server'
export { default as rolePermissionServer } from './rolePermissionServer'
export { default as roleServer } from './roleServer'
export { default as shpProfitServer } from './shpProfitServer'
export { default as specificationServer } from './specificationServer'
export { default as sysAddressServer } from './sysAddressServer'
export { default as sysModuleServer } from './sysModuleServer'
export { default as sysLogServe } from './sys_log_serve'
export { default as systemTypeServer } from './systemType_server'
export { default as tableServer } from './tableServer'
export { default as userServer } from './userServer'
export { default as wchProfessionsServer } from './wch_professions_server'

View File

@@ -0,0 +1,38 @@
import http from '@/utils/http'
export default {
// 获取所有平台账户
all(param) {
return http.get('/pla_account/all', param)
},
// 获取平台账户详情
detail(param) {
return http.get('/pla_account/detail', param)
},
// 分页查询平台账户
page(param) {
return http.post('/pla_account/page', param)
},
// 新增平台账户
add(param) {
return http.post('/pla_account/add', param)
},
// 编辑平台账户
edit(param) {
return http.post('/pla_account/edit', param)
},
// 删除平台账户
del(param) {
return http.post('/pla_account/del', param)
},
// 导出平台账户数据
exportCsv(param) {
return http.post('/pla_account/export', param)
}
}

View File

@@ -0,0 +1,30 @@
import http from '@/utils/http'
class RolePermissionServer {
async getRoles(callback) {
let res = await http.get('/SysRolePermission/Query', {})
return res
}
async getRole(row) {
let res = await http.get('/SysRolePermission/QueryByRoleId', row)
return res
}
async add(row) {
let res = await http.post('/SysRolePermission/add', row)
return res
}
async edit(row) {
let res = await http.post('/SysRolePermission/edit', row)
return res
}
async del(row) {
let res = await http.post('/SysRolePermission/del', row)
return res
}
}
const rolePermissionServer = new RolePermissionServer()
export default rolePermissionServer

View File

@@ -0,0 +1,26 @@
import http from "@/utils/http";
class RoleServer {
async list() {
let res = await http.get("/sys_role/index", {});
return res;
}
async add(row) {
let res = await http.post("/sys_role/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_role/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_role/del", row);
return res;
}
}
const roleServer = new RoleServer();
export default roleServer;

View File

@@ -0,0 +1,15 @@
import http from '@/utils/http'
class ShpProfitServer {
async report(param) {
let res = await http.post('/shpProfit/report', param)
return res
}
async list(param) {
let res = await http.post('/shpProfit/list', param)
return res
}
}
const shpProfitServer = new ShpProfitServer()
export default shpProfitServer

View File

@@ -0,0 +1,25 @@
import http from '@/utils/http'
class SpecificationServer {
async list() {
let res = await http.post('/specification/list', {})
return res
}
async add(row) {
let res = await http.post('/specification/add', row)
return res
}
async edit(row) {
let res = await http.post('/specification/edit', row)
return res
}
async del(row) {
let res = await http.post('/specification/del', row)
return res
}
}
const specificationServer = new SpecificationServer()
export default specificationServer

View File

@@ -0,0 +1,10 @@
import http from "@/utils/http";
class SysAddress {
async index(param) {
let res = await http.get("/sys_address/index", param);
return res;
}
}
const sysAddress = new SysAddress();
export default sysAddress;

View File

@@ -0,0 +1,30 @@
import http from "@/utils/http";
class SysModuleServer {
async all() {
let res = await http.get("/sys_menu/all", {});
return res;
}
async list(row) {
let res = await http.get("/sys_menu/all", row);
return res;
}
async add(row) {
let res = await http.post("/sys_menu/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_menu/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_menu/del", row);
return res;
}
}
const sysModuleServer = new SysModuleServer();
export default sysModuleServer;

View File

@@ -0,0 +1,30 @@
import http from "@/utils/http";
class SysLogServe {
async all(param) {
let res = await http.get("/sys_log/all", param);
return res;
}
async detail(param) {
let res = await http.get("/sys_log/detail", param);
return res;
}
async delete(param) {
let res = await http.get("/sys_log/delete", param);
return res;
}
async delete_all(param) {
let res = await http.get("/sys_log/delete_all", param);
return res;
}
async operates(param) {
let res = await http.get("/sys_log/operates", param);
return res;
}
}
const sys_log_serve = new SysLogServe();
export default sys_log_serve;

View File

@@ -0,0 +1,38 @@
import http from '@/utils/http';
class systemTypeClServer {
async all(param) {
let res= await http.get('/sys_project_type/all', param);
return res;
}
async page(row) {
let res= await http.post('/sys_project_type/page', row);
return res;
}
async exportCsv(row) {
let res = http.fileExport("/sys_project_type/export", row);
return res;
}
async add(row) {
let res= await http.post('/sys_project_type/add', row);
return res;
}
async edit(row) {
let res= await http.post('/sys_project_type/edit', row);
return res;
}
async del(row) {
let res= await http.post('/sys_project_type/del', row);
return res;
}
}
const systemTypeServer = new systemTypeClServer();
export default systemTypeServer;

View File

@@ -0,0 +1,31 @@
import http from '@/utils/http'
class TableServer {
async getAll(callback) {
return await http.get('/table/index', {})
}
async add(row, callback) {
return await http.post('/table/add', row)
}
async edit(row, callback) {
return await http.post('/table/edit', row, function(res) {
callback && callback(res)
})
}
async del(row, callback) {
return await http.post('/table/del', row)
}
async autoApi(id) {
return await http.get('/template/api', { id: id })
}
async autoDb(id) {
return await http.get('/template/autoDb', { id: id })
}
}
const tableServer = new TableServer()
export default tableServer

View File

@@ -0,0 +1,40 @@
import http from "@/utils/http";
class UserServer {
async login(row) {
let res = await http.post("/sys_user/login", row);
return res;
}
async all() {
let res = await http.get("/sys_user/index", {});
return res;
}
async exportCsv(row) {
let res = http.fileExport("/sys_user/export", row);
return res;
}
async authorityMenus() {
let res = await http.post("/sys_user/authorityMenus", {});
return res;
}
async add(row) {
let res = await http.post("/sys_user/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_user/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_user/del", row);
return res;
}
}
const userServer = new UserServer();
export default userServer;

View File

@@ -0,0 +1,33 @@
import http from '@/utils/http'
export default {
// 获取职业列表
page: (params) => {
return http.post('/wch_professions/page', params)
},
// 获取所有职业
all: (params) => {
return http.post('/wch_professions/all', params)
},
// 新增职业
add: (params) => {
return http.post('/wch_professions/add', params)
},
// 更新职业
edit: (params) => {
return http.post('/wch_professions/edit', params)
},
// 删除职业
del: (params) => {
return http.post('/wch_professions/del', params)
},
// 导出职业数据
exportCsv: (params) => {
return http.post('/wch_professions/exportCsv', params)
}
}

View File

@@ -0,0 +1,30 @@
import http from "@/utils/http";
class FormFieldServer {
async all(param) {
let res = await http.get("/sys_form_field/all", param);
return res;
}
async page(row) {
let res = await http.post("/sys_form_field/page", row);
return res;
}
async add(row) {
let res = await http.post("/sys_form_field/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_form_field/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_form_field/del", row);
return res;
}
}
const formFieldServer = new FormFieldServer();
export default formFieldServer;

View File

@@ -0,0 +1,35 @@
import http from "@/utils/http";
class FormServer {
async all(param) {
let res = await http.get("/sys_form/all", param);
return res;
}
async page(row) {
let res = await http.post("/sys_form/page", row);
return res;
}
async generate(row) {
let res = await http.post("/sys_form/generate", row);
return res;
}
async add(row) {
let res = await http.post("/sys_form/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_form/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_form/del", row);
return res;
}
}
const formServer = new FormServer();
export default formServer;

View File

@@ -0,0 +1,10 @@
// 高级系统 API 统一导出
export { default as formFieldServer } from './formFieldServer'
export { default as formServer } from './formServer'
export { default as menuServer } from './menuServer'
export { default as modelFieldServer } from './modelFieldServer'
export { default as modelServer } from './modelServer'
export { default as paramSetupServer } from './paramSetupServer'
export { default as sysControlTypeServer } from './sysControlTypeServer'

View File

@@ -0,0 +1,49 @@
import http from "@/utils/http";
class MenuServer {
async list(row) {
let res = await http.get("/sys_menu/index", row);
return res;
}
async generate(row) {
let res = await http.post("/sys_menu/generate", row);
return res;
}
async add(row) {
let res = await http.post("/sys_menu/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_menu/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_menu/del", row);
return res;
}
async generate(row) {
let res = await http.post("/form/generate", row);
return res;
}
async generateModel(row) {
let res = await http.post("/model/generate", row);
return res;
}
async modelAll(row) {
let res = await http.post("/model/all", row);
return res;
}
async modelInterface(row) {
let res = await http.post("/model/interface", row);
return res;
}
}
const menuServer = new MenuServer();
export default menuServer;

View File

@@ -0,0 +1,32 @@
import http from "@/utils/http";
class ModelFieldServer {
async all(row) {
let res = await http.get("/sys_model_field/all", row);
return res;
}
async allByKey(row) {
let res = await http.get("/sys_model_field/allByKey", row, {
hideLoad: true
});
return res;
}
async add(row) {
let res = await http.post("/sys_model_field/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_model_field/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_model_field/del", row);
return res;
}
}
const modelFieldServer = new ModelFieldServer();
export default modelFieldServer;

View File

@@ -0,0 +1,40 @@
import http from "@/utils/http";
class ModelServer {
async interface(row) {
let res = await http.post("/sys_model/interface", row);
return res;
}
async all() {
let res = await http.get("/sys_model/all", {});
return res;
}
async detail(row) {
let res = await http.get("/sys_model/detail", row);
return res;
}
async regenerate(row) {
let res = await http.post("/sys_model/regenerate", row);
return res;
}
async add(row) {
let res = await http.post("/sys_model/add", row);
return res;
}
async edit(row) {
let res = await http.post("/sys_model/edit", row);
return res;
}
async del(row) {
let res = await http.post("/sys_model/del", row);
return res;
}
}
const modelServer = new ModelServer();
export default modelServer;

View File

@@ -0,0 +1,29 @@
import http from "@/utils/http";
class ParamSetupServer {
async getAll() {
return await http.get("/sys_parameter/index", {});
}
async getOne(key) {
return await http.get("/sys_parameter/key", { key });
}
async add(row) {
return await http.post("/sys_parameter/add", row);
}
async edit(row) {
return await http.post("/sys_parameter/edit", row);
}
async setSysConfig(row) {
return await http.post("/sys_parameter/setSysConfig", row);
}
async del(row) {
return await http.post("/sys_parameter/del", row);
}
}
const paramSetupServer = new ParamSetupServer();
export default paramSetupServer;

View File

@@ -0,0 +1,29 @@
import http from "@/utils/http";
class SysControlTypeServer {
async all(param) {
let res = await http.get("/sys_control_type/all", param);
return res;
}
async page(row) {
let res = await http.post("/sys_control_type/page", row);
return res;
}
async add(param) {
let res = await http.post("/sys_control_type/add", param);
return res;
}
async edit(param) {
let res = await http.post("/sys_control_type/edit", param);
return res;
}
async del(param) {
let res = await http.post("/sys_control_type/del", param);
return res;
}
}
const sysControlTypeServer = new SysControlTypeServer();
export default sysControlTypeServer;

3688
src/assets/css/animate.css vendored Normal file

File diff suppressed because it is too large Load Diff

149
src/assets/css/base.less Normal file
View File

@@ -0,0 +1,149 @@
body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
code,
form,
fieldset,
legend,
input,
button,
textarea,
p,
blockquote,
th,
td {
margin: 0;
padding: 0;
}
body {
background: #fff;
color: #495060;
font-size: 14px;
font-family: Verdana, Arial, Helvetica, sans-serif;
}
td,
th,
caption {
font-size: 14px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: normal;
font-size: 100%;
}
a {
color: #495060;
text-decoration: none;
font-weight: bold;
}
a:hover {
text-decoration: underline;
}
img {
border: none;
}
ol,
ul,
li {
list-style: none;
}
input,
textarea,
select,
button {
font: 14px Verdana, Helvetica, Arial, sans-serif;
}
html {
overflow-y: auto;
}
/*滚动条整体样式*/
*::-webkit-scrollbar {
width: 10px;
/*高宽分别对应横竖滚动条的尺寸*/
height: 10px;
}
*::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 5px;
-webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.1);
background: #9ca6bb;
}
*::-webkit-scrollbar-track {
/*滚动条里面轨道*/
-webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.1);
border-radius: 5px;
background: #dcdee2;
}
.ml(@i) when(@i <= 300) {
.ml@{i} {
margin-left: @i + 0px;
}
.ml((@i + 5));
}
.ml(5);
.mt(@i) when(@i <= 100) {
.mt@{i} {
margin-top: @i + 0px;
}
.mt((@i + 5));
}
.mt(5);
.pa(@i) when(@i <= 100) {
.pa@{i} {
padding: @i + 0px;
}
.pa((@i + 5));
}
.pa(5);
.w(@i) when(@i <= 100) {
.w@{i} {
width: @i + 0%;
}
.w((@i + 5));
}
.w(5);
.h(@i) when(@i <= 100) {
.h@{i} {
height: @i + 0%;
}
.h((@i + 5));
}
.h(5);

View File

@@ -0,0 +1,322 @@
.ace_print-margin-layer {
display: none;
}
.ivu-select-dropdown {
z-index: 999999;
}
.ivu-select-dropdown-list {
height: 200px !important;
overflow: auto;
}
.float-right {
float: right;
margin-right: 10px;
}
.ivu-tooltip-inner {
background: #fff;
color: #495060;
}
.flex {
display: flex;
flex: 1;
align-items: center;
}
.flex-left {
justify-content: flex-start;
flex-direction: row;
}
.flex-right {
justify-content: flex-end;
margin-right: 10px;
}
.ivu-layout-header {
background-color: #f5f7f9;
padding: 0px 0.05rem;
}
.mt10 {
margin-top: 10px;
}
.mt20 {
margin-top: 20px;
}
.mt30 {
margin-top: 30px;
}
.ml5 {
margin-left: 5px;
}
.ml10 {
margin-left: 10px;
}
.ml20 {
margin-left: 20px;
}
.ml30 {
margin-left: 30px;
}
.ml100 {
margin-left: 100px;
}
.pa-5 {
padding: 0.05rem;
}
.pa-10 {
padding: 0.1rem;
}
.pa-20 {
padding: 0.2rem;
}
.pa-30 {
padding: 0.3rem;
}
.red {
color: red;
}
.buld {
color: green;
}
.bold {
font-weight: bold;
}
.float-right {
float: right;
}
.chart-element {
width: 100%;
height: 100%;
flex: 1;
flex-direction: column;
}
.table-warp {
.ivu-card-body {
height: 100%;
display: flex;
flex-direction: column;
}
}
.w-e-text-container {
min-height: 800px !important;
}
.ivu-card-body {
padding: 5px;
}
.ivu-btn {
margin: 0px 5px;
}
.head-tool-box {
padding: 5px;
background-color: #dcdee2;
width: 100%;
}
.h50 {
height: 0.5rem;
line-height: 0.5rem;
}
.mr10 {
margin-right: 10px;
}
.content-view {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
background-color: #fff;
padding: 5px;
height: 100%;
overflow: hidden;
.table-head-tool {
width: 100%;
padding: 10px;
line-height: 35px;
display: flex;
justify-content: space-between;
flex-shrink: 0;
.ivu-form-item {
margin-bottom: 5px;
}
}
.table-body {
display: flex;
flex-direction: column;
width: 100%;
flex: 1;
overflow: hidden;
min-height: 0;
}
}
.ivu-table-cell {
padding-left: 5px;
padding-right: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 自适应表格宽度 */
.ivu-table-wrapper {
width: 100%;
height: 100%;
overflow-x: auto;
overflow-y: auto;
flex: 1;
display: flex;
flex-direction: column;
}
.ivu-table {
width: 100% !important;
flex: 1;
}
/* 移除 table-layout: auto使用 iView 默认的 fixed 布局以确保列对齐 */
/* .ivu-table table {
table-layout: auto;
} */
.ivu-table-body {
overflow-y: auto;
flex: 1;
}
/* 按钮风格一致 */
.ivu-btn {
border-radius: 4px;
}
.ivu-btn + .ivu-btn {
margin-left: 8px;
}
/* 常用按钮容器(可选) */
.btn-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.arrowBox {
cursor: pointer;
position: absolute;
width: 13px;
height: 50px;
margin-top: -25px;
top: 49%;
right: 1px;
background-color: #043250;
border-radius: 4px 0 0 4px;
overflow: hidden;
.ivu-icon {
position: relative;
font-size: 36px;
color: #515a6e;
top: 8px;
left: -12px;
}
}
.ivu-badge-count {
top: 3px;
}
.datetime-box {
.ivu-date-picker-cells-cell:not(.ivu-date-picker-cells-cell-disabled) {
background-color: rgba(10, 255, 0, 0.3);
color: #000000;
}
}
.sub-title {
font-size: 20px;
font-weight: bold;
}
.tip-info {
color: #ff9900;
font-size: 12px;
}
.tc {
text-align: center;
}
.seach-input {
width: 300px;
float: right;
margin: 10px;
}
.item-right {
position: absolute;
right: 10px;
}
.sort-num {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
i {
margin: 5px;
font-size: 20px;
cursor: pointer;
&.ivu-icon-md-arrow-up {
color: green;
}
&.ivu-icon-md-arrow-down {
color: indianred;
}
}
}
.view-log-info
{
background-color: #292A2A;
color: #fff;
line-height: 30px;
padding: 5px;
white-space: pre-wrap;
overflow:auto;
}

View File

@@ -0,0 +1,37 @@
@font-face {font-family: "iconfont";
src: url('iconfont.eot?t=1541579316141'); /* IE9*/
src: url('iconfont.eot?t=1541579316141#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAiEAAsAAAAADmgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8eUnXY21hcAAAAYAAAACjAAACLi+YJuBnbHlmAAACJAAABAgAAAcg4dRWHmhlYWQAAAYsAAAAMQAAADYTL8piaGhlYQAABmAAAAAgAAAAJAfdA4xobXR4AAAGgAAAABQAAAAsLAD//2xvY2EAAAaUAAAAGAAAABgImgpGbWF4cAAABqwAAAAfAAAAIAEcAG5uYW1lAAAGzAAAAUUAAAJtPlT+fXBvc3QAAAgUAAAAbgAAAI54roygeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMTx/ytzwv4EhhrmBoRkozAiSAwDuUwzMeJzlkUEKwkAMRd/YabXFhQvxFF6qPYPrUujGY7jyIr1JoZNjtMnEhag3MOEN5MMk8D9QAoVyVSKEJwGrh6oh6wVN1iM3nc+cVImJVKdOehlklElmWdYVstp+ql8VdIv15a1NLW0zFXsO7Kjz3erH/3+rY37vr6kxnx1LKNWOJZlaxxJNnWOpSu+ot8jgqMvI6KjfyOSo88jsaAbI4tBsig89rQB4nLVUTWwbRRSeNzO767i2g7N/FP9s7MRrE5ON4/V6rSZyU0PiINSSNImES4IUoapWz6hEiqiMBDQqEojkAkiFStyKRC+9VSoFCeUEyqESVUAqEkcu3OAQb3hrJxAXwSGI3X0/szPz5vvm2x0i7O/vf8IJe5VkSJnUyUtklRBQJE1VIjRtUafkmk6pSu2ipleh4+xikkKxSksWTUeo8m8NoagpYtoslTmxrLl37z64e33esuJjU8P5Wd262LxoPVnPZ06Pxfe+C0YjkhSJygPhQCA8ABPOykwuN7NyuRvgUnAgLEnhATkaCQQiUe/7XKUyV6nQz+t2o7l66+rs7NVbq82GXTdrdjxjRGU5amTids2bUDMFtzCsqsMYMqr3IDY6OT05GjsI8Exv/6CSkOWEQigh+y3clxY5QVTcEZFIGtHLxDUJs6WsHR1y9SFKdr1HggCp3V1ICYL36OOpVmvKN9bC1u6R3vZ0qwWtVovgJfqOfUvfIYxIWL+fyETHNVJqSkIT1JTjW8ZWh3yDJDz0ctvsyt51etvrg9/QHhqGlzMM+vbmizPnDWPLMNbW19e7tffvsBzL99aWEfBRY46t+tbe3PypXv/IMDYN43WsQBe9HL2NC33RuxABrPsG+xH3o4bVRE2KgCRqulbWNf8W/UYVHM129aKra24VshZkq+CWD/Oy6Xt8cGYEthgHVlVliCfynAlqjo6oysTKlYUAD4docMI5/1ZioN+GwZNBcTwWUmTdBUqhTwX29QebXzF4An4JJMzwfMl+WQ01+IlQZVR4yhie53ycA16pOI/ODiYNGK4MChdCgXNnX5gIJXPCSYnf2OF850aQ+zJIyOs+u8+mMO8jQdwtg1TIWVRjKAnFcslMi8KfGUPoSUCergUyUk77dMyS69Ms6tijKZKYwUGKbpfdzu+iYeZYAHMFiOVi+MD7h9mb99qC0L7X8c+XatMfTj97KZ5IxJt/pd43tYYQKEjAnXMOB6kQEBrwg+LPjindAPOHNdC3q3ait0I3/ZIunZEARLNYNEUA6czSP3N/7j9wz6ZESdX0VNl1zGNS/szbQaQSIGk4DtVPcZf8AgXpf9A2OyTit5s2syZmand46bhEe2WtodLHkvaoqtTXuXN2/c42WADP9HGfbUcUW7JgqHss4xHtlMys679FqUomdP9VJBQBdnlPABBubpuNwqnmQj6/0HwNQzKxDUJFgKiXurBG6dqFjmeBzsvtRPJgGIZThYa5fdOvsReOticPh6JHHXxsv7ItJpOniYPYsmZ/x0QD/o5P105DeQwF6MH33ogoLi+KQp7zpY3HQV5bFMURzheXeds7gpP+jKNXljjHuYvXHke7cdCxLLZf6YX7B63UcCV4nGNgZGBgAOKAN2ZR8fw2Xxm4WRhA4AbHYRMY/f///1oWBuYGIJeDgQkkCgAvWgs2AAAAeJxjYGRgYG7438AQw8Lw/z8DAwsDA1AEBXADAHXiBHJ4nGNhYGBgYfj/nwVM48cATwECKwAAAAAAjAC6AOgBFAGAAf4CbgLqAzgDkHicY2BkYGDgZkhiYGcAASYg5gJCBob/YD4DABOmAYsAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicbYhdDoIwEAb3a6k/YIIX8VArWewmdJFWJOnpJTG+OQ+TzJCjLy39p4ODR4OAA4444YwWHS7U3IVzn6Voldtb8ksHnvohrlqjjmw1rmzXsvdT7fEbblnCmOfNfJIYStJJfGIL27yb6AOCGR89AAA=') format('woff'),
url('iconfont.ttf?t=1541579316141') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('iconfont.svg?t=1541579316141#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family:"iconfont" !important;
font-size:16px;
font-style:normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-bear:before { content: "\e600"; }
.icon-resize-vertical:before { content: "\e7c3"; }
.icon-chuizhifanzhuan:before { content: "\e661"; }
.icon-shuipingfanzhuan:before { content: "\e662"; }
.icon-qq:before { content: "\e609"; }
.icon-frown:before { content: "\e77e"; }
.icon-meh:before { content: "\e780"; }
.icon-smile:before { content: "\e783"; }
.icon-man:before { content: "\e7e2"; }
.icon-woman:before { content: "\e7e5"; }

Binary file not shown.

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font id="iconfont" horiz-adv-x="1024" >
<font-face
font-family="iconfont"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="bear" unicode="&#58880;" d="M1024 683.008q0-70.656-46.08-121.856 46.08-89.088 46.08-193.536 0-96.256-39.936-181.248t-109.568-147.968-162.816-99.328-199.68-36.352-199.68 36.352-162.304 99.328-109.568 147.968-40.448 181.248q0 104.448 46.08 193.536-46.08 51.2-46.08 121.856 0 37.888 13.824 71.168t37.376 58.368 55.808 39.424 68.096 14.336q43.008 0 78.848-18.432t59.392-50.176q46.08 17.408 96.256 26.624t102.4 9.216 102.4-9.216 96.256-26.624q24.576 31.744 59.904 50.176t78.336 18.432q36.864 0 68.608-14.336t55.296-39.424 37.376-58.368 13.824-71.168zM205.824 268.288q10.24 0 18.944 10.24t15.36 28.672 10.24 42.496 3.584 51.712-3.584 51.712-10.24 41.984-15.36 28.16-18.944 10.24q-9.216 0-17.92-10.24t-15.36-28.16-10.752-41.984-4.096-51.712 4.096-51.712 10.752-42.496 15.36-28.672 17.92-10.24zM512-31.744000000000028q53.248 0 99.84 13.312t81.408 35.84 54.784 52.736 19.968 65.024q0 33.792-19.968 64t-54.784 52.736-81.408 35.84-99.84 13.312-99.84-13.312-81.408-35.84-54.784-52.736-19.968-64q0-34.816 19.968-65.024t54.784-52.736 81.408-35.84 99.84-13.312zM818.176 268.288q10.24 0 18.944 10.24t15.36 28.672 10.24 42.496 3.584 51.712-3.584 51.712-10.24 41.984-15.36 28.16-18.944 10.24q-9.216 0-17.92-10.24t-15.36-28.16-10.752-41.984-4.096-51.712 4.096-51.712 10.752-42.496 15.36-28.672 17.92-10.24zM512 235.51999999999998q39.936 0 68.096-9.728t28.16-24.064-28.16-24.064-68.096-9.728-68.096 9.728-28.16 24.064 28.16 24.064 68.096 9.728z" horiz-adv-x="1024" />
<glyph glyph-name="resize-vertical" unicode="&#59331;" d="M512 896C229.248 896 0 666.752 0 384s229.248-512 512-512 512 229.248 512 512S794.752 896 512 896zM576 192l64 0-128-128-128 128 64 0L448 576l-64 0 128 128 128-128-64 0L576 192z" horiz-adv-x="1024" />
<glyph glyph-name="chuizhifanzhuan" unicode="&#58977;" d="M286.01856 645.08416l472.4224 0 0-146.2784-472.4224 0 0 146.2784ZM87.19872 420.37248l885.80096 0 0-70.87104-885.80096 0 0 70.87104ZM773.55008 268.05248l0-31.0016L270.6688 237.05088l0 31.0016L773.55008 268.05248zM773.55008 121.4208l0-31.0016L270.6688 90.4192l0 31.0016L773.55008 121.4208zM742.54848 240.75776l31.0016 0 0-123.04896-31.0016 0L742.54848 240.75776zM270.70464 240.57856l31.0016 0 0-123.04896-31.0016 0L270.70464 240.57856z" horiz-adv-x="1024" />
<glyph glyph-name="shuipingfanzhuan" unicode="&#58978;" d="M252.76928 596.096l146.2784 0 0-472.42752-146.2784 0 0 472.42752ZM477.48096 810.65472l70.87104 0 0-885.80608-70.87104 0 0 885.80608ZM629.80096 611.2l31.0016 0 0-502.88128-31.0016 0L629.80096 611.2zM776.42752 611.2l31.0016 0 0-502.88128-31.0016 0L776.42752 611.2zM657.09056 580.1984l0 31.0016 123.04896 0 0-31.0016L657.09056 580.1984zM657.27488 108.35456l0 31.0016 123.04896 0 0-31.0016L657.27488 108.35456z" horiz-adv-x="1024" />
<glyph glyph-name="qq" unicode="&#58889;" d="M147.372058 491.394284c-5.28997-13.909921 2.431986-22.698872 0-75.732573-0.682996-14.25092-62.165649-78.762555-86.569511-145.791177-24.192863-66.517625-27.519845-135.978232 9.811944-163.285078 37.419789-27.305846 72.191593 90.879487 76.757567 73.685584 1.961989-7.509958 4.436975-15.317914 7.423958-23.338868a331.945126 331.945126 0 0 1 61.140655-101.162429c5.929967-6.783962-36.009797-19.199892-61.140655-61.99365-25.173858-42.751759 7.209959-120.49032 132.223254-120.49032 161.27909 0 197.288886 56.70368 200.574868 56.447681 12.031932-0.895995 12.841928 0 25.599855 0 15.572912 0 9.129948-1.279993 23.593867 0 7.807956 0.682996 86.186514-67.839617 194.686901-56.447681 184.873956 19.45589 156.586116 81.40754 142.079198 120.48932-15.103915 40.83277-68.692612 59.946662-66.303626 62.549647 44.28775 48.938724 51.285711 79.018554 66.346626 123.9463 6.143965 18.473896 49.066723-101.674426 82.089537-73.685584 13.781922 11.690934 41.301767 60.24566 13.781922 163.285078-27.519845 102.996419-80.767544 126.505286-79.615551 145.791177 2.389987 40.191773 1.023994 68.436614-1.023994 75.732573-9.812945 35.4128-30.378829 27.604844-30.378829 35.4128C858.450044 730.752933 705.10691 896 515.966978 896s-342.398067-165.289067-342.398068-369.192916c0-16.169909-14.378919-4.223976-26.154852-35.4128z" horiz-adv-x="1024" />
<glyph glyph-name="frown" unicode="&#59262;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM512 363c-85.5 0-155.6-67.3-160-151.6-0.2-4.6 3.4-8.4 8-8.4h48.1c4.2 0 7.8 3.2 8.1 7.4C420 259.9 461.5 299 512 299s92.1-39.1 95.8-88.6c0.3-4.2 3.9-7.4 8.1-7.4H664c4.6 0 8.2 3.8 8 8.4-4.4 84.3-74.5 151.6-160 151.6z" horiz-adv-x="1024" />
<glyph glyph-name="meh" unicode="&#59264;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM664 331H360c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h304c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8z" horiz-adv-x="1024" />
<glyph glyph-name="smile" unicode="&#59267;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM664 363h-48.1c-4.2 0-7.8-3.2-8.1-7.4C604 306.1 562.5 267 512 267s-92.1 39.1-95.8 88.6c-0.3 4.2-3.9 7.4-8.1 7.4H360c-4.6 0-8.2-3.8-8-8.4 4.4-84.3 74.5-151.6 160-151.6s155.6 67.3 160 151.6c0.2 4.6-3.4 8.4-8 8.4z" horiz-adv-x="1024" />
<glyph glyph-name="man" unicode="&#59362;" d="M874 776H622c-3.3 0-6-2.7-6-6v-56c0-3.3 2.7-6 6-6h160.4L583.1 508.7c-50 38.5-111 59.3-175.1 59.3-76.9 0-149.3-30-203.6-84.4S120 356.9 120 280s30-149.3 84.4-203.6C258.7 22 331.1-8 408-8s149.3 30 203.6 84.4C666 130.7 696 203.1 696 280c0 64.1-20.8 124.9-59.2 174.9L836 654.1V494c0-3.3 2.7-6 6-6h56c3.3 0 6 2.7 6 6V746c0 16.5-13.5 30-30 30zM408 68c-116.9 0-212 95.1-212 212s95.1 212 212 212 212-95.1 212-212-95.1-212-212-212z" horiz-adv-x="1024" />
<glyph glyph-name="woman" unicode="&#59365;" d="M909.7 739.4l-42.2 42.2c-3.1 3.1-8.2 3.1-11.3 0L764 689.4l-84.2 84.2c-3.1 3.1-8.2 3.1-11.3 0l-42.1-42.1c-3.1-3.1-3.1-8.1 0-11.3l84.2-84.2-135.5-135.3c-50 38.5-111 59.3-175.1 59.3-76.9 0-149.3-30-203.6-84.4S112 348.9 112 272s30-149.3 84.4-203.6C250.7 14 323.1-16 400-16s149.3 30 203.6 84.4C658 122.7 688 195.1 688 272c0 64.2-20.9 125.1-59.3 175.1l135.4 135.4 84.2-84.2c3.1-3.1 8.2-3.1 11.3 0l42.1 42.1c3.1 3.1 3.1 8.1 0 11.3l-84.2 84.2 92.2 92.2c3.1 3.1 3.1 8.2 0 11.3zM400 60c-116.9 0-212 95.1-212 212s95.1 212 212 212 212-95.1 212-212-95.1-212-212-212z" horiz-adv-x="1024" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/assets/images/famen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

BIN
src/assets/images/load.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
src/assets/images/meter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
src/assets/images/start.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
<template>
<Modal v-model="showModal" :width="width||80" :title="title" v-bind="$attrs">
<div class="asyncModal-box">
<slot></slot>
</div>
<div slot="footer">
<Button @click="hide">取消</Button>
<Button class="ml30" type="primary" @click="saveInfo">确定</Button>
</div>
</Modal>
</template>
<script>
export default {
props: ['title', 'width'],
data() {
return {
showModal: false,
callback: null
}
},
methods: {
show(callback) {
this.showModal = true
this.callback = callback
},
hide() {
this.showModal = false
},
async saveInfo() {
if (this.callback) {
await this.callback()
}
this.$emit('on-ok')
this.hide()
}
}
}
</script>
<style lang="less" scoped>
.asyncModal-box {
max-height: 90vh;
min-height: 20vh;
overflow: auto;
}
</style>

View File

@@ -0,0 +1,42 @@
<template>
<component :is="iconType" :type="iconName" :color="iconColor" :size="iconSize" />
</template>
<script>
import Icons from '@component/md-icons'
export default {
name: 'CommonIcon',
components: { Icons },
props: {
type: {
type: String,
},
color: String,
size: Number,
},
computed: {
iconType() {
if (this.type) {
return this.type.indexOf('_') === 0 ? 'Icons' : 'Icon'
}
},
iconName() {
return this.iconType === 'Icons' ? this.getCustomIconName(this.type) : this.type
},
iconSize() {
return this.size || (this.iconType === 'Icons' ? 12 : undefined)
},
iconColor() {
return this.color || ''
},
},
methods: {
getCustomIconName(iconName) {
return iconName.slice(1)
},
},
}
</script>
<style>
</style>

View File

@@ -0,0 +1,2 @@
import CommonIcon from './common-icon.vue'
export default CommonIcon

View File

@@ -0,0 +1,105 @@
<template>
<Dropdown trigger="click" style="width:100%" @on-visible-change="showDrop">
<Input :value="value" readonly placeholder="请选择" />
<Dropdown-menu slot="list">
<Form :model="cronForm" ref="cronForm" label-position="left" :label-width="160" class="cron-form">
<!-- Minute Input -->
<FormItem label="Minute">
<Select v-model="cronForm.minute" placeholder="Minute" class="scrollable-select">
<Option v-for="minute in minutes" :value="minute" :key="minute">{{ minute }}</Option>
</Select>
</FormItem>
<!-- Hour Input -->
<FormItem label="Hour">
<Select v-model="cronForm.hour" placeholder="Hour" class="scrollable-select">
<Option v-for="hour in hours" :value="hour" :key="hour">{{ hour }}</Option>
</Select>
</FormItem>
<!-- Day of Month Input -->
<FormItem label="Day">
<Select v-model="cronForm.day" placeholder="Day" class="scrollable-select">
<Option v-for="day in days" :value="day" :key="day">{{ day }}</Option>
</Select>
</FormItem>
<!-- Month Input -->
<FormItem label="Month">
<Select v-model="cronForm.month" placeholder="Month" class="scrollable-select">
<Option v-for="month in months" :value="month" :key="month">{{ month }}</Option>
</Select>
</FormItem>
<!-- Day of Week Input -->
<FormItem label="Week">
<Select v-model="cronForm.dayOfWeek" placeholder="Day of Week" class="scrollable-select">
<Option v-for="dayOfWeek in daysOfWeek" :value="dayOfWeek" :key="dayOfWeek">{{ dayOfWeek }}</Option>
</Select>
</FormItem>
<Button type="primary" @click="applyCron">确定</Button>
</Form>
</Dropdown-menu>
</Dropdown>
</template>
<script>
export default {
props: ['value'],
data() {
return {
cronForm: {
minute: '*',
hour: '*',
day: '*',
month: '*',
dayOfWeek: '*'
},
minutes: ['*', ...Array.from({ length: 60 }, (_, i) => i.toString())],
hours: ['*', ...Array.from({ length: 24 }, (_, i) => i.toString())],
days: ['*', ...Array.from({ length: 31 }, (_, i) => (i + 1).toString())],
months: ['*', ...Array.from({ length: 12 }, (_, i) => (i + 1).toString())],
daysOfWeek: ['*', '0', '1', '2', '3', '4', '5', '6']
}
},
methods: {
showDrop(visible) {
if (visible && this.value) {
let timeArray = this.value.split(' ')
this.cronForm = {
minute: timeArray[0],
hour: timeArray[1],
day: timeArray[2],
month: timeArray[3],
dayOfWeek: timeArray[4]
}
}
},
applyCron() {
const { minute, hour, day, month, dayOfWeek } = this.cronForm
this.$emit('input', `${minute} ${hour} ${day} ${month} ${dayOfWeek}`)
}
}
}
</script>
<style lang="less" scoped>
.ivu-dropdown-menu {
padding: 10px;
}
.scrollable-select {
.ivu-select-dropdown {
max-height: 200px; /* Adjust this value to your needs */
overflow-y: auto; /* Enable vertical scrollbar */
}
}
</style>

View File

@@ -0,0 +1,2 @@
import Cropper from './index.vue'
export default Cropper

View File

@@ -0,0 +1,36 @@
.bg{
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")
}
.cropper-wrapper{
width:100%;
height: 100%;
.img-box{
height: 600px;
width: 600px;
border: 1px solid #ebebeb;
display: inline-block;
.bg;
img{
max-width: 100%;
display: block;
}
}
.right-con{
display: inline-block;
width: 170px;
vertical-align: top;
box-sizing: border-box;
padding: 0 10px;
margin-left: 30px;
.preview-box{
height: 150px !important;
width: 100% !important;
overflow: hidden;
border: 1px solid #ebebeb;
.bg;
}
.button-box{
padding: 10px 0 0;
}
}
}

View File

@@ -0,0 +1,155 @@
<template>
<div class="cropper-wrapper">
<div class="img-box">
<img class="cropper-image" :id="imgId" alt="">
</div>
<div class="right-con">
<div v-if="preview" class="preview-box" :id="previewId"></div>
<div class="button-box">
<slot>
<CustomUpload @chage="beforeUpload"> </CustomUpload>
</slot>
<div v-show="insideSrc">
<Button type="primary" @click="rotate">
<Icon type="md-refresh" :size="18" />
</Button>
</div>
</div>
</div>
<div style="text-align: right;margin-top: 20px;">
<Button style="width: 150px;margin-top: 10px;" type="primary" @click="crop">{{ cropButtonText }}</Button>
</div>
</div>
</template>
<script>
import Cropper from 'cropperjs'
import './index.less'
import 'cropperjs/dist/cropper.min.css'
export default {
name: 'Cropper',
props: {
src: {
type: String,
default: '',
},
preview: {
type: Boolean,
default: true,
},
moveStep: {
type: Number,
default: 4,
},
cropButtonText: {
type: String,
default: '裁剪',
},
},
data() {
return {
actionUrl: window.rootVue.$config.apiUrl + 'sys_file/upload_oos_img',
cropper: null,
insideSrc: '',
file: null,
}
},
computed: {
imgId() {
return `cropper${this._uid}`
},
previewId() {
return `cropper_preview${this._uid}`
},
},
watch: {
src(src) {
this.replace(src)
},
insideSrc(src) {
this.replace(src)
},
},
methods: {
beforeUpload(file) {
this.file = file
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = async (event) => {
this.getSize(event.srcElement.result).then(({ width, height }) => {
if (width < 500 || height < 500) {
rootVue.$Message.error('图片尺寸小于 500*500 ,请更换图片')
return false
}
this.insideSrc = event.srcElement.result
})
}
return false
},
getSize(src) {
let pro = new Promise((resolve, reject) => {
let img = new Image()
img.onload = function () {
let { width, height } = img
resolve({ width, height })
}
img.src = src
})
return pro
},
replace(src) {
this.cropper.replace(src)
this.insideSrc = src
},
rotate() {
this.cropper.rotate(90)
},
shrink() {
this.cropper.zoom(-0.1)
},
magnify() {
this.cropper.zoom(0.1)
},
scale(d) {
this.cropper[`scale${d}`](-this.cropper.getData()[`scale${d}`])
},
move(...argu) {
this.cropper.move(...argu)
},
crop() {
this.cropper.getCroppedCanvas({ fillColor: '#fff' }).toBlob(
(blob) => {
let file = new File([blob], this.file.name, {
type: this.file.type,
lastModified: Date.now(),
})
this.$emit('on-crop', file)
this.insideSrc = ''
this.filefile = null
},
'image/jpeg',
0.8
)
},
},
mounted() {
this.$nextTick(() => {
let dom = document.getElementById(this.imgId)
this.cropper = new Cropper(dom, {
preview: `#${this.previewId}`,
dragMode: 'move',
checkCrossOrigin: true,
aspectRatio: 5 / 5,
cropBoxResizable: false,
cropBoxMovable: false,
outputSize: 0.8,
minContainerWidth: 600, // 容器的最小宽度
minContainerHeight: 600, // 容器的最小高度
})
})
},
}
</script>

View File

@@ -0,0 +1,95 @@
<template>
<div :id="id" class="edit-box"> </div>
</template>
<script>
import WangEditor from 'wangeditor'
export default {
props: ['value'],
data() {
return {
id: '',
editor: null,
}
},
created() {
let date = new Date()
this.id = 'wangeditor_' + date.getTime()
},
mounted() {
this.init()
},
methods: {
init() {
let domId = `#${this.id}`
this.editor = new WangEditor(domId)
this.editor.config.uploadImgShowBase64 = true
this.editor.config.uploadImgServer = window.rootVue.$config.apiUrl + 'sys_file/upload_oos_img'
this.editor.config.uploadImgHooks = {
customInsert: (insertImg, result, editor) => {
var url = result.data.path
insertImg(url)
},
}
this.editor.config.menus = [
'head', // 标题
'bold', // 粗体
'fontSize', // 字号
'fontName', // 字体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
'backColor', // 背景颜色
'link', // 插入链接
'list', // 列表
'justify', // 对齐方式
'image', // 插入图片
'fullscreen', // 全屏
]
this.editor.config.pasteTextHandle = function (content) {
if (content === '' && !content) return ''
var str = content
str = str.replace(/<xml>[\s\S]*?<\/xml>/gi, '')
str = str.replace(/<style>[\s\S]*?<\/style>/gi, '')
str = str.replace(/<\/?[^>]*>/g, '')
str = str.replace(/[ | ]*\n/g, '\n')
str = str.replace(/&nbsp;/gi, '')
str = str.replace(/spanyes'/gi, 'div')
str = str.replace(/spanyes'/gi, '')
str = str.replace(/';/gi, '')
str = str.replace(/spanyes'/gi, 'span')
str = str.replace(/<\/font>/gi, '')
return str
}
this.editor.config.onchange = (newHtml) => {
this.$emit('input', newHtml)
}
this.editor.create()
this.editor.$textElem.css('text-align', 'left')
// 初始化设置值
if (this.value) {
this.setHtml(this.value)
}
},
getHtml() {
return this.editor.txt.html()
},
setHtml(text) {
this.editor.txt.html(text)
},
},
}
</script>
<style lang="less" scoped>
.edit-box {
width: 100%;
}
</style>

View File

@@ -0,0 +1,2 @@
import InforCard from './infor-card.vue'
export default InforCard

View File

@@ -0,0 +1,94 @@
<template>
<Card :shadow="shadow" class="info-card-wrapper" :padding="0">
<div class="content-con">
<div class="left-area" :style="{background: color, width: leftWidth}">
<common-icon class="icon" :type="icon" :size="iconSize" color="#fff" />
</div>
<div class="right-area" :style="{width: rightWidth}">
<div>
<slot></slot>
</div>
</div>
</div>
</Card>
</template>
<script>
import CommonIcon from '@component/common-icon'
export default {
name: 'InforCard',
components: {
CommonIcon,
},
props: {
left: {
type: Number,
default: 36,
},
color: {
type: String,
default: '#2d8cf0',
},
icon: {
type: String,
default: '',
},
iconSize: {
type: Number,
default: 20,
},
shadow: {
type: Boolean,
default: false,
},
},
computed: {
leftWidth() {
return `${this.left}%`
},
rightWidth() {
return `${100 - this.left}%`
},
},
}
</script>
<style lang="less" scoped>
.common {
float: left;
height: 100%;
display: table;
text-align: center;
}
.size {
width: 100%;
height: 100%;
}
.middle-center {
display: table-cell;
vertical-align: middle;
}
.info-card-wrapper {
.size;
overflow: hidden;
.ivu-card-body {
.size;
}
.content-con {
.size;
position: relative;
.left-area {
.common;
& > .icon {
.middle-center;
}
}
.right-area {
.common;
& > div {
.middle-center;
}
}
}
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<div id="load-warp">
<div class="m-load"> </div>
</div>
</template>
<script>
export default {}
</script>
<style lang="less" scoped>
#load-warp {
position: absolute;
height: 100%;
line-height: 100%;
width: 100%;
background: rgba(255, 255, 255, 0.8);
z-index: 99999;
display: none;
text-align: center;
}
.m-load {
position: relative;
top: 50%;
margin-top: -18px;
width: 36px;
height: 36px;
background: url('../../assets/images/load.gif') center center no-repeat;
display: inline-block;
}
</style>

View File

@@ -0,0 +1,2 @@
import LoginForm from './login-form.vue'
export default LoginForm

View File

@@ -0,0 +1,68 @@
<template>
<Form ref="loginForm" :model="form" :rules="rules" @keydown.enter.native="handleSubmit">
<FormItem prop="userName">
<Input v-model="form.userName" placeholder="请输入用户名">
<span slot="prepend">
<Icon :size="16" type="ios-person"></Icon>
</span>
</Input>
</FormItem>
<FormItem prop="password">
<Input type="password" v-model="form.password" placeholder="请输入密码">
<span slot="prepend">
<Icon :size="14" type="md-lock"></Icon>
</span>
</Input>
</FormItem>
<FormItem>
<Button style="margin-top:20px;" size="large" @click="handleSubmit" type="primary" long>登录</Button>
</FormItem>
</Form>
</template>
<script>
export default {
name: 'LoginForm',
props: {
userNameRules: {
type: Array,
default: () => {
return [{ required: true, message: '账号不能为空', trigger: 'blur' }]
}
},
passwordRules: {
type: Array,
default: () => {
return [{ required: true, message: '密码不能为空', trigger: 'blur' }]
}
}
},
data() {
return {
form: {
userName: '',
password: ''
}
}
},
computed: {
rules() {
return {
userName: this.userNameRules,
password: this.passwordRules
}
}
},
methods: {
handleSubmit() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.$emit('on-success-valid', {
userName: this.form.userName,
password: this.form.password
})
}
})
}
}
}
</script>

View File

@@ -0,0 +1,2 @@
import ABackTop from './index.vue'
export default ABackTop

View File

@@ -0,0 +1,94 @@
<template>
<div :class="classes" :style="styles" @click="back">
<slot>
<div :class="innerClasses">
<i class="ivu-icon ivu-icon-ios-arrow-up"></i>
</div>
</slot>
</div>
</template>
<script>
import { scrollTop, on, off } from '@/utils/tools'
const prefixCls = 'ivu-back-top'
export default {
name: 'ABackTop',
props: {
height: {
type: Number,
default: 400
},
bottom: {
type: Number,
default: 30
},
right: {
type: Number,
default: 30
},
duration: {
type: Number,
default: 1000
},
container: {
type: null,
default: window
}
},
data() {
return {
backTop: false
}
},
mounted() {
// window.addEventListener('scroll', this.handleScroll, false)
// window.addEventListener('resize', this.handleScroll, false)
on(this.containerEle, 'scroll', this.handleScroll)
on(this.containerEle, 'resize', this.handleScroll)
},
beforeDestroy() {
// window.removeEventListener('scroll', this.handleScroll, false)
// window.removeEventListener('resize', this.handleScroll, false)
off(this.containerEle, 'scroll', this.handleScroll)
off(this.containerEle, 'resize', this.handleScroll)
},
computed: {
classes() {
return [
`${prefixCls}`,
{
[`${prefixCls}-show`]: this.backTop
}
]
},
styles() {
return {
bottom: `${this.bottom}px`,
right: `${this.right}px`
}
},
innerClasses() {
return `${prefixCls}-inner`
},
containerEle() {
return this.container === window
? window
: document.querySelector(this.container)
}
},
methods: {
handleScroll() {
this.backTop = this.containerEle.scrollTop >= this.height
},
back() {
let target =
typeof this.container === 'string'
? this.containerEle
: document.documentElement || document.body
const sTop = target.scrollTop
scrollTop(this.containerEle, sTop, 0, this.duration)
this.$emit('on-click')
}
}
}
</script>

View File

@@ -0,0 +1,90 @@
<template>
<div v-if="showFullScreenBtn" class="full-screen-btn-con">
<Tooltip :content="value ? '退出全屏' : '全屏'" placement="bottom">
<Icon @click.native="handleChange" :type="value ? 'md-contract' : 'md-expand'" :size="23"></Icon>
</Tooltip>
</div>
</template>
<script>
export default {
name: 'Fullscreen',
computed: {
showFullScreenBtn() {
return window.navigator.userAgent.indexOf('MSIE') < 0
}
},
props: {
value: {
type: Boolean,
default: false
}
},
methods: {
handleFullscreen() {
let main = document.body
if (this.value) {
if (document.exitFullscreen) {
document.exitFullscreen()
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen()
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen()
} else if (document.msExitFullscreen) {
document.msExitFullscreen()
}
} else {
if (main.requestFullscreen) {
main.requestFullscreen()
} else if (main.mozRequestFullScreen) {
main.mozRequestFullScreen()
} else if (main.webkitRequestFullScreen) {
main.webkitRequestFullScreen()
} else if (main.msRequestFullscreen) {
main.msRequestFullscreen()
}
}
},
handleChange() {
this.handleFullscreen()
}
},
mounted() {
let isFullscreen =
document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.fullScreen ||
document.mozFullScreen ||
document.webkitIsFullScreen
isFullscreen = !!isFullscreen
document.addEventListener('fullscreenchange', () => {
this.$emit('input', !this.value)
this.$emit('on-change', !this.value)
})
document.addEventListener('mozfullscreenchange', () => {
this.$emit('input', !this.value)
this.$emit('on-change', !this.value)
})
document.addEventListener('webkitfullscreenchange', () => {
this.$emit('input', !this.value)
this.$emit('on-change', !this.value)
})
document.addEventListener('msfullscreenchange', () => {
this.$emit('input', !this.value)
this.$emit('on-change', !this.value)
})
this.$emit('input', isFullscreen)
}
}
</script>
<style lang="less">
.full-screen-btn-con .ivu-tooltip-rel {
height: 64px;
line-height: 56px;
i {
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,2 @@
import Fullscreen from './fullscreen.vue'
export default Fullscreen

View File

@@ -0,0 +1,4 @@
.custom-bread-crumb{
display: inline-block;
vertical-align: top;
}

View File

@@ -0,0 +1,46 @@
<template>
<div class="custom-bread-crumb">
<Breadcrumb :style="{fontSize: `${fontSize}px`}">
<BreadcrumbItem v-for="item in list" :to="item.to" :key="`bread-crumb-${item.name}`">
<common-icon style="margin-right: 4px;" :type="item.icon || ''" />
{{ showTitle(item) }}
</BreadcrumbItem>
</Breadcrumb>
</div>
</template>
<script>
import { showTitle } from '@/utils/tools'
import CommonIcon from '@component/common-icon/common-icon'
import './custom-bread-crumb.less'
export default {
name: 'customBreadCrumb',
components: {
CommonIcon,
},
props: {
list: {
type: Array,
default: () => [],
},
fontSize: {
type: Number,
default: 14,
},
showIcon: {
type: Boolean,
default: false,
},
},
methods: {
showTitle(item) {
return showTitle(item, this)
},
isCustomIcon(iconName) {
return iconName.indexOf('_') === 0
},
getCustomIconName(iconName) {
return iconName.slice(1)
},
},
}
</script>

View File

@@ -0,0 +1,2 @@
import customBreadCrumb from './custom-bread-crumb.vue'
export default customBreadCrumb

View File

@@ -0,0 +1,18 @@
.header-bar {
width: 100%;
height: 100%;
position: relative;
height: 64px;
display: flex;
flex-direction: row;
.custom-content-con {
height: 64px;
padding-right: 120px;
line-height: 64px;
display: inline-block;
vertical-align: top;
flex: 1;
}
}

View File

@@ -0,0 +1,39 @@
<template>
<div class="header-bar">
<sider-trigger :collapsed="collapsed" icon="md-menu" @on-change="handleCollpasedChange"></sider-trigger>
<custom-bread-crumb show-icon style="margin-left: 30px;" :list="breadCrumbList"></custom-bread-crumb>
<div class="custom-content-con">
<slot></slot>
</div>
</div>
</template>
<script>
import siderTrigger from './sider-trigger'
import customBreadCrumb from './custom-bread-crumb'
import './header-bar.less'
export default {
name: 'HeaderBar',
components: {
siderTrigger,
customBreadCrumb,
},
props: {
collapsed: Boolean,
},
computed: {
breadCrumbList() {
let route = this.$route.matched.map((p) => {
let { name, path, meta } = p
return { name, path, meta }
})
route.shift()
return route
},
},
methods: {
handleCollpasedChange(state) {
this.$emit('on-coll-change', state)
},
},
}
</script>

View File

@@ -0,0 +1,2 @@
import HeaderBar from './header-bar'
export default HeaderBar

View File

@@ -0,0 +1,2 @@
import siderTrigger from './sider-trigger.vue'
export default siderTrigger

View File

@@ -0,0 +1,21 @@
.trans{
transition: transform .2s ease;
}
@size: 40px;
.sider-trigger-a{
padding: 6px;
width: @size;
height: @size;
display: inline-block;
text-align: center;
color: #5c6b77;
margin-top: 12px;
i{
.trans;
vertical-align: top;
}
&.collapsed i{
transform: rotateZ(90deg);
.trans;
}
}

View File

@@ -0,0 +1,27 @@
<template>
<a @click="handleChange" type="text" :class="['sider-trigger-a', collapsed ? 'collapsed' : '']"><Icon :type="icon" :size="size" /></a>
</template>
<script>
export default {
name: 'siderTrigger',
props: {
collapsed: Boolean,
icon: {
type: String,
default: 'navicon-round'
},
size: {
type: Number,
default: 26
}
},
methods: {
handleChange() {
this.$emit('on-change', !this.collapsed)
}
}
}
</script>
<style lang="less">
@import './sider-trigger.less';
</style>

View File

@@ -0,0 +1,2 @@
import Language from './language.vue'
export default Language

View File

@@ -0,0 +1,51 @@
<template>
<div>
<Dropdown trigger="click" @on-click="selectLang">
<a href="javascript:void(0)">
{{ title }}
<Icon :size="18" type="md-arrow-dropdown" />
</a>
<DropdownMenu slot="list">
<DropdownItem v-for="(value, key) in localList" :name="key" :key="`lang-${key}`">{{ value }}</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</template>
<script>
export default {
name: 'Language',
props: {
lang: String
},
data() {
return {
langList: {
'zh-CN': '语言',
'zh-TW': '語言',
'en-US': 'Lang'
},
localList: {
'zh-CN': '中文简体',
'zh-TW': '中文繁体',
'en-US': 'English'
}
}
},
watch: {
lang(lang) {
this.$i18n.locale = lang
}
},
computed: {
title() {
return this.langList[this.lang]
}
},
methods: {
selectLang(name) {
this.$emit('on-lang-change', name)
}
}
}
</script>

View File

@@ -0,0 +1,58 @@
<template>
<Dropdown ref="dropdown" @on-click="handleClick" :class="hideTitle ? '' : 'collased-menu-dropdown'" :transfer="hideTitle" :placement="placement">
<a class="drop-menu-a" type="text" @mouseover="handleMousemove($event, children)" :style="{textAlign: !hideTitle ? 'left' : ''}">
<common-icon :size="rootIconSize" :color="textColor" :type="parentItem.meta.icon" /><span class="menu-title" v-if="!hideTitle">{{
showTitle(parentItem) }}</span>
<Icon style="float: right;" v-if="!hideTitle" type="ios-arrow-forward" :size="16" />
</a>
<DropdownMenu ref="dropdown" slot="list">
<template v-for="child in children">
<collapsed-menu v-if="showChildren(child)" :icon-size="iconSize" :parent-item="child" :key="`drop-${child.name}`"></collapsed-menu>
<DropdownItem v-else :key="`drop-${child.name}`" :name="child.name">
<common-icon :size="iconSize" :type="child.meta.icon" /><span class="menu-title">{{ showTitle(child) }}</span>
</DropdownItem>
</template>
</DropdownMenu>
</Dropdown>
</template>
<script>
import mixin from './mixin'
import itemMixin from './item-mixin'
import { findNodeUpperByClasses } from '@/utils/tools'
export default {
name: 'CollapsedMenu',
mixins: [mixin, itemMixin],
props: {
hideTitle: {
type: Boolean,
default: false,
},
rootIconSize: {
type: Number,
default: 16,
},
},
data() {
return {
placement: 'right-end',
}
},
methods: {
handleClick(name) {
this.$emit('on-click', name)
},
handleMousemove(event, children) {
const { pageY } = event
const height = children.length * 38
const isOverflow = pageY + height < window.innerHeight
this.placement = isOverflow ? 'right-start' : 'right-end'
},
},
mounted() {
let dropdown = findNodeUpperByClasses(this.$refs.dropdown.$el, ['ivu-select-dropdown', 'ivu-dropdown-transfer'])
if (dropdown) dropdown.style.overflow = 'visible'
},
}
</script>

View File

@@ -0,0 +1,2 @@
import SideMenu from './side-menu.vue'
export default SideMenu

View File

@@ -0,0 +1,21 @@
export default {
props: {
parentItem: {
type: Object,
default: () => {}
},
theme: String,
iconSize: Number
},
computed: {
parentName() {
return this.parentItem.name
},
children() {
return this.parentItem.children
},
textColor() {
return this.theme === 'dark' ? '#fff' : '#495060'
}
}
}

View File

@@ -0,0 +1,18 @@
import CommonIcon from "@component/common-icon";
import { showTitle } from "@/utils/tools";
export default {
components: {
CommonIcon
},
methods: {
showTitle(item) {
return showTitle(item, this);
},
showChildren(item) {
return item.type === "菜单";
},
getNameOrHref(item, children0) {
return item.href ? `isTurnByHref_${item.href}` : item.name;
}
}
};

View File

@@ -0,0 +1,25 @@
<template>
<Submenu :name="`${parentName}`">
<template slot="title">
<common-icon :type="parentItem.meta.icon || ''" />
<span>{{ showTitle(parentItem) }}</span>
</template>
<template v-for="item in children">
<template v-if="item.is_show_menu">
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
<menu-item v-else :name="getNameOrHref(item)" :key="`menu-${item.name}`">
<common-icon :type="item.meta.icon || ''" /><span>{{ showTitle(item) }}</span>
</menu-item>
</template>
</template>
</Submenu>
</template>
<script>
import mixin from './mixin'
import itemMixin from './item-mixin'
export default {
name: 'SideMenuItem',
mixins: [mixin, itemMixin],
mounted() {},
}
</script>

View File

@@ -0,0 +1,39 @@
.side-menu-wrapper {
user-select: none;
.menu-collapsed {
padding-top: 10px;
.ivu-dropdown {
width: 100%;
.ivu-dropdown-rel a {
width: 100%;
}
}
.ivu-tooltip {
width: 100%;
.ivu-tooltip-rel {
width: 100%;
}
.ivu-tooltip-popper .ivu-tooltip-content {
.ivu-tooltip-arrow {
border-right-color: #fff;
}
.ivu-tooltip-inner {
background: #fff;
color: #495060;
}
}
}
}
a.drop-menu-a {
display: inline-block;
padding: 6px 15px;
width: 100%;
text-align: center;
color: #495060;
}
}
.menu-title {
padding-left: 6px;
}

View File

@@ -0,0 +1,112 @@
<template>
<div class="side-menu-wrapper">
<slot></slot>
<Menu ref="menu" v-show="!collapsed" :active-name="activeName" :open-names="openedNames" :accordion="true" :theme="theme" width="auto" @on-select="handleSelect">
<template v-for="item in menuList">
<template>
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
<menu-item v-else :name="getNameOrHref(item)" :key="`menu-${item.name}`">
<common-icon :type="item.meta.icon || ''" /><span>{{ showTitle(item) }}</span>
</menu-item>
</template>
</template>
</Menu>
<div class="menu-collapsed" v-show="collapsed" :list="menuList">
<template v-for="item in menuList">
<collapsed-menu v-if="item.type==='菜单'" @on-click="handleSelect" hide-title :root-icon-size="rootIconSize" :icon-size="iconSize" :theme="theme" :parent-item="item" :key="`drop-menu-${item.name}`">
</collapsed-menu>
<Tooltip transfer v-else :content="showTitle(item.children && item.children[0] ? item.children[0] : item)" placement="right" :key="`drop-menu-${item.name}`">
<a @click="handleSelect(getNameOrHref(item, true))" class="drop-menu-a" :style="{textAlign: 'center'}">
<common-icon :size="rootIconSize" :color="textColor" :type="item.meta.icon" />
</a>
</Tooltip>
</template>
</div>
</div>
</template>
<script>
import SideMenuItem from './side-menu-item.vue'
import CollapsedMenu from './collapsed-menu.vue'
import { getUnion } from '@/utils/tools'
import mixin from './mixin'
export default {
name: 'SideMenu',
mixins: [mixin],
components: {
SideMenuItem,
CollapsedMenu,
},
props: {
menuList: {
type: Array,
default() {
return []
},
},
collapsed: {
type: Boolean,
},
theme: {
type: String,
default: 'light',
},
rootIconSize: {
type: Number,
default: 20,
},
iconSize: {
type: Number,
default: 16,
},
activeName: {
type: String,
default: '',
},
openNames: {
type: Array,
default: () => [],
},
},
data() {
return {
openedNames: [],
}
},
methods: {
handleSelect(name) {
this.$emit('on-select', name)
},
getOpenedNamesByActiveName(name) {
let names = this.$route.matched.map((item) => item.name).filter((item) => item !== name)
return names
},
},
computed: {
textColor() {
return this.theme === 'dark' ? '#fff' : '#495060'
},
},
watch: {
activeName(name) {
this.openedNames = this.getOpenedNamesByActiveName(name)
},
openNames(newNames) {
this.openedNames = newNames
},
openedNames() {
this.$nextTick(() => {
this.$refs.menu.updateOpened()
})
},
},
mounted() {
let openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName(this.activeName))
this.openedNames = openedNames
},
}
</script>
<style lang="less">
@import './side-menu.less';
</style>

View File

@@ -0,0 +1,63 @@
<template>
<div class="terminal-box">
<Icon :class="iconclass" type="ios-radio" @click="show" />
<asyncModal ref="asyncModal" title="终端" top="30" :footer-hide="true">
<Terminal></Terminal>
</asyncModal>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Terminal from './terminal.vue'
export default {
components: {
Terminal
},
computed: {
...mapGetters({
isServerRun: 'isServerRun'
}),
iconclass() {
let curClass = 'terminal-icon ml10 '
if (this.isServerRun) {
curClass += ' run'
}
return curClass
}
},
methods: {
show() {
this.$refs['asyncModal'].show()
}
}
}
</script>
<style lang="less" scoped>
.terminal-box {
margin-top: 3px;
}
.terminal-icon {
color: #c5c8ce;
font-size: 22px;
cursor: pointer;
&.run {
animation: colorChange 0.4s infinite; /* 调用颜色变化动画 */
}
}
@keyframes colorChange {
0% {
color: #19be6b; /* 初始颜色 */
}
100% {
color: red; /* 返回到初始颜色 */
}
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<div class="content-view">
<div class="log-box">
<div class="pa5 flex" style="align-items: flex-end; justify-content: flex-end;">
<Button class="ml10" size="small" @click="reloadLog">重新加载日志</Button>
<Button type="error" size="small" @click="clearLog">清空日志</Button>
</div>
<div class="view-log-info" id="view-log-info" style="height:70vh;width: 100%;">{{ infoMsg }}</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import uiTool from '@/utils/uiTool'
export default {
data() {
return {}
},
mounted() {},
computed: {
...mapGetters({
infoMsg: 'infoMsg'
})
},
watch: {
infoMsg: {
handler() {
this.scrollEnd()
}
}
},
methods: {
clearLog() {
this.$store.commit('clearInfoMsg')
},
reloadLog() {
this.$store.dispatch('setInteverLog')
},
scrollEnd() {
setTimeout(() => {
let outerDiv = document.querySelector('#view-log-info')
if (outerDiv) {
outerDiv.scrollTop = outerDiv.scrollHeight + 5
}
}, 300)
}
}
}
</script>
<style lang="less" scoped>
.log-box {
display: flex;
align-items: flex-start;
justify-content: flex-start;
flex-direction: column;
width: 100%;
}
</style>

View File

@@ -0,0 +1,2 @@
import User from './user.vue'
export default User

View File

@@ -0,0 +1,12 @@
.user{
&-avator-dropdown{
cursor: pointer;
display: inline-block;
// height: 64px;
vertical-align: middle;
// line-height: 64px;
.ivu-badge-dot{
top: 16px;
}
}
}

View File

@@ -0,0 +1,68 @@
<template>
<div class="user-avator-dropdown">
<label class="loginName-box">{{userName}}</label>
<Dropdown>
<Avatar :src="typeof userAvator === 'string' ? userAvator : userIcon" />
<Icon :size="18" type="md-arrow-dropdown"></Icon>
<DropdownMenu slot="list">
<a @click="logout">
<DropdownItem name="logout">
<Icon type="md-exit" />
退出登录
</DropdownItem>
</a>
</DropdownMenu>
</Dropdown>
</div>
</template>
<script>
import './user.less'
import { mapMutations, mapActions } from 'vuex'
const userIcon = require("@/assets/images/administrato.png").default
export default {
name: 'User',
props: {
userName: String,
userAvator: {
type: [String, Object],
default: '',
},
},
data(){
return {
userIcon: userIcon
}
},
methods: {
...mapActions(['handleLogOut']),
logout() {
this.handleLogOut(this)
},
message() {
this.$router.push({
name: 'message_page',
})
},
},
}
</script>
<style lang="less" scoped>
.user-avator-dropdown {
position: absolute;
top: 0px;
right: 10px;
}
.loginName-box {
font-weight: bold;
color: #4b81d3;
margin-right: 10px;
}
</style>

View File

@@ -0,0 +1,2 @@
import Main from './main.vue'
export default Main

View File

@@ -0,0 +1,109 @@
.main {
.logo-con {
height: 64px;
padding: 10px;
padding-left: 0px;
color: white;
img {
width: auto;
display: block;
margin: 0 auto;
}
}
.header-con {
padding: 0px 10px;
width: 100%;
background: #fff;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
margin-bottom: 5px;
}
.main-content-con {
height: 100%;
overflow: hidden;
color: darkgray;
display: flex;
}
.main-layout-con {
display: flex;
display: -webkit-flex;
overflow: hidden;
}
.tag-nav-wrapper {
padding: 0;
height: 40px;
background: #f0f0f0;
}
.content-wrapper {
padding: 0px 0.05rem;
height: ~"calc(100% - 40px)";
display: flex;
display: -webkit-flex;
flex-direction: column;
overflow: auto;
}
.left-sider {
.ivu-layout-sider-children {
overflow-y: scroll;
margin-right: -10px;
}
}
}
.ivu-menu-item > i {
margin-right: 12px !important;
}
.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i {
margin-right: 8px !important;
}
.collased-menu-dropdown {
width: 100%;
margin: 0;
line-height: normal;
padding: 7px 0 6px 16px;
clear: both;
font-size: 12px !important;
white-space: nowrap;
list-style: none;
cursor: pointer;
transition: background 0.2s ease-in-out;
&:hover {
background: rgba(100, 100, 100, 0.1);
}
& * {
color: #515a6e;
}
.ivu-menu-item > i {
margin-right: 12px !important;
}
.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i {
margin-right: 8px !important;
}
}
.ivu-select-dropdown.ivu-dropdown-transfer {
max-height: 400px;
}
.police-box {
margin-left: 10px;
color: darkgoldenrod;
overflow: hidden;
height: 100%;
span {
margin: 0px 5px;
}
}

View File

@@ -0,0 +1,145 @@
<template>
<Layout style="height: 100%" class="main">
<Sider hide-trigger collapsible :width="256" :collapsed-width="64" v-model="collapsed" class="left-sider" :style="{overflow: 'hidden'}">
<div class="sidebar-brand" @click="goHome" v-if="!collapsed">
<img style="width:50px" :src="sysFormModel.logoUrl" />
<span class="ml10">{{sysFormModel.title}}</span>
</div>
<side-menu class="mt30" theme="dark" ref="sideMenu" :active-name="$route.name" :collapsed="collapsed" @on-select="turnToPage" :menu-list="menuList"> </side-menu>
</Sider>
<Content class="main-content-con">
<Layout class="main-layout-con">
<Header>
<headerBar class="header-con" @on-coll-change="collpasedChange" :collapsed="collapsed">
<Terminal></Terminal>
<user :userName="userName" :user-avator="userAvator || ''" />
</headerBar>
</Header>
<Layout>
<Content class="content-wrapper">
<router-view />
<loadFlower> </loadFlower>
<ABackTop :height="100" :bottom="80" :right="50" container=".content-wrapper"></ABackTop>
</Content>
</Layout>
</Layout>
</Content>
</Layout>
</template>
<script>
const { title } = require('../../config')
import SideMenu from './components/side-menu'
import HeaderBar from './components/header-bar'
import User from './components/user'
import ABackTop from './components/a-back-top'
import Fullscreen from './components/fullscreen'
import Language from './components/language'
import { mapGetters } from 'vuex'
import headerBar from './components/header-bar'
import loadFlower from '../load-flower/index'
import Terminal from './components/terminal'
import './main.less'
export default {
name: 'Main',
components: {
SideMenu,
HeaderBar,
Language,
Fullscreen,
User,
Terminal,
ABackTop,
loadFlower,
headerBar
},
data() {
return {
title,
collapsed: false,
isFullscreen: false
}
},
computed: {
...mapGetters({
sysFormModel: 'sysFormModel',
menuList: 'menuList',
tagRouter: 'tagRouter',
userName: 'userName',
userAvator: 'avatorImgPath'
}),
cacheList() {
return ['ParentView']
}
},
watch: {},
created() {
this.init()
},
methods: {
async init() {
await this.$store.dispatch('getSysTitle')
},
collpasedChange(collapsed) {
this.collapsed = collapsed
},
goHome() {
this.$router.push({ path: '/' })
},
turnToPage(route) {
let { name, params, query } = {}
if (typeof route === 'string') name = route
else {
name = route.name
params = route.params
query = route.query
}
if (name.indexOf('isTurnByHref_') > -1) {
window.open(name.split('_')[1])
return
}
this.$router.push({
name,
params,
query
})
},
handleCollapsedChange(state) {
this.collapsed = state
},
handleClick(item) {
this.turnToPage(item)
}
}
}
</script>
<style lang="less" scoped>
.shop-select {
z-index: 999999;
}
.sidebar-brand {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
text-align: center;
height: 60px;
line-height: 25px;
color: #fff;
font-size: 18px;
font-weight: bold;
}
</style>

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