前端目录调整

This commit is contained in:
molong
2022-05-12 21:23:18 +08:00
parent fe38e19924
commit a37870c93b
145 changed files with 3090 additions and 7244 deletions

16
app/controller/Base.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace app\controller;
use app\BaseController;
class Base extends BaseController{
public $data = ['code' => 1, 'data' => '', 'message' => ''];
}

View File

@@ -1,17 +0,0 @@
<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
public function index()
{
return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V' . \think\facade\App::version() . '<br/><span style="font-size:30px;">14载初心不改 - 你值得信赖的PHP框架</span></p><span style="font-size:25px;">[ V6.0 版本由 <a href="https://www.yisu.com/" target="yisu">亿速云</a> 独家赞助发布 ]</span></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="ee9b1aa918103c4fc"></think>';
}
public function hello($name = 'ThinkPHP6')
{
return 'hello,' . $name;
}
}

View File

@@ -0,0 +1,26 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace app\controller\auth;
use app\controller\Base;
use app\services\auth\LoginService;
class Index extends Base{
public function login(LoginService $service){
try {
$data = $service->authLogin($this->request);
$this->data['data'] = $data;
} catch (\think\Exception $e) {
$this->data['code'] = 0;
$this->data['message'] = $e->getMessage();
}
return $this->data;
}
}

View File

@@ -1,6 +1,7 @@
<?php
// 全局中间件定义文件
return [
\app\middleware\AllowCrossDomain::class,
\app\middleware\Api::class,
\app\middleware\Validate::class,
];

View File

@@ -0,0 +1,48 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace app\middleware;
use think\Config;
use think\Request;
use think\Response;
class AllowCrossDomain{
protected $header = [
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => 1800,
'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
];
public function __construct(Config $config){
$this->header = array_merge($this->header, $config->get('cross', ''));
}
/**
* 允许跨域请求
* @access public
* @param Request $request
* @param Closure $next
* @param array $header
* @return Response
*/
public function handle($request, \Closure $next, ? array $header = []){
$header = !empty($header) ? array_merge($this->header, $header) : $this->header;
if (!isset($header['Access-Control-Allow-Origin'])) {
$origin = $request->header('origin');
$header['Access-Control-Allow-Origin'] = $origin ? $origin : "*";
}
return $next($request)->header($header);
}
}

View File

@@ -11,6 +11,9 @@ declare (strict_types = 1);
namespace app\middleware;
class Api{
public $data = ['code' => 1, 'data' => '', 'message' => ''];
/**
* 处理请求
*

15
app/model/BaseModel.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace app\model;
use think\Model;
class BaseModel extends Model{
}

20
app/model/user/Users.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace app\model\user;
use app\model\BaseModel;
use xiaodi\JWTAuth\Facade\Jwt;
class Users extends BaseModel{
public function getTokenAttr($value, $data){
$token = Jwt::store('api')->token($data)->__toString();
return $token;
}
}

View File

@@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace app\services\auth;
use app\model\user\Users;
class LoginService{
public function authLogin($request){
$params = $request->post();
$map = [];
foreach($params as $field => $value){
if(in_array($field, ['username', 'email', 'mobile']) && $field != 'password'){
$map[$field] = $value;
}
}
$user = Users::where($map)->field(['uid','username', 'password', 'email', 'avatar', 'department_id', 'status'])->findOrEmpty();
if (!$user->isEmpty()) {
if(password_verify(base64_decode($params['password']), $user->password)){
throw new \think\Exception('密码不正确!', 100002);
}elseif($user->status != 1){
throw new \think\Exception('当前用户不可用', 100003);
}else{
return $user->append(['token']);
}
}else{
throw new \think\Exception('当前用户不存在', 100001);
}
}
}

View File

@@ -22,7 +22,8 @@
"require": {
"php": ">=7.2.5",
"topthink/framework": "^6.0.0",
"topthink/think-orm": "^2.0"
"topthink/think-orm": "^2.0",
"xiaodi/think-jwt": "^2.0"
},
"require-dev": {
"symfony/var-dumper": "^4.2",

272
composer.lock generated
View File

@@ -4,8 +4,72 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "972d5f37c97d6a688bc084d56a25d074",
"content-hash": "aed98006f10d9fbc1ef4ac60e21a2a0e",
"packages": [
{
"name": "lcobucci/jwt",
"version": "3.3.3",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
"reference": "c1123697f6a2ec29162b82f170dd4a491f524773"
},
"dist": {
"type": "zip",
"url": "https://repo.huaweicloud.com/repository/php/lcobucci/jwt/3.3.3/lcobucci-jwt-3.3.3.zip",
"reference": "c1123697f6a2ec29162b82f170dd4a491f524773",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-openssl": "*",
"php": "^5.6 || ^7.0"
},
"require-dev": {
"mikey179/vfsstream": "~1.5",
"phpmd/phpmd": "~2.2",
"phpunit/php-invoker": "~1.1",
"phpunit/phpunit": "^5.7 || ^7.3",
"squizlabs/php_codesniffer": "~2.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1-dev"
}
},
"autoload": {
"psr-4": {
"Lcobucci\\JWT\\": "src"
}
},
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Luís Otávio Cobucci Oblonczyk",
"email": "lcobucci@gmail.com",
"role": "Developer"
}
],
"description": "A simple library to work with JSON Web Token and JSON Web Signature",
"keywords": [
"JWS",
"jwt"
],
"funding": [
{
"url": "https://github.com/lcobucci",
"type": "github"
},
{
"url": "https://www.patreon.com/lcobucci",
"type": "patreon"
}
],
"time": "2020-08-20T13:22:28+00:00"
},
{
"name": "league/flysystem",
"version": "1.1.9",
@@ -192,6 +256,149 @@
],
"time": "2022-04-17T13:12:02+00:00"
},
{
"name": "nette/php-generator",
"version": "v3.6.7",
"source": {
"type": "git",
"url": "https://github.com/nette/php-generator.git",
"reference": "b9ba414c9895fd9420887f20eeb4eabde123677f"
},
"dist": {
"type": "zip",
"url": "https://repo.huaweicloud.com/repository/php/nette/php-generator/v3.6.7/nette-php-generator-v3.6.7.zip",
"reference": "b9ba414c9895fd9420887f20eeb4eabde123677f",
"shasum": ""
},
"require": {
"nette/utils": "^3.1.2",
"php": ">=7.2 <8.2"
},
"require-dev": {
"nette/tester": "^2.4",
"nikic/php-parser": "^4.13",
"phpstan/phpstan": "^0.12",
"tracy/tracy": "^2.8"
},
"suggest": {
"nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.6-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"license": [
"BSD-3-Clause",
"GPL-2.0-only",
"GPL-3.0-only"
],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.",
"homepage": "https://nette.org",
"keywords": [
"code",
"nette",
"php",
"scaffolding"
],
"time": "2022-03-10T01:51:00+00:00"
},
{
"name": "nette/utils",
"version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99"
},
"dist": {
"type": "zip",
"url": "https://repo.huaweicloud.com/repository/php/nette/utils/v3.2.7/nette-utils-v3.2.7.zip",
"reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99",
"shasum": ""
},
"require": {
"php": ">=7.2 <8.2"
},
"conflict": {
"nette/di": "<3.0.6"
},
"require-dev": {
"nette/tester": "~2.0",
"phpstan/phpstan": "^1.0",
"tracy/tracy": "^2.3"
},
"suggest": {
"ext-gd": "to use Image",
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
"ext-json": "to use Nette\\Utils\\Json",
"ext-mbstring": "to use Strings::lower() etc...",
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
"ext-xml": "to use Strings::length() etc. when mbstring is not available"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"license": [
"BSD-3-Clause",
"GPL-2.0-only",
"GPL-3.0-only"
],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
"homepage": "https://nette.org",
"keywords": [
"array",
"core",
"datetime",
"images",
"json",
"nette",
"paginator",
"password",
"slugify",
"string",
"unicode",
"utf-8",
"utility",
"validation"
],
"time": "2022-01-24T11:29:14+00:00"
},
{
"name": "psr/cache",
"version": "1.0.1",
@@ -574,6 +781,69 @@
"orm"
],
"time": "2022-02-28T14:54:22+00:00"
},
{
"name": "xiaodi/think-jwt",
"version": "v2.0.3",
"source": {
"type": "git",
"url": "https://github.com/friendsofthinkphp/think-jwt.git",
"reference": "e24e6084a25d3463f125b3844061b0b0fa1ab843"
},
"dist": {
"type": "zip",
"url": "https://repo.huaweicloud.com/repository/php/xiaodi/think-jwt/v2.0.3/xiaodi-think-jwt-v2.0.3.zip",
"reference": "e24e6084a25d3463f125b3844061b0b0fa1ab843",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"lcobucci/jwt": "3.3.3",
"nette/php-generator": "^3.2",
"php": ">=7.1.0",
"topthink/framework": "^6.0.2"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.15",
"mockery/mockery": "^1.2",
"phpstan/phpstan": "^0.12.0",
"phpunit/phpunit": "^6.2"
},
"type": "think-extend",
"extra": {
"think": {
"services": [
"xiaodi\\JWTAuth\\JwtService"
],
"config": {
"jwt": "config/config.php"
}
},
"think-config": {
"jwt": "config/config.php"
}
},
"autoload": {
"psr-4": {
"xiaodi\\JWTAuth\\": "src/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "xiaodi",
"email": "liangjinbiao@live.com"
}
],
"description": "ThinkPHP Jwt Component",
"keywords": [
"php",
"thinkphp"
],
"time": "2021-02-22T02:09:36+00:00"
}
],
"packages-dev": [

15
config/cross.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
return [
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Max-Age' => 1800,
'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
];

45
config/jwt.php Normal file
View File

@@ -0,0 +1,45 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
use app\model\user\Users;
return [
'stores' => [
'api' => [
'sso' => [
'enable' => false,
],
'token' => [
'unique_id_key' => 'uid',
'signer_key' => 'tensent',
'not_before' => 0,
'expires_at' => 3600,
'refresh_ttL' => 7200,
'signer' => 'Lcobucci\JWT\Signer\Hmac\Sha256',
'type' => 'Header',
'relogin_code' => 50001,
'refresh_code' => 50002,
'iss' => 'client.tensent',
'aud' => 'server.tensent',
'automatic_renewal' => false,
],
'user' => [
'bind' => true,
'class' => null,
]
]
],
'manager' => [
// 缓存前缀
'prefix' => 'jwt',
// 黑名单缓存名
'blacklist' => 'blacklist',
// 白名单缓存名
'whitelist' => 'whitelist'
]
];

14
route/auth.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
use think\facade\Route;
use app\controller\auth;
Route::group('auth', function(){
Route::post('login', 'auth.Index/login');
});

View File

@@ -1,65 +1,19 @@
# thinkvue-admin
<div align="center">
## Project setup
```
npm install
```
![logo](https://lolicode.gitee.io/scui-doc/logo.png)
<p align="center">
<a href="https://v3.vuejs.org/" target="_blank">
<img src="https://img.shields.io/badge/vue.js-3.x-green" alt="vue">
</a>
<a href="https://element-plus.gitee.io/#/zh-CN/component/changelog" target="_blank">
<img src="https://img.shields.io/badge/element--plus-latest-blue" alt="element plus">
</a>
</p>
<h1>SCUI Admin</h1>
</div>
## 介绍
SCUI 是一个中后台前端解决方案基于VUE3和elementPlus实现。
使用最新的前端技术栈,提供各类实用的组件方便在业务开发时的调用,并且持续性的提供丰富的业务模板帮助你快速搭建企业级中后台前端任务。
SCUI的宗旨是 让一切复杂的东西傻瓜化。
![logo](https://lolicode.gitee.io/scui-doc/g_1.jpg)
## 特点
- **组件** 多个独家组件、业务模板和代码生成器
- **权限** 完整的鉴权体系和高精度权限控制
- **布局** 提供多套布局模式,满足各种视觉需求
- **API** 完善的API管理使用真实网络MOCK
- **配置** 统一的全局配置和组件配置支持build后配置热更新
- **性能** 在减少带宽请求和前端算力上多次优化,并且持续着
- **其他** 多功能视图标签、动态权限菜单、控制台组态化、统一异常处理等等
## 演示和文档
- <a href="https://lolicode.gitee.io/scui-doc/demo/#/login" target="_blank">演示</a>
- <a href="https://lolicode.gitee.io/scui-doc/" target="_blank">文档</a>
## 部分截图
![logo](https://lolicode.gitee.io/scui-doc/g_2.jpg)
## 安装教程
``` sh
# 克隆项目
git clone https://gitee.com/lolicode/scui.git
# 进入项目目录
cd scui
# 安装依赖
npm i
# 启动项目(开发模式)
### Compiles and hot-reloads for development
```
npm run serve
```
启动完成后浏览器访问 http://localhost:2800
## 感谢
![fastmock](https://www.fastmock.site/resource/images/logo.png)
### Compiles and minifies for production
```
npm run build
```
## 支持
如果觉得本项目还不错或在工作中有所启发请在Gitee(码云)帮开发者点亮星星,这是对开发者最大的支持和鼓励!
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -1,5 +1,5 @@
{
"name": "scui",
"name": "thinkvue-admin",
"version": "1.5.0",
"private": true,
"scripts": {

View File

@@ -4,8 +4,8 @@
const APP_CONFIG = {
//标题
APP_NAME: "SCUI",
// APP_NAME: "SCUI",
//接口地址如遇跨域需使用nginx代理
API_URL: "https://www.fastmock.site/mock/5039c4361c39a7e3252c5b55971f1bd3/api"
// API_URL: "https://www.fastmock.site/mock/5039c4361c39a7e3252c5b55971f1bd3/api"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -1,29 +1,29 @@
<template>
<el-config-provider :locale="$i18n.messages[$i18n.locale].el" :button="{autoInsertSpace: false}">
<router-view></router-view>
</el-config-provider>
<el-config-provider :locale="$i18n.messages[$i18n.locale].el" :button="{autoInsertSpace: false}">
<router-view></router-view>
</el-config-provider>
</template>
<script>
import colorTool from '@/utils/color'
import colorTool from '@/utils/color'
export default {
name: 'App',
created() {
//设置主题颜色
const app_color = this.$CONFIG.COLOR || this.$TOOL.data.get('APP_COLOR')
if(app_color){
document.documentElement.style.setProperty('--el-color-primary', app_color);
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, colorTool.lighten(app_color,i/10));
}
document.documentElement.style.setProperty(`--el-color-primary-darken-1`, colorTool.darken(app_color,0.1));
export default {
name: 'App',
created() {
//设置主题颜色
const app_color = this.$CONFIG.COLOR || this.$TOOL.data.get('APP_COLOR')
if(app_color){
document.documentElement.style.setProperty('--el-color-primary', app_color);
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, colorTool.lighten(app_color,i/10));
}
document.documentElement.style.setProperty(`--el-color-primary-darken-1`, colorTool.darken(app_color,0.1));
}
}
}
</script>
<style lang="scss">
@import '@/style/style.scss';
@import '@/style/theme/dark.scss';
@import '@/static/style/style.scss';
@import '@/static/style/theme/dark.scss';
</style>

View File

@@ -2,11 +2,18 @@ import config from "@/config"
import http from "@/utils/request"
export default {
token: {
url: `${config.API_URL}/token`,
login: {
url: `${config.API_URL}auth/login`,
name: "登录获取TOKEN",
post: async function(data={}){
return await http.post(this.url, data);
}
},
logout:{
url: `${config.API_URL}auth/logout`,
name: "登出",
get: async function(data={}){
return await http.get(this.url, data);
}
}
}

View File

@@ -1,37 +0,0 @@
import config from "@/config"
import http from "@/utils/request"
export default {
ver: {
url: `${config.API_URL}/demo/ver`,
name: "获取最新版本号",
get: async function(){
return await http.get(this.url);
}
},
post: {
url: `${config.API_URL}/demo/post`,
name: "分页列表",
post: async function(data){
return await http.post(this.url, data, {
headers: {
//'response-status': 401
}
});
}
},
page: {
url: `${config.API_URL}/demo/page`,
name: "分页列表",
get: async function(params){
return await http.get(this.url, params);
}
},
menu: {
url: `${config.API_URL}/demo/menu`,
name: "普通用户菜单",
get: async function(){
return await http.get(this.url);
}
}
}

View File

@@ -2,62 +2,130 @@ import config from "@/config"
import http from "@/utils/request"
export default {
version:{
url: `${config.API_URL}system/index/version`,
name: "获取最新版本号",
get: async function(){
return await http.get(this.url);
}
},
setting:{
list: {
url: `${config.API_URL}system/index/setting`,
name: "获取最新版本号",
get: function(){
return http.get(this.url);
}
}
},
menu: {
myMenus: {
url: `${config.API_URL}/system/menu/my/1.5.0`,
url: `${config.API_URL}system/menu/my/1.5.0`,
name: "获取我的菜单",
get: async function(){
return await http.get(this.url);
}
},
list: {
url: `${config.API_URL}/system/menu/list`,
url: `${config.API_URL}system/menu/index`,
name: "获取菜单",
get: async function(){
return await http.get(this.url);
}
},
add: {
url: `${config.API_URL}/system/menu/add`,
name: "添加菜单",
post: async function(data){
return await http.post(this.url, data);
}
},
edit: {
url: `${config.API_URL}/system/menu/edit`,
name: "编辑菜单",
post: async function(data){
return await http.post(this.url, data);
}
},
delete: {
url: `${config.API_URL}/system/menu/delete`,
name: "删除菜单",
post: async function(data){
return await http.post(this.url, data);
}
}
},
dic: {
tree: {
url: `${config.API_URL}/system/dic/tree`,
dictionary: {
category: {
url: `${config.API_URL}/system/dictionary/category`,
name: "获取字典树",
get: async function(){
return await http.get(this.url);
}
},
editcate:{
url: `${config.API_URL}/system/dictionary/editcate`,
name: "编辑字典树",
post: async function(data = {}){
return await http.post(this.url, data);
}
},
addcate:{
url: `${config.API_URL}/system/dictionary/addcate`,
name: "添加字典树",
post: async function(data = {}){
return await http.post(this.url, data);
}
},
delCate:{
url: `${config.API_URL}/system/dictionary/delcate`,
name: "删除字典树",
post: async function(data = {}){
return await http.post(this.url, data);
}
},
list: {
url: `${config.API_URL}/system/dic/list`,
url: `${config.API_URL}/system/dictionary/lists`,
name: "字典明细",
get: async function(params){
return await http.get(this.url, params);
}
},
get: {
url: `${config.API_URL}/system/dic/get`,
url: `${config.API_URL}/system/dictionary/detail`,
name: "获取字典数据",
get: async function(params){
return await http.get(this.url, params);
}
}
},
role: {
list: {
url: `${config.API_URL}/system/role/list`,
name: "获取角色列表",
},
edit:{
url: `${config.API_URL}/system/dictionary/edit`,
name: "编辑字典明细",
post: async function(data = {}){
return await http.post(this.url, data);
}
},
add:{
url: `${config.API_URL}/system/dictionary/add`,
name: "添加字典明细",
post: async function(data = {}){
return await http.post(this.url, data);
}
},
delete:{
url: `${config.API_URL}/system/dictionary/delete`,
name: "删除字典明细",
post: async function(data = {}){
return await http.post(this.url, data);
}
},
detail: {
url: `${config.API_URL}/system/dictionary/detail`,
name: "字典明细",
get: async function(params){
return await http.get(this.url, params);
}
}
},
user: {
list: {
url: `${config.API_URL}/system/user/list`,
name: "获取用户列表",
get: async function(params){
return await http.get(this.url, params);
}
}
},
},
app: {
list: {
@@ -70,7 +138,7 @@ export default {
},
log: {
list: {
url: `${config.API_URL}/system/log/list`,
url: `${config.API_URL}/system/log/index`,
name: "日志列表",
get: async function(params){
return await http.get(this.url, params);

65
ui/src/api/model/user.js Normal file
View File

@@ -0,0 +1,65 @@
import config from "@/config"
import http from "@/utils/request"
export default {
list: {
url: `${config.API_URL}/user/user/index`,
name: "获得用户列表",
get: async function(params){
return await http.get(this.url, params);
}
},
add: {
url: `${config.API_URL}/user/user/add`,
name: "添加用户",
post: async function(params){
return await http.post(this.url, params);
}
},
edit: {
url: `${config.API_URL}/user/user/edit`,
name: "编辑用户",
post: async function(params){
return await http.post(this.url, params);
}
},
role: {
list: {
url: `${config.API_URL}/user/role/index`,
name: "获得角色列表",
get: async function(params){
return await http.get(this.url, params);
}
},
edit: {
url: `${config.API_URL}/user/role/edit`,
name: "编辑角色",
post: async function(params){
return await http.post(this.url, params);
}
}
},
department: {
list: {
url: `${config.API_URL}/user/department/index`,
name: "获得部门列表",
get: async function(params){
return await http.get(this.url, params);
}
},
edit: {
url: `${config.API_URL}/user/department/edit`,
name: "编辑部门",
post: async function(params){
return await http.post(this.url, params);
}
}
},
userinfo:{
url: `${config.API_URL}/user/user/info`,
name: "获得部门列表",
get: async function(params){
return await http.get(this.url, params);
}
}
}

View File

@@ -1,32 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -1,6 +1,6 @@
const DEFAULT_CONFIG = {
//标题
APP_NAME: "SCUI",
APP_NAME: "管理系统",
//首页地址
DASHBOARD_URL: "/dashboard",
@@ -12,7 +12,7 @@ const DEFAULT_CONFIG = {
CORE_VER: "1.5.0",
//接口地址
API_URL: "/api",
API_URL: "http://localhost:8000/",
//请求超时
TIMEOUT: 10000,
@@ -51,12 +51,11 @@ const DEFAULT_CONFIG = {
//控制台首页默认布局
DEFAULT_GRID: {
//默认分栏数量和宽度 例如 [24] [18,6] [8,8,8] [6,12,6]
layout: [12, 6, 6],
layout: [12, 12],
//小组件分布com取值:views/home/components 文件名
copmsList: [
['welcome'],
['about', 'ver'],
['time', 'progress']
['time', 'version'],
['welcome']
]
}
}

View File

@@ -3,7 +3,7 @@ import API from "@/api";
//字典选择器配置
export default {
dicApiObj: API.system.dic.get, //获取字典接口对象
dicApiObj: API.system.dictionary.detail, //获取字典接口对象
parseData: function (res) {
return {
data: res.data, //分析行数据字段结构
@@ -15,7 +15,7 @@ export default {
name: 'name' //规定搜索字段
},
props: {
label: 'label', //映射label显示字段
value: 'value', //映射value值字段
label: 'name', //映射label显示字段
value: 'key', //映射value值字段
}
}

View File

@@ -3,15 +3,15 @@
import tool from '@/utils/tool'
export default {
successCode: 200, //请求完成代码
successCode: 1, //请求完成代码
pageSize: 20, //表格每一页条数
parseData: function (res) { //数据分析
return {
data: res.data, //分析无分页的数据字段结构
rows: res.data.rows, //分析行数据字段结构
rows: res.data.data, //分析行数据字段结构
total: res.data.total, //分析总数字段结构
summary: res.data.summary, //分析合计行字段结构
msg: res.message, //分析描述字段结构
msg: res.msg, //分析描述字段结构
code: res.code //分析状态字段结构
}
},

View File

@@ -4,7 +4,7 @@ import API from "@/api";
export default {
apiObj: API.common.upload, //上传请求API对象
successCode: 200, //请求完成代码
successCode: 1, //请求完成代码
maxSize: 10, //最大文件大小 默认10MB
parseData: function (res) {
return {

View File

@@ -26,11 +26,11 @@ export default {
},
//配置用户
user: {
apiObj: API.demo.page,
apiObj: API.system.page,
pageSize: 20,
parseData: function (res) {
return {
rows: res.data.rows,
rows: res.data.data,
total: res.data.total,
msg: res.message,
code: res.code

View File

@@ -63,40 +63,12 @@
userName: "",
userNameF: "",
msg: false,
msgList: [
{
id: 1,
type: 'user',
avatar: "img/avatar.jpg",
title: "Skuya",
describe: "如果喜欢就点个星星支持一下哦",
link: "https://gitee.com/lolicode/scui",
time: "5分钟前"
},
{
id: 2,
type: 'user',
avatar: "img/avatar2.gif",
title: "Lolowan",
describe: "点进去Gitee获取最新开源版本",
link: "https://gitee.com/lolicode/scui",
time: "14分钟前"
},
{
id: 3,
type: 'system',
avatar: "img/logo.png",
title: "感谢登录SCUI Admin",
describe: "Vue 3.0 + Vue-Router 4.0 + ElementPlus + Axios 后台管理系统。",
link: "https://gitee.com/lolicode/scui",
time: "2020年7月24日"
}
]
msgList: []
}
},
created() {
var userInfo = this.$TOOL.data.get("USER_INFO");
this.userName = userInfo.userName;
this.userName = userInfo.username;
this.userNameF = this.userName.substring(0,1);
},
methods: {

View File

@@ -1,7 +1,7 @@
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/display.css'
import scui from './scui'
import sent from './sent'
import i18n from './locales'
import router from './router'
import store from './store'
@@ -14,7 +14,7 @@ app.use(store);
app.use(router);
app.use(ElementPlus, {size: 'default'});
app.use(i18n);
app.use(scui);
app.use(sent);
//挂载app
app.mount('#app');

View File

@@ -0,0 +1,48 @@
<template>
<div v-if="pageLoading">
<el-main>
<el-card shadow="never">
<el-skeleton :rows="1"></el-skeleton>
</el-card>
<el-card shadow="never" style="margin-top: 15px;">
<el-skeleton></el-skeleton>
</el-card>
</el-main>
</div>
<work v-if="dashboard=='1'" @on-mounted="onMounted"></work>
<widgets v-else @on-mounted="onMounted"></widgets>
</template>
<script>
import { defineAsyncComponent } from 'vue';
const work = defineAsyncComponent(() => import('./work'));
const widgets = defineAsyncComponent(() => import('./widgets'));
export default {
name: "dashboard",
components: {
work,
widgets
},
data(){
return {
pageLoading: true,
dashboard: '1'
}
},
created(){
this.dashboard = this.$TOOL.data.get("USER_INFO").dashboard || '1';
},
mounted(){
},
methods: {
onMounted(){
this.pageLoading = false
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,41 @@
<template>
<el-card shadow="hover" header="时钟" class="item-background">
<div class="time">
<h2>{{ time }}</h2>
<p>{{ day }}</p>
</div>
</el-card>
</template>
<script>
export default {
title: "时钟",
icon: "el-icon-clock",
description: "演示部件效果",
data() {
return {
time: '',
day: ''
}
},
mounted() {
this.showTime()
setInterval(()=>{
this.showTime()
},1000)
},
methods: {
showTime(){
this.time = this.$TOOL.dateFormat(new Date(), 'hh:mm:ss')
this.day = this.$TOOL.dateFormat(new Date(), 'yyyy年MM月dd日')
}
}
}
</script>
<style scoped>
.item-background {background: linear-gradient(to right, #8E54E9, #4776E6);color: #fff;}
.time{display: flex; justify-content: space-between;}
.time h2 {font-size: 40px;}
.time p {font-size: 14px;margin-top: 13px;opacity: 0.7;}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<el-card shadow="hover" header="版本信息">
<div style="height: 200px;text-align: center;">
<img src="img/ver.svg" style="height:130px"/>
<h2 style="margin-top: 15px;">电子通行证 {{$CONFIG.CORE_VER}}</h2>
<p style="margin-top: 5px;">最新版本 {{ver.version}}</p>
</div>
</el-card>
</template>
<script>
export default {
title: "版本信息",
icon: "el-icon-monitor",
description: "当前项目版本信息",
data() {
return {
ver: 'loading...'
}
},
mounted() {
this.getVer()
},
methods: {
async getVer(){
const ver = await this.$API.system.version.get()
this.ver = ver.data
},
golog(){
window.open("https://gitee.com/lolicode/scui/releases")
},
gogit(){
window.open("https://gitee.com/lolicode/scui")
}
}
}
</script>

View File

@@ -3,7 +3,7 @@
<div class="welcome">
<div class="logo">
<img src="img/logo.png">
<h2>欢迎体验 SCUI</h2>
<h2>南昌工程学院电子通行证管理系统</h2>
</div>
<div class="tips">
<div class="tips-item">
@@ -19,9 +19,6 @@
<div class="tips-item-message">项目目的让前端工作更快乐</div>
</div>
</div>
<div class="actions">
<el-button type="primary" icon="el-icon-check" size="large" @click="godoc">文档</el-button>
</div>
</div>
</el-card>
</template>

View File

@@ -0,0 +1,48 @@
<template>
<el-main>
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never" header="我的常用">
<myapp></myapp>
</el-card>
</el-col>
</el-row>
<el-row :gutter="15">
<el-col :lg="12">
<el-card header="门岗出校码">
<div style="margin: 30px; text-align: center;"><sc-qr-code text="http://dztxz.nit.edu.cn:81/h5/#/pages/pass/index?type=out" size="300"></sc-qr-code></div>
</el-card>
</el-col>
<el-col :lg="12">
<el-card header="门岗进校码">
<div style="margin: 30px; text-align: center;"><sc-qr-code text="http://dztxz.nit.edu.cn:81/h5/#/pages/pass/index?type=in" size="300"></sc-qr-code></div>
</el-card>
</el-col>
</el-row>
</el-main>
</template>
<script>
import myapp from './components/myapp';
export default {
components: {
myapp
},
data() {
return {
}
},
mounted(){
this.$emit('on-mounted')
},
methods: {
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,146 @@
<template>
<el-container>
<el-header>
<div class="left-panel">
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
</div>
<div class="right-panel">
<div class="right-panel-search">
<el-input v-model="search.keyword" placeholder="部门名称" clearable></el-input>
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
</div>
</div>
</el-header>
<el-main class="nopadding">
<scTable ref="table" :apiObj="apiObj" row-key="id" @selection-change="selectionChange" hidePagination>
<el-table-column type="selection" width="50"></el-table-column>
<el-table-column label="#" type="index" width="50"></el-table-column>
<el-table-column label="部门名称" prop="title" width="350"></el-table-column>
<el-table-column label="排序" prop="sort" width="150"></el-table-column>
<el-table-column label="操作" fixed="right" align="right" width="140">
<template #default="scope">
<el-button type="text" size="small" @click="table_show(scope.row, scope.$index)">查看</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" size="small" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
<template #reference>
<el-button type="text" size="small">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</scTable>
</el-main>
</el-container>
<save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSaveSuccess" @closed="dialog.save=false"></save-dialog>
<permission-dialog v-if="dialog.permission" ref="permissionDialog" @closed="dialog.permission=false"></permission-dialog>
</template>
<script>
import saveDialog from './save'
export default {
name: 'role',
components: {
saveDialog
},
data() {
return {
dialog: {
save: false,
permission: false
},
apiObj: this.$API.user.department.list,
selection: [],
search: {
keyword: null
}
}
},
methods: {
//添加
add(){
this.dialog.save = true
this.$nextTick(() => {
this.$refs.saveDialog.open()
})
},
//编辑
table_edit(row){
this.dialog.save = true
this.$nextTick(() => {
this.$refs.saveDialog.open('edit').setData(row)
})
},
//查看
table_show(row){
this.dialog.save = true
this.$nextTick(() => {
this.$refs.saveDialog.open('show').setData(row)
})
},
//删除
async table_del(row){
var reqData = {id: row.id}
var res = await this.$API.demo.post.post(reqData);
if(res.code == 200){
this.$refs.table.refresh()
this.$message.success("删除成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
}
},
//批量删除
async batch_del(){
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?如果删除项中含有子集将会被一并删除`, '提示', {
type: 'warning'
}).then(() => {
const loading = this.$loading();
this.$refs.table.refresh()
loading.close();
this.$message.success("操作成功")
}).catch(() => {
})
},
//表格选择后回调事件
selectionChange(selection){
this.selection = selection;
},
//搜索
upsearch(){
this.$refs.table.upData(this.search)
},
//根据ID获取树结构
filterTree(id){
var target = null;
function filter(tree){
tree.forEach(item => {
if(item.id == id){
target = item
}
if(item.children){
filter(item.children)
}
})
}
filter(this.$refs.table.tableData)
return target
},
//本地更新数据
handleSaveSuccess(data, mode){
if(mode=='add'){
this.$refs.table.refresh()
}else if(mode=='edit'){
this.$refs.table.refresh()
}
}
}
}
</script>

View File

@@ -0,0 +1,118 @@
<template>
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
<el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px" label-position="left">
<el-form-item label="上级部门" prop="parent_id">
<el-cascader v-model="form.parent_id" :options="groups" :props="groupsProps" :show-all-levels="false" clearable style="width: 100%;"></el-cascader>
</el-form-item>
<el-form-item label="部门名称" prop="title">
<el-input v-model="form.title" clearable></el-input>
</el-form-item>
<el-form-item label="部门别名" prop="identify">
<el-input v-model="form.identify" clearable></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="form.sort" controls-position="right" :min="1" style="width: 100%;"></el-input-number>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible=false" > </el-button>
<el-button v-if="mode!='show'" type="primary" :loading="isSaveing" @click="submit()"> </el-button>
</template>
</el-dialog>
</template>
<script>
export default {
emits: ['success', 'closed'],
data() {
return {
mode: "add",
titleMap: {
add: '新增',
edit: '编辑',
show: '查看'
},
visible: false,
isSaveing: false,
//表单数据
form: {
id:"",
title: "",
identify: "",
sort: 1,
parent_id: ""
},
//验证规则
rules: {
sort: [
{required: true, message: '请输入排序', trigger: 'change'}
],
label: [
{required: true, message: '请输入角色名称'}
],
alias: [
{required: true, message: '请输入角色别名'}
]
},
//所需数据选项
groups: [],
groupsProps: {
value: "id",
label: "title",
emitPath: false,
checkStrictly: true
}
}
},
mounted() {
this.getGroup()
},
methods: {
//显示
open(mode='add'){
this.mode = mode;
this.visible = true;
return this
},
//加载树数据
async getGroup(){
var res = await this.$API.user.department.list.get();
this.groups = res.data;
},
//表单提交方法
submit(){
this.$refs.dialogForm.validate(async (valid) => {
if (valid) {
this.isSaveing = true;
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
var res = {}
if(this.mode == 'add'){
res = await this.$API.user.department.add.post(this.form);
}else{
res = await this.$API.user.department.edit.post(this.form);
}
this.isSaveing = false;
if(res.code == 1){
this.$emit('success', this.form, this.mode)
this.visible = false;
this.$message.success("操作成功")
}else{
this.$alert(res.msg, "提示", {type: 'error'})
}
}
})
},
//表单注入数据
setData(data){
this.form.id = data.id
this.form.title = data.title
this.form.identify = data.identify
this.form.sort = data.sort
this.form.parent_id = data.parent_id
//可以和上面一样单个注入,也可以像下面一样直接合并进去
//Object.assign(this.form, data)
}
}
}
</script>

View File

@@ -7,8 +7,8 @@
<el-form-item label="字典名称" prop="name">
<el-input v-model="form.name" clearable placeholder="字典显示名称"></el-input>
</el-form-item>
<el-form-item label="父路径" prop="parentId">
<el-cascader v-model="form.parentId" :options="dic" :props="dicProps" :show-all-levels="false" clearable></el-cascader>
<el-form-item label="父路径" prop="parent_id">
<el-cascader v-model="form.parent_id" :options="dic" :props="dicProps" :show-all-levels="false" clearable></el-cascader>
</el-form-item>
</el-form>
<template #footer>
@@ -25,8 +25,8 @@
return {
mode: "add",
titleMap: {
add: '新增字典',
edit: '编辑字典'
add: '新增字典分类',
edit: '编辑字典分类'
},
visible: false,
isSaveing: false,
@@ -34,7 +34,7 @@
id:"",
name: "",
code: "",
parentId: ""
parent_id: ""
},
rules: {
code: [
@@ -65,7 +65,7 @@
},
//
async getDic(){
var res = await this.$API.system.dic.tree.get();
var res = await this.$API.system.dictionary.category.get();
this.dic = res.data;
},
//
@@ -73,14 +73,21 @@
this.$refs.dialogForm.validate(async (valid) => {
if (valid) {
this.isSaveing = true;
var res = await this.$API.demo.post.post(this.form);
var res = {};
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
if(this.mode == 'add'){
res = await this.$API.system.dictionary.addcate.post(this.form);
}else{
res = await this.$API.system.dictionary.editcate.post(this.form);
}
this.isSaveing = false;
if(res.code == 200){
if(res.code == 1){
this.$emit('success', this.form, this.mode)
this.visible = false;
this.$message.success("操作成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
this.$alert(res.msg, "提示", {type: 'error'})
}
}
})
@@ -90,10 +97,7 @@
this.form.id = data.id
this.form.name = data.name
this.form.code = data.code
this.form.parentId = data.parentId
//
//Object.assign(this.form, data)
this.form.parent_id = data.parent_id
}
}
}

View File

@@ -0,0 +1,317 @@
<template>
<el-container>
<el-aside width="300px" v-loading="showDicloading">
<el-container>
<el-header>
<el-input placeholder="输入关键字进行过滤" v-model="dicFilterText" clearable></el-input>
</el-header>
<el-main class="nopadding">
<el-tree ref="dic" class="menu" node-key="id" :data="dicList" :props="dicProps" :highlight-current="true" :expand-on-click-node="false" :filter-node-method="dicFilterNode" @node-click="dicClick">
<template #default="{node, data}">
<span class="custom-tree-node">
<span class="label">{{ node.label }}</span>
<span class="code">{{ data.code }}</span>
<span class="do">
<el-icon :size="26" @click.stop="dicEdit(data)"><el-icon-edit /></el-icon>
<el-icon :size="26" @click.stop="dicDel(node, data)"><el-icon-delete /></el-icon>
</span>
</span>
</template>
</el-tree>
</el-main>
<el-footer style="height:51px;">
<el-button type="primary" size="small" icon="el-icon-plus" style="width: 100%;" @click="addDic">字典分类</el-button>
</el-footer>
</el-container>
</el-aside>
<el-container class="is-vertical">
<el-header>
<div class="left-panel">
<el-button type="primary" icon="el-icon-plus" @click="addInfo"></el-button>
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
</div>
</el-header>
<el-main class="nopadding">
<scTable ref="table" :apiObj="listApi" row-key="id" :params="listApiParams" @selection-change="selectionChange" stripe :paginationLayout="'prev, pager, next'">
<el-table-column type="selection" width="50"></el-table-column>
<el-table-column label="" width="60">
<template #default>
<el-tag class="move" style="cursor: move;"><el-icon-d-caret style="width: 1em; height: 1em;"/></el-tag>
</template>
</el-table-column>
<el-table-column label="名称" prop="name" width="150"></el-table-column>
<el-table-column label="键值" prop="key" width="150"></el-table-column>
<el-table-column label="是否有效" prop="yx" width="100">
<template #default="scope">
<el-switch v-model="scope.row.status" @change="changeSwitch($event, scope.row)" :loading="scope.row.$switch_status" active-value="1" inactive-value="0"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" align="right" width="140">
<template #default="scope">
<el-button type="text" size="small" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
<template #reference>
<el-button type="text" size="small">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</scTable>
</el-main>
</el-container>
</el-container>
<dic-dialog v-if="dialog.dic" ref="dicDialog" @success="handleDicSuccess" @closed="dialog.dic=false"></dic-dialog>
<list-dialog v-if="dialog.list" ref="listDialog" @success="handleListSuccess" @closed="dialog.list=false"></list-dialog>
</template>
<script>
import dicDialog from './dic'
import listDialog from './list'
import Sortable from 'sortablejs'
export default {
name: 'dic',
components: {
dicDialog,
listDialog
},
data() {
return {
dialog: {
dic: false,
info: false
},
showDicloading: true,
dicList: [],
dicFilterText: '',
dicProps: {
label: 'name'
},
listApi: this.$API.system.dictionary.list,
listApiParams: {},
selection: []
}
},
watch: {
dicFilterText(val) {
this.$refs.dic.filter(val);
}
},
mounted() {
this.getDic()
this.rowDrop()
},
methods: {
//加载树数据
async getDic(){
var res = await this.$API.system.dictionary.category.get();
this.showDicloading = false;
this.dicList = res.data;
//获取第一个节点,设置选中 & 加载明细列表
var firstNode = this.dicList[0];
if(firstNode){
this.$nextTick(() => {
this.$refs.dic.setCurrentKey(firstNode.id)
})
this.listApiParams = {
code: firstNode.code
}
this.$refs.table.reload(this.listApiParams);
}
},
//树过滤
dicFilterNode(value, data){
if (!value) return true;
var targetText = data.name + data.code;
return targetText.indexOf(value) !== -1;
},
//树增加
addDic(){
this.dialog.dic = true
this.$nextTick(() => {
this.$refs.dicDialog.open()
})
},
//编辑树
dicEdit(data){
this.dialog.dic = true
this.$nextTick(() => {
var editNode = this.$refs.dic.getNode(data.id);
var editNodeParentId = editNode.level==1?undefined:editNode.parent.data.id
data.parent_id = editNodeParentId
this.$refs.dicDialog.open('edit').setData(data)
})
},
//树点击事件
dicClick(data){
this.$refs.table.reload({
code: data.code
})
},
//删除树
dicDel(node, data){
this.$confirm(`确定删除 ${data.name} 项吗?`, '提示', {
type: 'warning'
}).then(() => {
this.showDicloading = true;
this.$API.system.dictionary.delCate.post({id: data.id});
//删除节点是否为高亮当前 是的话 设置第一个节点高亮
var dicCurrentKey = this.$refs.dic.getCurrentKey();
this.$refs.dic.remove(data.id)
if(dicCurrentKey == data.id){
var firstNode = this.dicList[0];
if(firstNode){
this.$refs.dic.setCurrentKey(firstNode.id);
this.$refs.table.upData({
code: firstNode.code
})
}else{
this.listApi = null;
this.$refs.table.tableData = []
}
}
this.showDicloading = false;
this.$message.success("操作成功")
}).catch(() => {
})
},
//行拖拽
rowDrop(){
const _this = this
const tbody = this.$refs.table.$el.querySelector('.el-table__body-wrapper tbody')
Sortable.create(tbody, {
handle: ".move",
animation: 300,
ghostClass: "ghost",
onEnd({ newIndex, oldIndex }) {
const tableData = _this.$refs.table.tableData
const currRow = tableData.splice(oldIndex, 1)[0]
tableData.splice(newIndex, 0, currRow)
_this.$message.success("排序成功")
}
})
},
//添加明细
addInfo(){
this.dialog.list = true
this.$nextTick(() => {
var dicCurrentKey = this.$refs.dic.getCurrentKey();
const data = {
dic: dicCurrentKey
}
this.$refs.listDialog.open().setData(data)
})
},
//编辑明细
table_edit(row){
this.dialog.list = true
this.$nextTick(() => {
this.$refs.listDialog.open('edit').setData(row)
})
},
//删除明细
async table_del(row, index){
var reqData = {id: row.id}
var res = await this.$API.system.dictionary.delete.post(reqData);
if(res.code == 200){
this.$refs.table.tableData.splice(index, 1);
this.$message.success("删除成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
}
},
//批量删除
async batch_del(){
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?`, '提示', {
type: 'warning'
}).then(() => {
const loading = this.$loading();
this.selection.forEach(item => {
this.$refs.table.tableData.forEach((itemI, indexI) => {
if (item.id === itemI.id) {
this.$refs.table.tableData.splice(indexI, 1)
}
})
})
loading.close();
this.$message.success("操作成功")
}).catch(() => {
})
},
//提交明细
saveList(){
this.$refs.listDialog.submit(async (formData) => {
this.isListSaveing = true;
var res = await this.$API.system.dictionary.post.post(formData);
this.isListSaveing = false;
if(res.code == 1){
//这里选择刷新整个表格 OR 插入/编辑现有表格数据
this.listDialogVisible = false;
this.$message.success("操作成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
}
})
},
//表格选择后回调事件
selectionChange(selection){
this.selection = selection;
},
//表格内开关事件
changeSwitch(val, row){
this.$API.system.dictionary.edit.post({id: row.id, status: val});
},
//本地更新数据
handleDicSuccess(data, mode){
if(mode=='add'){
if(this.dicList.length > 0){
this.$refs.table.upData({
code: data.code
})
}else{
this.listApiParams = {
code: data.code
}
this.listApi = this.$API.system.dictionary.list;
}
this.$refs.dic.append(data, data.parent_id)
this.$refs.dic.setCurrentKey(data.id)
}else if(mode=='edit'){
var editNode = this.$refs.dic.getNode(data.id);
//判断是否移动?
var editNodeParentId = editNode.level==1 ? undefined : editNode.parent.data.id
console.log(editNodeParentId)
if(editNodeParentId != data.parent_id){
var obj = editNode.data;
this.$refs.dic.remove(data.id)
this.$refs.dic.append(obj, data.parent_id)
}
Object.assign(editNode.data, data)
this.$refs.table.refresh()
}
},
//本地更新数据
handleListSuccess(){
this.$refs.table.refresh()
}
}
}
</script>
<style scoped>
.custom-tree-node {display: flex;flex: 1;align-items: center;justify-content: space-between;font-size: 14px;padding-right: 24px;height:100%;}
.custom-tree-node .code {font-size: 12px;color: #999;}
.custom-tree-node .do {display: none;}
.custom-tree-node .do i {margin-left:5px;color: #999;padding:5px;}
.custom-tree-node .do i:hover {color: #333;}
.custom-tree-node:hover .code {display: none;}
.custom-tree-node:hover .do {display: inline-block;}
</style>

View File

@@ -0,0 +1,117 @@
<template>
<el-dialog :title="titleMap[mode]" v-model="visible" :width="400" destroy-on-close @closed="$emit('closed')">
<el-form :model="form" :rules="rules" ref="dialogForm" label-width="100px" label-position="left">
<el-form-item label="所属字典" prop="dic_type">
<el-cascader v-model="form.dic_type" :options="dic" :props="dicProps" :show-all-levels="false" clearable></el-cascader>
</el-form-item>
<el-form-item label="项名称" prop="name">
<el-input v-model="form.name" clearable></el-input>
</el-form-item>
<el-form-item label="键值" prop="key">
<el-input v-model="form.key" clearable></el-input>
</el-form-item>
<el-form-item label="是否有效" prop="status">
<el-switch v-model="form.status" active-value="1" inactive-value="0"></el-switch>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible=false" > </el-button>
<el-button type="primary" :loading="isSaveing" @click="submit()"> </el-button>
</template>
</el-dialog>
</template>
<script>
export default {
emits: ['success', 'closed'],
data() {
return {
mode: "add",
titleMap: {
add: '新增项',
edit: '编辑项'
},
visible: false,
isSaveing: false,
form: {
id: "",
dic_type: "",
name: "",
key: "",
status: "1"
},
rules: {
dic_type: [
{required: true, message: '请选择所属字典'}
],
name: [
{required: true, message: '请输入项名称'}
],
key: [
{required: true, message: '请输入键值'}
]
},
dic: [],
dicProps: {
value: "code",
label: "name",
emitPath: false,
checkStrictly: true
}
}
},
mounted() {
if(this.params){
this.form.dic = this.params.code
}
this.getDic()
},
methods: {
//显示
open(mode='add'){
this.mode = mode;
this.visible = true;
return this;
},
//获取字典列表
async getDic(){
var res = await this.$API.system.dictionary.category.get();
this.dic = res.data;
},
//表单提交方法
submit(){
this.$refs.dialogForm.validate(async (valid) => {
if (valid) {
this.isSaveing = true;
var res;
if(this.mode == 'add'){
res = await this.$API.system.dictionary.add.post(this.form);
}else{
res = await this.$API.system.dictionary.edit.post(this.form);
}
this.isSaveing = false;
if(res.code == 1){
this.$emit('success', this.form, this.mode)
this.visible = false;
this.$message.success("操作成功")
}else{
this.$alert(res.msg, "提示", {type: 'error'})
}
}
})
},
//表单注入数据
setData(data){
this.form.id = data.id
this.form.name = data.name
this.form.key = data.key
this.form.status = data.status
this.form.dic_type = data.dic_type
return this;
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,103 @@
<template>
<el-container>
<el-aside width="220px">
<el-tree ref="category" class="menu" node-key="label" :data="category" :default-expanded-keys="['系统日志']" current-node-key="系统日志" :highlight-current="true" :expand-on-click-node="false" @node-click="groupClick">
</el-tree>
</el-aside>
<el-container>
<el-main class="nopadding">
<el-container>
<el-header>
<div class="left-panel">
<el-date-picker v-model="date" type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
</div>
<div class="right-panel">
</div>
</el-header>
<el-main class="nopadding">
<scTable ref="table" :apiObj="apiObj" stripe highlightCurrentRow @row-click="rowClick">
<el-table-column label="级别" prop="code" width="60">
<template #default="scope">
<el-icon v-if="scope.row.code=='500'" style="color: #F56C6C;"><el-icon-circle-close-filled /></el-icon>
<el-icon v-if="scope.row.code!='200' && scope.row.code!='500'" style="color: #E6A23C;"><el-icon-warning-filled /></el-icon>
<el-icon v-if="scope.row.code=='200'" style="color: #409EFF;"><el-icon-info-filled /></el-icon>
</template>
</el-table-column>
<el-table-column label="ID" prop="id" width="100"></el-table-column>
<el-table-column label="日志名" prop="title" width="150"></el-table-column>
<el-table-column label="请求接口" prop="route" width="220"></el-table-column>
<el-table-column label="请求方法" prop="method" width="150"></el-table-column>
<el-table-column label="用户" prop="nickname" width="150"></el-table-column>
<el-table-column label="客户端IP" prop="client_ip" width="220"></el-table-column>
<el-table-column label="日志时间" prop="create_time" width="170"></el-table-column>
</scTable>
</el-main>
</el-container>
</el-main>
</el-container>
</el-container>
<el-drawer v-model="infoDrawer" title="日志详情" :size="600" destroy-on-close>
<info ref="info"></info>
</el-drawer>
</template>
<script>
import info from './info'
export default {
name: 'log',
components: {
info
},
data() {
return {
infoDrawer: false,
category: [
{
label: '系统日志',
children: [
{label: 'GET', name: 'get'},
{label: 'POST', name: 'post'},
{label: 'DELETE', name: 'delete'},
{label: 'PUT', name: 'put'}
]
}
// ,{
// label: '应用日志',
// children: [
// {label: 'selfHelp'},
// {label: 'WechatApp'}
// ]
// }
],
date: [],
apiObj: this.$API.system.log.list,
search: {
keyword: ""
}
}
},
methods: {
upsearch(){
},
rowClick(row){
this.infoDrawer = true
this.$nextTick(() => {
this.$refs.info.setData(row)
})
},
groupClick(data){
var params = {
method: data.name || 0
}
this.$refs.table.reload(params)
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,47 @@
<template>
<el-main style="padding:0 20px;">
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="请求接口">{{data.route}}</el-descriptions-item>
<el-descriptions-item label="请求方法">{{data.method}}</el-descriptions-item>
<el-descriptions-item label="状态代码">{{data.code}}</el-descriptions-item>
<el-descriptions-item label="日志名">{{data.title}}</el-descriptions-item>
<el-descriptions-item label="日志时间">{{data.create_time}}</el-descriptions-item>
</el-descriptions>
<el-collapse v-model="activeNames" style="margin-top: 20px;">
<el-collapse-item title="请求参数" name="1">
<div class="code">{{data.params}}</div>
</el-collapse-item>
<el-collapse-item title="详细" name="2">
<div class="code">
{{data.browser}}
</div>
</el-collapse-item>
</el-collapse>
</el-main>
</template>
<script>
export default {
data() {
return {
data: {},
activeNames: ['1'],
typeMap: {
'info': "info",
'warn': "warning",
'error': "error"
}
}
},
methods: {
setData(data){
this.data = data
}
}
}
</script>
<style scoped>
.code {background: #848484;padding:15px;color: #fff;font-size: 12px;border-radius: 4px;}
</style>

View File

@@ -0,0 +1,162 @@
<template>
<el-container>
<el-aside width="300px" v-loading="menuloading">
<el-container>
<el-header>
<el-input placeholder="输入关键字进行过滤" v-model="menuFilterText" clearable></el-input>
</el-header>
<el-main class="nopadding">
<el-tree ref="menu" class="menu" node-key="id" :data="menuList" :props="menuProps" draggable highlight-current :expand-on-click-node="false" check-strictly show-checkbox :filter-node-method="menuFilterNode" @node-click="menuClick" @node-drop="nodeDrop">
<template #default="{node, data}">
<span class="custom-tree-node el-tree-node__label">
<span class="label">
{{ node.label }}
</span>
<span class="do">
<el-icon @click.stop="add(node, data)"><el-icon-plus /></el-icon>
</span>
</span>
</template>
</el-tree>
</el-main>
<el-footer style="height:51px;">
<el-button type="primary" size="small" icon="el-icon-plus" @click="add()"></el-button>
<el-button type="danger" size="small" plain icon="el-icon-delete" @click="delMenu"></el-button>
</el-footer>
</el-container>
</el-aside>
<el-container>
<el-main class="nopadding" style="padding:20px;" ref="main">
<save ref="save" :menu="menuList"></save>
</el-main>
</el-container>
</el-container>
</template>
<script>
let newMenuIndex = 1;
import save from './save'
export default {
name: "settingMenu",
components: {
save
},
data(){
return {
menuloading: false,
menuList: [],
menuProps: {
label: (data)=>{
return data.title
}
},
menuFilterText: ""
}
},
watch: {
menuFilterText(val){
this.$refs.menu.filter(val);
}
},
mounted() {
this.getMenu();
},
methods: {
//加载树数据
async getMenu(){
this.menuloading = true
var res = await this.$API.system.menu.list.get();
this.menuloading = false
this.menuList = res.data;
},
//树点击
menuClick(data, node){
var pid = node.level==1?undefined:node.parent.data.id;
this.$refs.save.setData(data, pid)
this.$refs.main.$el.scrollTop = 0
},
//树过滤
menuFilterNode(value, data){
if (!value) return true;
var targetText = data.meta.title;
return targetText.indexOf(value) !== -1;
},
//树拖拽
nodeDrop(draggingNode, dropNode, dropType){
this.$refs.save.setData({})
this.$message(`拖拽对象:${draggingNode.data.meta.title}, 释放对象:${dropNode.data.meta.title}, 释放对象的位置:${dropType}`)
},
//增加
async add(node, data){
var newMenuName = "新菜单" + newMenuIndex++;
var newMenuData = {
parent_id: data ? data.id : "",
name: newMenuName,
path: "",
component: "",
title: newMenuName,
type: "menu"
}
this.menuloading = true
var res = await this.$API.system.menu.add.post(newMenuData)
this.menuloading = false
newMenuData.id = res.data.id
this.$refs.menu.append(newMenuData, node)
this.$refs.menu.setCurrentKey(newMenuData.id)
var pid = node ? node.data.id : ""
this.$refs.save.setData(newMenuData, pid)
},
//删除菜单
async delMenu(){
var CheckedNodes = this.$refs.menu.getCheckedNodes()
if(CheckedNodes.length == 0){
this.$message.warning("请选择需要删除的项")
return false;
}
var confirm = await this.$confirm('确认删除已选择的菜单吗?','提示', {
type: 'warning',
confirmButtonText: '删除',
confirmButtonClass: 'el-button--danger'
}).catch(() => {})
if(confirm != 'confirm'){
return false
}
this.menuloading = true
var reqData = {
ids: CheckedNodes.map(item => item.id)
}
var res = await this.$API.system.menu.delete.post(reqData)
this.menuloading = false
if(res.code == 1){
CheckedNodes.forEach(item => {
var node = this.$refs.menu.getNode(item)
if(node.isCurrent){
this.$refs.save.setData({})
}
this.$refs.menu.remove(item)
})
}else{
this.$message.warning(res.msg)
}
}
}
}
</script>
<style scoped>
.custom-tree-node {display: flex;flex: 1;align-items: center;justify-content: space-between;font-size: 14px;padding-right: 24px;height:100%;}
.custom-tree-node .label {display: flex;align-items: center;;height: 100%;}
.custom-tree-node .label .el-tag {margin-left: 5px;}
.custom-tree-node .do {display: none;}
.custom-tree-node .do i {margin-left:5px;color: #999;}
.custom-tree-node .do i:hover {color: #333;}
.custom-tree-node:hover .do {display: inline-block;}
</style>

View File

@@ -7,14 +7,14 @@
<el-col :lg="12">
<h2>{{form.meta.title || "新增菜单"}}</h2>
<el-form :model="form" :rules="rules" ref="dialogForm" label-width="80px" label-position="left">
<el-form-item label="显示名称" prop="meta.title">
<el-input v-model="form.meta.title" clearable placeholder="菜单显示名字"></el-input>
<el-form-item label="显示名称" prop="title">
<el-input v-model="form.title" clearable placeholder="菜单显示名字"></el-input>
</el-form-item>
<el-form-item label="上级菜单" prop="parentId">
<el-cascader v-model="form.parentId" :options="menuOptions" :props="menuProps" :show-all-levels="false" placeholder="顶级菜单" clearable disabled></el-cascader>
</el-form-item>
<el-form-item label="类型" prop="meta.type">
<el-radio-group v-model="form.meta.type">
<el-form-item label="类型" prop="type">
<el-radio-group v-model="form.type">
<el-radio-button label="menu">菜单</el-radio-button>
<el-radio-button label="iframe">Iframe</el-radio-button>
<el-radio-button label="link">外链</el-radio-button>
@@ -25,8 +25,8 @@
<el-input v-model="form.name" clearable placeholder="菜单别名"></el-input>
<div class="el-form-item-msg">系统唯一且与内置组件名一致否则导致缓存失效如类型为Iframe的菜单别名将代替源地址显示在地址栏</div>
</el-form-item>
<el-form-item label="菜单图标" prop="meta.icon">
<sc-icon-select v-model="form.meta.icon" clearable></sc-icon-select>
<el-form-item label="菜单图标" prop="icon">
<sc-icon-select v-model="form.icon" clearable></sc-icon-select>
</el-form-item>
<el-form-item label="路由地址" prop="path">
<el-input v-model="form.path" clearable placeholder=""></el-input>
@@ -34,25 +34,32 @@
<el-form-item label="重定向" prop="redirect">
<el-input v-model="form.redirect" clearable placeholder=""></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="form.sort" class="mx-4" :min="0" :max="100" controls-position="right" clearable />
</el-form-item>
<el-form-item label="菜单高亮" prop="active">
<el-input v-model="form.active" clearable placeholder=""></el-input>
<div class="el-form-item-msg">子节点或详情页需要高亮的上级菜单路由地址</div>
</el-form-item>
<el-form-item label="视图" prop="component">
<el-input v-model="form.component" clearable placeholder="">
<template #prepend>views/</template>
<template #prepend>pages/</template>
</el-input>
<div class="el-form-item-msg">如父节点链接或Iframe等没有视图的菜单不需要填写</div>
</el-form-item>
<el-form-item label="颜色" prop="color">
<el-color-picker v-model="form.meta.color" :predefine="predefineColors"></el-color-picker>
<el-color-picker v-model="form.color" :predefine="predefineColors"></el-color-picker>
</el-form-item>
<el-form-item label="是否隐藏" prop="meta.hidden">
<el-checkbox v-model="form.meta.hidden">隐藏菜单</el-checkbox>
<el-checkbox v-model="form.meta.hiddenBreadcrumb">隐藏面包屑</el-checkbox>
<el-form-item label="是否隐藏" prop="hidden">
<el-checkbox v-model="form.hidden">隐藏菜单</el-checkbox>
<el-checkbox v-model="form.hiddenBreadcrumb">隐藏面包屑</el-checkbox>
<div class="el-form-item-msg">菜单不显示在导航中但用户依然可以访问例如详情页</div>
</el-form-item>
<el-form-item label="是否固定" prop="affix">
<el-switch v-model="form.affix" />
<div class="el-form-item-msg">是否固定类似首页控制台在标签中是没有关闭按钮的</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save" :loading="loading"> </el-button>
</el-form-item>
@@ -98,13 +105,13 @@
path: "",
component: "",
redirect: "",
meta:{
title: "",
icon: "",
active: "",
color: "",
type: "menu"
},
sort: 0,
title: "",
icon: "",
active: "",
color: "",
type: "menu",
affix: false,
apiList: []
},
menuOptions: [],
@@ -149,7 +156,7 @@
var obj = {
id: item.id,
parentId: item.parentId,
title: item.meta.title,
title: item.title,
children: item.children&&item.children.length>0 ? this.treeToMap(item.children) : null
}
map.push(obj)
@@ -159,19 +166,27 @@
//
async save(){
this.loading = true
var res = await this.$API.demo.post.post(this.form)
this.loading = false
if(res.code == 200){
this.$message.success("保存成功")
let res = {};
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
if(this.form.id){
res = await this.$API.system.menu.edit.post(this.form)
}else{
this.$message.warning(res.message)
res = await this.$API.system.menu.add.post(this.form)
}
this.loading = false
if(res.code == 1){
this.$message.success("保存成功")
this.$TOOL.data.set("MENU", res.data)
}else{
this.$message.warning(res.msg)
}
},
//
setData(data, pid){
this.form = data
this.form.apiList = data.apiList || []
this.form.parentId = pid
this.form.parent_id = pid
}
}
}

View File

@@ -0,0 +1,160 @@
<template>
<el-container>
<el-header>
<div class="left-panel">
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
<el-button type="primary" plain :disabled="selection.length!=1" @click="permission">权限设置</el-button>
</div>
<div class="right-panel">
<div class="right-panel-search">
<el-input v-model="search.keyword" placeholder="角色名称" clearable></el-input>
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
</div>
</div>
</el-header>
<el-main class="nopadding">
<scTable ref="table" :apiObj="apiObj" row-key="id" @selection-change="selectionChange" hidePagination>
<el-table-column type="selection" width="50"></el-table-column>
<el-table-column label="#" type="index" width="50"></el-table-column>
<el-table-column label="角色名称" prop="title" width="250"></el-table-column>
<el-table-column label="别名" prop="identify" width="150"></el-table-column>
<el-table-column label="排序" prop="sort" width="150"></el-table-column>
<el-table-column label="操作" fixed="right" align="right" width="140">
<template #default="scope">
<el-button type="text" size="small" @click="table_show(scope.row, scope.$index)">查看</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" size="small" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
<template #reference>
<el-button type="text" size="small">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</scTable>
</el-main>
</el-container>
<save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSaveSuccess" @closed="dialog.save=false"></save-dialog>
<permission-dialog v-if="dialog.permission" ref="permissionDialog" @closed="dialog.permission=false" @success="permissionSuccess"></permission-dialog>
</template>
<script>
import saveDialog from './save'
import permissionDialog from './permission'
export default {
name: 'role',
components: {
saveDialog,
permissionDialog
},
data() {
return {
dialog: {
save: false,
permission: false
},
apiObj: this.$API.user.role.list,
selection: [],
search: {
keyword: null
}
}
},
methods: {
//添加
add(){
this.dialog.save = true
this.$nextTick(() => {
this.$refs.saveDialog.open()
})
},
//编辑
table_edit(row){
this.dialog.save = true
this.$nextTick(() => {
this.$refs.saveDialog.open('edit').setData(row)
})
},
//查看
table_show(row){
this.dialog.save = true
this.$nextTick(() => {
this.$refs.saveDialog.open('show').setData(row)
})
},
//权限设置
permission(){
this.dialog.permission = true
this.$nextTick(() => {
this.$refs.permissionDialog.open().setData(this.selection)
})
},
//删除
async table_del(row){
var reqData = {id: row.id}
var res = await this.$API.user.role.delete.post(reqData);
if(res.code == 1){
this.$refs.table.refresh()
this.$message.success("删除成功")
}else{
this.$alert(res.msg, "提示", {type: 'error'})
}
},
//批量删除
async batch_del(){
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?如果删除项中含有子集将会被一并删除`, '提示', {
type: 'warning'
}).then(() => {
const loading = this.$loading();
this.$refs.table.refresh()
loading.close();
this.$message.success("操作成功")
}).catch(() => {
})
},
//表格选择后回调事件
selectionChange(selection){
this.selection = selection;
},
//搜索
upsearch(){
this.$refs.table.upData(this.search)
},
//根据ID获取树结构
filterTree(id){
var target = null;
function filter(tree){
tree.forEach(item => {
if(item.id == id){
target = item
}
if(item.children){
filter(item.children)
}
})
}
filter(this.$refs.table.tableData)
return target
},
//本地更新数据
handleSaveSuccess(data, mode){
if(mode=='add'){
this.$refs.table.refresh()
}else if(mode=='edit'){
this.$refs.table.refresh()
}
},
permissionSuccess(){
this.$refs.table.refresh()
}
}
}
</script>

View File

@@ -0,0 +1,144 @@
<template>
<el-dialog title="角色权限设置" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
<el-tabs tab-position="top">
<el-tab-pane label="菜单权限">
<div class="treeMain">
<el-tree ref="menu" node-key="id" :data="menu.list" :default-checked-keys="menu.checked" :props="menu.props" check-strictly show-checkbox></el-tree>
</div>
</el-tab-pane>
<el-tab-pane label="数据权限">
<el-form label-width="100px" label-position="left">
<el-form-item label="数据权限">
<sc-select dic="data_auth" v-model="form.data_range"></sc-select>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="移动端功能">
<el-form label-width="100px" label-position="left">
<el-form-item label="移动端功能">
<el-select v-model="form.mobile_module" multiple placeholder="请选择移动端模块" >
<el-option v-for="item in mobileOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="控制台">
<el-form label-width="100px" label-position="left">
<el-form-item label="控制台视图">
<el-select v-model="form.dashboard" placeholder="请选择">
<el-option v-for="item in dashboardOptions" :key="item.value" :label="item.label" :value="item.value">
<span style="float: left">{{ item.label }}</span>
<span style="float: right; color: #8492a6; font-size: 12px">{{ item.views }}</span>
</el-option>
</el-select>
<div class="el-form-item-msg">用于控制角色登录后控制台的视图</div>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button @click="visible=false" > </el-button>
<el-button type="primary" :loading="isSaveing" @click="submit()"> </el-button>
</template>
</el-dialog>
</template>
<script>
export default {
emits: ['success', 'closed'],
data() {
return {
visible: false,
isSaveing: false,
menu: {
list: [],
checked: [],
props: {
label: (data)=>{
return data.title
}
}
},
group: {
list: [],
checked: [],
props: {}
},
type: {
list: [],
checked: [],
props: {}
},
form: {
role_id: 0,
auth: [],
data_range: "",
dashboard: "work",
},
mobileOptions: [
{value: 'leave', label: '请假申请'},
{value: 'visit', label: '来访提交'},
{value: 'permit', label: '通行设置'},
{value: 'stats', label: '数据统计'}
],
dashboardOptions: [
{
value: '0',
label: '数据统计',
views: 'stats'
},
{
value: '1',
label: '工作台',
views: 'work'
},
]
}
},
mounted() {
this.getMenu();
},
methods: {
open(){
this.visible = true;
return this;
},
async submit(){
let authSelect = this.$refs.menu.getCheckedNodes();
this.form.auth = [];
authSelect.map(item => {
this.form.auth.push(item.id);
})
this.isSaveing = true;
var res = await this.$API.user.role.auth.post(this.form);
this.isSaveing = false;
if(res.code == 1){
this.$emit('success', this.form)
this.visible = false;
this.$message.success("操作成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
}
},
async getMenu(){
var res = await this.$API.system.menu.list.get();
this.menu.list = res.data;
},
//表单注入数据
setData(data){
data.map(item => {
this.form.role_id = item.id;
this.form.data_range = parseInt(item.data_range);
this.form.dashboard = item.dashboard;
this.form.mobile_module = item.mobile_module;
this.menu.checked = item.permission_id;
})
}
}
}
</script>
<style scoped>
.treeMain {height:280px;overflow: auto;border: 1px solid #dcdfe6;margin-bottom: 10px;}
</style>

View File

@@ -1,14 +1,14 @@
<template>
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
<el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px" label-position="left">
<el-form-item label="上级角色" prop="parentId">
<el-cascader v-model="form.parentId" :options="groups" :props="groupsProps" :show-all-levels="false" clearable style="width: 100%;"></el-cascader>
<el-form-item label="上级角色" prop="parent_id">
<el-cascader v-model="form.parent_id" :options="groups" :props="groupsProps" :show-all-levels="false" clearable style="width: 100%;"></el-cascader>
</el-form-item>
<el-form-item label="角色名称" prop="label">
<el-input v-model="form.label" clearable></el-input>
<el-form-item label="角色名称" prop="title">
<el-input v-model="form.title" clearable></el-input>
</el-form-item>
<el-form-item label="角色别名" prop="alias">
<el-input v-model="form.alias" clearable></el-input>
<el-form-item label="角色别名" prop="identify">
<el-input v-model="form.identify" clearable></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="form.sort" controls-position="right" :min="1" style="width: 100%;"></el-input-number>
@@ -37,10 +37,10 @@
//
form: {
id:"",
label: "",
alias: "",
title: "",
identify: "",
sort: 1,
parentId: ""
parent_id: 0
},
//
rules: {
@@ -58,6 +58,7 @@
groups: [],
groupsProps: {
value: "id",
label: "title",
emitPath: false,
checkStrictly: true
}
@@ -75,7 +76,7 @@
},
//
async getGroup(){
var res = await this.$API.system.role.list.get();
var res = await this.$API.user.role.list.get();
this.groups = res.data;
},
//
@@ -83,14 +84,20 @@
this.$refs.dialogForm.validate(async (valid) => {
if (valid) {
this.isSaveing = true;
var res = await this.$API.demo.post.post(this.form);
var res = {}
this.form.parent_id = this.form.parent_id ? this.form.parent_id : 0;
if(this.mode == 'add'){
res = await this.$API.user.role.add.post(this.form);
}else{
res = await this.$API.user.role.edit.post(this.form);
}
this.isSaveing = false;
if(res.code == 200){
if(res.code == 1){
this.$emit('success', this.form, this.mode)
this.visible = false;
this.$message.success("操作成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
this.$alert(res.msg, "提示", {type: 'error'})
}
}
})
@@ -98,10 +105,10 @@
//
setData(data){
this.form.id = data.id
this.form.label = data.label
this.form.alias = data.alias
this.form.title = data.title
this.form.identify = data.identify
this.form.sort = data.sort
this.form.parentId = data.parentId
this.form.parent_id = data.parent_id
//
//Object.assign(this.form, data)

View File

@@ -0,0 +1,200 @@
<template>
<el-container>
<el-aside width="200px" v-loading="showGrouploading">
<el-container>
<el-header>
<el-input placeholder="输入关键字进行过滤" v-model="groupFilterText" clearable></el-input>
</el-header>
<el-main class="nopadding">
<el-tree ref="group" class="menu" node-key="id" :data="group" :props="{label: 'title'}" :current-node-key="''" :highlight-current="true" :expand-on-click-node="false" :filter-node-method="groupFilterNode" @node-click="groupClick"></el-tree>
</el-main>
</el-container>
</el-aside>
<el-container>
<el-header>
<div class="left-panel">
<el-button type="primary" icon="el-icon-plus" @click="add"></el-button>
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
<el-button type="primary" plain :disabled="selection.length!=1" @click="roleSet">分配角色</el-button>
<el-button type="primary" plain @click="insertData" v-if="false">数据导入</el-button>
</div>
<div class="right-panel">
<div class="right-panel-search">
<el-input v-model="search.name" placeholder="登录账号 / 姓名" clearable></el-input>
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
</div>
</div>
</el-header>
<el-main class="nopadding">
<scTable ref="table" :apiObj="apiObj" @selection-change="selectionChange" stripe remoteSort remoteFilter>
<el-table-column type="selection" width="50"></el-table-column>
<el-table-column label="头像" width="80" column-key="avatar" :filters="[{text: '已上传', value: '1'}, {text: '未上传', value: '0'}]">
<template #default="scope">
<el-avatar :src="scope.row.avatar" shape="square" size="small"></el-avatar>
</template>
</el-table-column>
<el-table-column label="UID" prop="uid" width="150" sortable='custom'></el-table-column>
<el-table-column label="登录账号" prop="username" width="150" sortable='custom' column-key="username" :filters="[{text: '系统账号', value: '1'}, {text: '普通账号', value: '0'}]"></el-table-column>
<el-table-column label="姓名" prop="nickname" width="150" sortable='custom'></el-table-column>
<el-table-column label="所属角色" prop="roleName" width="200" sortable='custom'></el-table-column>
<el-table-column label="加入时间" prop="create_time" width="150" sortable='custom'></el-table-column>
<el-table-column label="操作" fixed="right" align="right" width="140">
<template #default="scope">
<el-button type="text" size="small" @click="table_show(scope.row, scope.$index)">查看</el-button>
<el-button type="text" size="small" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
<template #reference>
<el-button type="text" size="small">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</scTable>
</el-main>
</el-container>
</el-container>
<save-dialog v-if="dialog.save" ref="saveDialog" @success="handleSuccess" @closed="dialog.save=false"></save-dialog>
<role-dialog v-if="dialog.role" ref="roleDialog" @success="handleSuccess" @closed="dialog.role=false"></role-dialog>
</template>
<script>
import saveDialog from './save'
import roleDialog from './role'
export default {
name: 'user',
components: {
saveDialog,
roleDialog
},
data() {
return {
dialog: {
save: false
},
showGrouploading: false,
groupFilterText: '',
group: [],
apiObj: this.$API.user.list,
selection: [],
search: {
name: null
}
}
},
watch: {
groupFilterText(val) {
this.$refs.group.filter(val);
}
},
mounted() {
this.getGroup()
},
methods: {
//添加
add(){
this.dialog.save = true
this.$nextTick(() => {
this.$refs.saveDialog.open()
})
},
//编辑
table_edit(row){
this.dialog.save = true
this.$nextTick(() => {
this.$refs.saveDialog.open('edit').setData(row)
})
},
//查看
table_show(row){
this.dialog.save = true
this.$nextTick(() => {
this.$refs.saveDialog.open('show').setData(row)
})
},
//删除
async table_del(row, index){
var reqData = {id: row.uid}
var res = await this.$API.user.delete.post(reqData);
if(res.code == 200){
//这里选择刷新整个表格 OR 插入/编辑现有表格数据
this.$refs.table.tableData.splice(index, 1);
this.$message.success("删除成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
}
},
//批量删除
async batch_del(){
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?`, '提示', {
type: 'warning'
}).then(() => {
const loading = this.$loading();
this.selection.forEach(item => {
this.$refs.table.tableData.forEach((itemI, indexI) => {
if (item.id === itemI.id) {
this.$refs.table.tableData.splice(indexI, 1)
}
})
})
loading.close();
this.$message.success("操作成功")
}).catch(() => {
})
},
//权限设置
roleSet(){
this.dialog.role = true
this.$nextTick(() => {
this.$refs.roleDialog.open().setData(this.selection)
})
},
insertData(){
this.dialog.insert = true
this.$nextTick(() => {
this.$refs.insertDialog.open()
})
},
//表格选择后回调事件
selectionChange(selection){
this.selection = selection;
},
//加载树数据
async getGroup(){
this.showGrouploading = true;
var res = await this.$API.user.department.list.get();
this.showGrouploading = false;
var allNode ={id: '', title: '所有'}
res.data.unshift(allNode);
this.group = res.data;
},
//树过滤
groupFilterNode(value, data){
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
//树点击事件
groupClick(data){
var params = {
department_id: data.id
}
this.$refs.table.reload(params)
},
//搜索
upsearch(){
this.$refs.table.upData(this.search)
},
//本地更新数据
handleSuccess(){
this.$refs.table.refresh()
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,81 @@
<template>
<el-dialog title="角色设置" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
<el-tabs tab-position="top">
<el-tab-pane label="角色选择">
<div class="treeMain">
<el-tree ref="role" node-key="id" :data="role.list" :default-checked-keys="role.checked" :props="role.props" check-strictly default-expand-all show-checkbox></el-tree>
</div>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button @click="visible=false" > </el-button>
<el-button type="primary" :loading="isSaveing" @click="submit()"> </el-button>
</template>
</el-dialog>
</template>
<script>
export default {
data(){
return {
visible: false,
isSaveing: false,
role: {
list: [],
checked: [],
props: {
label: (data)=>{
return data.title
}
}
},
form: {
uid: '',
role: []
}
}
},
mounted(){
this.getRole();
},
methods:{
open(){
this.visible = true;
return this;
},
async submit(){
this.isSaveing = true;
let role = this.$refs.role.getCheckedNodes();
this.form.role = [];
role.map(item => {
this.form.role.push(item.id);
})
this.isSaveing = true;
var res = await this.$API.user.uprole.post(this.form);
this.isSaveing = false;
if(res.code == 1){
this.$emit('success', this.form)
this.visible = false;
this.$message.success("操作成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
}
},
async getRole(){
let res = await this.$API.user.role.list.get();
this.role.list = res.data;
},
setData(data){
data.map(item => {
this.role.checked = item.role_id;
this.form.uid = item.uid;
})
}
}
}
</script>
<style scoped>
.treeMain {height:280px;overflow: auto;border: 1px solid #dcdfe6;margin-bottom: 10px;}
</style>

View File

@@ -2,13 +2,13 @@
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
<el-form :model="form" :rules="rules" :disabled="mode=='show'" ref="dialogForm" label-width="100px" label-position="left">
<el-form-item label="头像" prop="avatar">
<sc-upload v-model="form.avatar" title="上传头像"></sc-upload>
<sc-upload v-model="form.avatar" :cropper="true" :aspectRatio="1" title="上传头像"></sc-upload>
</el-form-item>
<el-form-item label="登录账号" prop="userName">
<el-input v-model="form.userName" placeholder="用于登录系统" clearable></el-input>
<el-form-item label="登录账号" prop="username">
<el-input v-model="form.username" placeholder="用于登录系统" clearable></el-input>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="请输入完整的真实姓名" clearable></el-input>
<el-form-item label="姓名" prop="nickname">
<el-input v-model="form.nickname" placeholder="请输入完整的真实姓名" clearable></el-input>
</el-form-item>
<template v-if="mode=='add'">
<el-form-item label="登录密码" prop="password">
@@ -18,8 +18,11 @@
<el-input type="password" v-model="form.password2" clearable show-password></el-input>
</el-form-item>
</template>
<el-form-item label="所属部门" prop="group">
<el-cascader v-model="form.department_id" :options="department" :props="departmentProps" :show-all-levels="false" clearable style="width: 100%;"></el-cascader>
</el-form-item>
<el-form-item label="所属角色" prop="group">
<el-cascader v-model="form.group" :options="groups" :props="groupsProps" :show-all-levels="false" clearable style="width: 100%;"></el-cascader>
<el-cascader v-model="form.role_id" :options="groups" :props="groupsProps" :show-all-levels="false" clearable style="width: 100%;"></el-cascader>
</el-form-item>
</el-form>
<template #footer>
@@ -48,17 +51,18 @@
userName: "",
avatar: "",
name: "",
group: ""
department_id: 0,
role_id: []
},
//
rules: {
avatar:[
{required: true, message: '请上传头像'}
],
userName: [
// avatar:[
// {required: true, message: ''}
// ],
username: [
{required: true, message: '请输入登录账号'}
],
name: [
nickname: [
{required: true, message: '请输入真实姓名'}
],
password: [
@@ -80,14 +84,22 @@
}
}}
],
group: [
role_id: [
{required: true, message: '请选择所属角色'}
]
},
//
department: [],
departmentProps: {
value: "id",
label: "title",
multiple: false,
checkStrictly: true
},
groups: [],
groupsProps: {
value: "id",
label: "title",
multiple: true,
checkStrictly: true
}
@@ -95,6 +107,7 @@
},
mounted() {
this.getGroup()
this.getDepartment()
},
methods: {
//
@@ -105,17 +118,27 @@
},
//
async getGroup(){
var res = await this.$API.system.role.list.get();
var res = await this.$API.user.role.list.get();
this.groups = res.data;
},
async getDepartment(){
var res = await this.$API.user.department.list.get();
this.department = res.data;
},
//
submit(){
this.$refs.dialogForm.validate(async (valid) => {
if (valid) {
this.isSaveing = true;
var res = await this.$API.demo.post.post(this.form);
var res = {};
if(this.mode == 'add'){
res = await this.$API.user.add.post(this.form);
}else{
res = await this.$API.user.edit.post(this.form);
}
this.isSaveing = false;
if(res.code == 200){
if(res.code == 1){
this.$emit('success', this.form, this.mode)
this.visible = false;
this.$message.success("操作成功")
@@ -129,14 +152,14 @@
},
//
setData(data){
this.form.id = data.id
this.form.userName = data.userName
this.form.avatar = data.avatar
this.form.name = data.name
this.form.group = data.group
// this.form.id = data.uid
// this.form.userName = data.username
// this.form.avatar = data.avatar
// this.form.name = data.nickname
// this.form.role_id = data.role_id
//
//Object.assign(this.form, data)
Object.assign(this.form, data)
}
}
}

View File

@@ -0,0 +1,169 @@
<template>
<el-main>
<el-row :gutter="15">
<el-col :lg="8">
<el-card shadow="never">
<div class="user-info">
<div class="user-info-top">
<el-avatar :size="80" :src="userInfo.avatar"></el-avatar>
<h2>{{ userInfo.nickname||'-' }}</h2>
<p>{{ userInfo.about||'无签名' }}</p>
<el-button type="primary" round icon="el-icon-collection-tag" size="large">{{userInfo.username}}</el-button>
</div>
<div class="user-info-main">
<ul>
<li><label><el-icon><el-icon-user /></el-icon></label><span>{{userInfo.email||'暂无邮箱'}}</span></li>
<li><label><el-icon><el-icon-male /></el-icon></label><span>{{userInfo.email||'未知'}}</span></li>
<li><label><el-icon><el-icon-office-building /></el-icon></label><span>{{userInfo.department_name||'暂无部门'}}</span></li>
</ul>
</div>
<div class="user-info-bottom">
<h2>当前账号权限</h2>
<el-space wrap>
<el-tag v-auth="'user.add'">user.add</el-tag>
<el-tag v-auth="'user.edit'">user.edit</el-tag>
<el-tag v-if="$AUTH('user.delete')">user.delete</el-tag>
<el-tag v-if="$AUTH('list.add')">list.add</el-tag>
<el-tag v-if="$AUTH('list.edit')">list.edit</el-tag>
<el-tag v-if="$AUTH('list.delete')">list.delete</el-tag>
</el-space>
</div>
</div>
</el-card>
</el-col>
<el-col :lg="16">
<el-card shadow="never">
<el-tabs tab-position="top">
<el-tab-pane :label="$t('user.dynamic')">
<el-timeline style="margin-top:20px;padding-left:10px;">
<el-timeline-item v-for="(activity, index) in activities" :key="index" :timestamp="activity.timestamp" placement="top">
<div class="activity-item">
<el-avatar class="avatar" :size="24" src="img/avatar.jpg"></el-avatar>
<label>{{activity.operate}}</label><el-tag v-if="activity.mod">{{activity.mod}}</el-tag>{{activity.describe}}
</div>
</el-timeline-item>
</el-timeline>
</el-tab-pane>
<el-tab-pane :label="$t('user.info')">
<el-form ref="form" :model="userInfo" label-width="80px" style="width: 460px;margin-top:20px;">
<el-form-item label="账号">
<el-input v-model="userInfo.username" disabled></el-input>
<div class="el-form-item-msg">账号信息用于登录系统不允许修改</div>
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="userInfo.nickname"></el-input>
</el-form-item>
<el-form-item label="性别">
<el-select v-model="userInfo.sex" placeholder="请选择">
<el-option label="保密" value="0"></el-option>
<el-option label="男" value="1"></el-option>
<el-option label="女" value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="个性签名">
<el-input v-model="userInfo.about" type="textarea"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary">保存</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane :label="$t('user.settings')">
<el-form ref="form" :model="form" label-width="120px" style="margin-top:20px;">
<el-form-item :label="$t('user.nightmode')">
<el-switch v-model="config.theme" active-value="dark" inactive-value="default" inline-prompt active-icon="el-icon-moon" inactive-icon="el-icon-sunny"></el-switch>
<div class="el-form-item-msg">{{ $t('user.nightmode_msg') }}</div>
</el-form-item>
<el-form-item label="主题颜色">
<el-color-picker v-model="config.colorPrimary" :predefine="colorList">></el-color-picker>
</el-form-item>
<el-form-item :label="$t('user.language')">
<el-select v-model="config.lang">
<el-option label="简体中文" value="zh-cn"></el-option>
<el-option label="English" value="en"></el-option>
<el-option label="日本語" value="ja"></el-option>
</el-select>
<div class="el-form-item-msg">{{ $t('user.language_msg') }}</div>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</el-main>
</template>
<script>
import colorTool from '@/utils/color'
export default {
name: 'userCenter',
data() {
return {
activities: [
{
operate: '更改了',
mod: '系统配置',
describe: 'systemName 为 SCUI',
type: 'edit',
timestamp: '刚刚'
}
],
userInfo: this.$TOOL.data.get("USER_INFO"),
colorList: ['#409EFF', '#009688', '#536dfe', '#ff5c93', '#c62f2f', '#fd726d'],
config: {
lang: this.$TOOL.data.get('APP_LANG') || this.$CONFIG.LANG,
theme: this.$TOOL.data.get('APP_THEME') || 'default',
colorPrimary: this.$TOOL.data.get('APP_COLOR') || this.$CONFIG.COLOR || '#409EFF'
}
}
},
watch:{
'config.theme'(val){
document.body.setAttribute('data-theme', val)
this.$TOOL.data.set("APP_THEME", val);
},
'config.lang'(val){
this.$i18n.locale = val
this.$TOOL.data.set("APP_LANG", val);
},
'config.colorPrimary'(val){
document.documentElement.style.setProperty('--el-color-primary', val);
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, colorTool.lighten(val,i/10));
}
document.documentElement.style.setProperty(`--el-color-primary-darken-1`, colorTool.darken(val,0.1));
this.$TOOL.data.set("APP_COLOR", val);
}
},
//路由跳转进来 判断from是否有特殊标识做特殊处理
beforeRouteEnter (to, from, next){
next((vm)=>{
if(from.is){
//删除特殊标识,防止标签刷新重复执行
delete from.is
//执行特殊方法
vm.$alert('路由跳转过来后含有特殊标识,做特殊处理', '提示', {
type: 'success',
center: true
}).then(() => {}).catch(() => {})
}
})
},
methods: {
}
}
</script>
<style scoped>
.el-card {margin-bottom:15px;}
.activity-item {font-size: 13px;color: #999;display: flex;align-items: center;}
.activity-item label {color: #333;margin-right:10px;}
.activity-item .el-avatar {margin-right:10px;}
.activity-item .el-tag {margin-right:10px;}
[data-theme='dark'] .user-info-bottom {border-color: var(--el-border-color-base);}
[data-theme='dark'] .activity-item label {color: #999;}
</style>

View File

@@ -0,0 +1,247 @@
<template>
<div class="login_bg">
<div class="login_adv" style="background-image: url(img/auth_banner.jpg);">
<div class="login_adv__title">
<h2>ThinkVue</h2>
<h4>{{ $t('login.slogan') }}</h4>
<p>{{ $t('login.describe') }}</p>
<div>
<span>
<el-icon><sc-icon-vue /></el-icon>
</span>
<span>
<el-icon class="add"><el-icon-plus /></el-icon>
</span>
<span>
<el-icon><el-icon-eleme-filled /></el-icon>
</span>
</div>
</div>
<div class="login_adv__bottom">
© {{$CONFIG.APP_NAME}} {{$CONFIG.APP_VER}}
</div>
</div>
<div class="login_main">
<div class="login_config">
<el-button :icon="config.theme=='dark'?'el-icon-sunny':'el-icon-moon'" circle type="info" @click="configTheme"></el-button>
<el-dropdown trigger="click" placement="bottom-end" @command="configLang">
<el-button circle>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 512 512"><path d="M478.33 433.6l-90-218a22 22 0 0 0-40.67 0l-90 218a22 22 0 1 0 40.67 16.79L316.66 406h102.67l18.33 44.39A22 22 0 0 0 458 464a22 22 0 0 0 20.32-30.4zM334.83 362L368 281.65L401.17 362z" fill="currentColor"></path><path d="M267.84 342.92a22 22 0 0 0-4.89-30.7c-.2-.15-15-11.13-36.49-34.73c39.65-53.68 62.11-114.75 71.27-143.49H330a22 22 0 0 0 0-44H214V70a22 22 0 0 0-44 0v20H54a22 22 0 0 0 0 44h197.25c-9.52 26.95-27.05 69.5-53.79 108.36c-31.41-41.68-43.08-68.65-43.17-68.87a22 22 0 0 0-40.58 17c.58 1.38 14.55 34.23 52.86 83.93c.92 1.19 1.83 2.35 2.74 3.51c-39.24 44.35-77.74 71.86-93.85 80.74a22 22 0 1 0 21.07 38.63c2.16-1.18 48.6-26.89 101.63-85.59c22.52 24.08 38 35.44 38.93 36.1a22 22 0 0 0 30.75-4.9z" fill="currentColor"></path></svg>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in lang" :key="item.value" :command="item" :class="{'selected':config.lang==item.value}">{{item.name}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="login-form">
<div class="login-header">
<div class="logo">
<img :alt="$CONFIG.APP_NAME" src="img/logo.png">
<label>{{$CONFIG.APP_NAME}}</label>
</div>
<h2>{{ $t('login.signInTitle') }}</h2>
</div>
<el-form ref="loginForm" :model="ruleForm" :rules="rules" label-width="0" size="large">
<el-form-item prop="user">
<el-input v-model="ruleForm.user" prefix-icon="el-icon-user" clearable :placeholder="$t('login.userPlaceholder')"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="ruleForm.password" prefix-icon="el-icon-lock" clearable show-password :placeholder="$t('login.PWPlaceholder')"></el-input>
</el-form-item>
<el-form-item style="margin-bottom: 10px;">
<el-row>
<el-col :span="12">
<el-checkbox :label="$t('login.rememberMe')" v-model="ruleForm.autologin"></el-checkbox>
</el-col>
<el-col :span="12" style="text-align: right;">
<el-button type="text">{{ $t('login.forgetPassword') }}</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width: 100%;" :loading="islogin" round @click="login">{{ $t('login.signIn') }}</el-button>
</el-form-item>
</el-form>
<el-divider>{{ $t('login.signInOther') }}</el-divider>
<div class="login-oauth">
<el-button size="small" type="success" icon="sc-icon-wechat" circle></el-button>
</div>
</div>
</div>
</div>
</template>
<script>
import {mapMutations} from 'vuex'
export default {
data() {
return {
userType: 'admin',
ruleForm: {
user: "admin",
password: "admin888",
autologin: false
},
rules: {
user: [
{required: true, message: this.$t('login.userError'), trigger: 'blur'}
],
password: [
{required: true, message: this.$t('login.PWError'), trigger: 'blur'}
]
},
islogin: false,
config: {
lang: this.$TOOL.data.get('APP_LANG') || this.$CONFIG.LANG,
theme: this.$TOOL.data.get('APP_THEME') || 'default'
},
lang: [
{
name: '简体中文',
value: 'zh-cn',
},
{
name: 'English',
value: 'en',
},
{
name: '日本語',
value: 'ja',
}
]
}
},
watch:{
userType(val){
if(val == 'admin'){
this.ruleForm.user = 'molong@tensent.cn'
this.ruleForm.password = 'password'
}else if(val == 'user'){
this.ruleForm.user = 'user'
this.ruleForm.password = 'user'
}
},
'config.theme'(val){
document.body.setAttribute('data-theme', val)
this.$TOOL.data.set("APP_THEME", val);
},
'config.lang'(val){
this.$i18n.locale = val
this.$TOOL.data.set("APP_LANG", val);
}
},
created: function() {
this.$TOOL.data.remove("TOKEN")
this.$TOOL.data.remove("USER_INFO")
this.$TOOL.data.remove("MENU")
this.$TOOL.data.remove("PERMISSIONS")
this.$TOOL.data.remove("grid")
this.$store.commit("clearViewTags")
this.$store.commit("clearKeepLive")
this.$store.commit("clearIframeList")
},
methods: {
...mapMutations('account', ['setUser', 'setPermissions', 'setRoles']),
async login(){
var validate = await this.$refs.loginForm.validate().catch(()=>{})
if(!validate){ return false }
this.islogin = true
var data = {
username: this.ruleForm.user,
// password: this.$TOOL.crypto.BASE64.encrypt(this.ruleForm.password)
password: this.ruleForm.password
}
//获取token
var user = await this.$API.auth.login.post(data)
if(user.code == 1){
this.$TOOL.data.set("TOKEN", user.data.token)
this.$TOOL.data.set("USER_INFO", user.data)
}else{
this.islogin = false
this.$message.warning(user.message)
return false
}
this.afterLogin();
},
async afterLogin(){
//获取菜单
var menu = null
if(this.ruleForm.user == 'admin'){
menu = await this.$API.system.menu.myMenus.get()
}else{
menu = await this.$API.system.menu.get()
}
if(menu.code == 1){
if(menu.data.menu.length==0){
this.islogin = false
this.$alert("当前用户无任何菜单权限,请联系系统管理员", "无权限访问", {
type: 'error',
center: true
})
return false
}
this.$TOOL.data.set("MENU", menu.data.menu)
this.$TOOL.data.set("PERMISSIONS", menu.data.permissions)
}else{
this.islogin = false
this.$message.warning(menu.message)
return false
}
this.$router.replace({
path: '/'
})
this.$message.success("Login Success 登录成功")
this.islogin = false
},
configTheme(){
this.config.theme = this.config.theme=='default'?'dark':'default'
},
configLang(command){
this.config.lang = command.value
}
}
}
</script>
<style scoped>
.login_bg {width: 100%;height: 100%;background: #fff;display: flex;}
.login_adv {width: 33.33333%;background-color: #555;background-size: cover;background-position: center center;background-repeat: no-repeat;position: relative;}
.login_adv__title {color: #fff;padding: 40px;}
.login_adv__title h2 {font-size: 40px;}
.login_adv__title h4 {font-size: 18px;margin-top: 10px;font-weight: normal;}
.login_adv__title p {font-size: 14px;margin-top:10px;line-height: 1.8;color: rgba(255,255,255,0.6);}
.login_adv__title div {margin-top: 10px;display: flex;align-items: center;}
.login_adv__title div span {margin-right: 15px;}
.login_adv__title div i {font-size: 40px;}
.login_adv__title div i.add {font-size: 20px;color: rgba(255,255,255,0.6);}
.login_adv__bottom {position: absolute;left:0px;right: 0px;bottom: 0px;color: #fff;padding: 40px;background-image:linear-gradient(transparent, #000);}
.login_main {flex: 1;overflow: auto;display:flex;}
.login-form {width: 400px;margin: auto;padding:20px 0;}
.login-header {margin-bottom: 20px;}
.login-header .logo {display: flex;align-items: center;}
.login-header .logo img {width: 35px;height: 35px;vertical-align: bottom;margin-right: 10px;}
.login-header .logo label {font-size: 24px;}
.login-header h2 {font-size: 24px;font-weight: bold;margin-top: 50px;}
.login-oauth {display: flex;justify-content:space-around;}
.login-form .el-divider {margin-top:40px;}
.login_config {position: absolute;top:20px;right: 20px;}
.el-dropdown-menu__item.selected {color: var(--el-color-primary);}
@media (max-width: 1200px){
.login-form {width: 340px;}
}
@media (max-width: 1000px){
.login_main {display: block;}
.login-form {width:100%;padding:20px 40px;}
.login_adv {display: none;}
}
</style>

View File

@@ -131,7 +131,7 @@ function filterAsyncRouter(routerMap) {
}
function loadComponent(component){
if(component){
return () => import(/* webpackChunkName: "[request]" */ `@/views/${component}`)
return () => import(/* webpackChunkName: "[request]" */ `@/pages/${component}`)
}else{
return () => import(`@/layout/other/empty`)
}

View File

@@ -11,7 +11,7 @@ const routes = [
},
{
path: "/login",
component: () => import(/* webpackChunkName: "login" */ '@/views/userCenter/login'),
component: () => import(/* webpackChunkName: "login" */ '@/pages/ucenter/login'),
meta: {
title: "登录"
}

View File

@@ -28,7 +28,7 @@ import copy from './directives/copy'
import errorHandler from './utils/errorHandler'
import * as elIcons from '@element-plus/icons'
import * as scIcons from './assets/icons'
import * as scIcons from './static/icons'
export default {
install(app) {

View File

@@ -0,0 +1,33 @@
import tool from '@/utils/tool';
export default {
state: {
user: undefined,
permissions: null,
roles: null,
routesConfig: null,
userMenu: tool.data.get("MENU") || []
},
mutations: {
setUser (state, user) {
state.user = user
localStorage.setItem(process.env.VUE_APP_USER_KEY, JSON.stringify(user))
},
setPermissions(state, permissions) {
state.permissions = permissions
localStorage.setItem(process.env.VUE_APP_PERMISSIONS_KEY, JSON.stringify(permissions))
},
setRoles(state, roles) {
state.roles = roles
localStorage.setItem(process.env.VUE_APP_ROLES_KEY, JSON.stringify(roles))
},
setRoutesConfig(state, routesConfig) {
state.routesConfig = routesConfig
localStorage.setItem(process.env.VUE_APP_ROUTES_KEY, JSON.stringify(routesConfig))
},
setUserMenu (state, menu){
state.userMenu = menu;
tool.data.set("MENU", menu)
}
}
}

View File

@@ -4,8 +4,7 @@ import sysConfig from "@/config";
import tool from '@/utils/tool';
import router from '@/router';
axios.defaults.baseURL = ''
axios.defaults.withCredentials = false
axios.defaults.timeout = sysConfig.TIMEOUT
// HTTP request 拦截器
@@ -30,6 +29,16 @@ axios.interceptors.request.use(
// HTTP response 拦截器
axios.interceptors.response.use(
(response) => {
if(response.data.code == 2000 || response.data.code == 2001){
ElMessageBox.confirm('当前用户已被登出或无权限访问当前资源,请尝试重新登录后再操作。', '无权限访问', {
type: 'error',
closeOnClickModal: false,
center: true,
confirmButtonText: '重新登录'
}).then(() => {
router.replace({path: '/login'});
}).catch(() => {})
}
return response;
},
(error) => {

View File

@@ -1,48 +0,0 @@
<template>
<div v-if="pageLoading">
<el-main>
<el-card shadow="never">
<el-skeleton :rows="1"></el-skeleton>
</el-card>
<el-card shadow="never" style="margin-top: 15px;">
<el-skeleton></el-skeleton>
</el-card>
</el-main>
</div>
<work v-if="dashboard=='1'" @on-mounted="onMounted"></work>
<widgets v-else @on-mounted="onMounted"></widgets>
</template>
<script>
import { defineAsyncComponent } from 'vue';
const work = defineAsyncComponent(() => import('./work'));
const widgets = defineAsyncComponent(() => import('./widgets'));
export default {
name: "dashboard",
components: {
work,
widgets
},
data(){
return {
pageLoading: true,
dashboard: '0'
}
},
created(){
this.dashboard = this.$TOOL.data.get("USER_INFO").dashboard || '0';
},
mounted(){
},
methods: {
onMounted(){
this.pageLoading = false
}
}
}
</script>
<style>
</style>

View File

@@ -1,40 +0,0 @@
<template>
<el-card shadow="hover" header="时钟" class="item-background">
<div class="time">
<h2>{{ time }}</h2>
<p>{{ day }}</p>
</div>
</el-card>
</template>
<script>
export default {
title: "时钟",
icon: "el-icon-clock",
description: "演示部件效果",
data() {
return {
time: '',
day: ''
}
},
mounted() {
this.showTime()
setInterval(()=>{
this.showTime()
},1000)
},
methods: {
showTime(){
this.time = this.$TOOL.dateFormat(new Date(), 'hh:mm:ss')
this.day = this.$TOOL.dateFormat(new Date(), 'yyyy年MM月dd日')
}
}
}
</script>
<style scoped>
.item-background {background: linear-gradient(to right, #8E54E9, #4776E6);color: #fff;}
.time h2 {font-size: 40px;}
.time p {font-size: 14px;margin-top: 13px;opacity: 0.7;}
</style>

View File

@@ -1,41 +0,0 @@
<template>
<el-card shadow="hover" header="版本信息">
<div style="height: 210px;text-align: center;">
<img src="img/ver.svg" style="height:140px"/>
<h2 style="margin-top: 15px;">SCUI {{$CONFIG.CORE_VER}}</h2>
<p style="margin-top: 5px;">最新版本 {{ver}}</p>
</div>
<div style="margin-top: 20px;">
<el-button type="primary" plain round @click="golog">更新日志</el-button>
<el-button type="primary" plain round @click="gogit">gitee</el-button>
</div>
</el-card>
</template>
<script>
export default {
title: "版本信息",
icon: "el-icon-monitor",
description: "当前项目版本信息",
data() {
return {
ver: 'loading...'
}
},
mounted() {
this.getVer()
},
methods: {
async getVer(){
const ver = await this.$API.demo.ver.get()
this.ver = ver.data
},
golog(){
window.open("https://gitee.com/lolicode/scui/releases")
},
gogit(){
window.open("https://gitee.com/lolicode/scui")
}
}
}
</script>

View File

@@ -1,37 +0,0 @@
<template>
<el-main>
<el-alert title="根据角色配置,可让不同角色访问不同的控制台视图,参数值在登录成功后返回 dashboard:{type}" type="success" style="margin-bottom:20px;"></el-alert>
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never" header="我的常用">
<myapp></myapp>
</el-card>
</el-col>
</el-row>
</el-main>
</template>
<script>
import myapp from './components/myapp';
export default {
components: {
myapp
},
data() {
return {
}
},
mounted(){
this.$emit('on-mounted')
},
methods: {
}
}
</script>
<style scoped>
</style>

View File

@@ -1,52 +0,0 @@
<template>
<el-main>
<el-row :gutter="15">
<el-col :lg="24">
<el-card shadow="never" class="aboutTop">
<div class="aboutTop-info">
<img src="img/logo.png">
<h2>{{data.name}}</h2>
<p>{{data.version}}</p>
</div>
</el-card>
<el-card shadow="never" header="dependencies">
<el-descriptions border :column="3">
<el-descriptions-item v-for="(value, key) in data.dependencies" :key="key" :label="key">{{value}}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="never" header="devDependencies">
<el-descriptions border :column="3">
<el-descriptions-item v-for="(value, key) in data.devDependencies" :key="key" :label="key">{{value}}</el-descriptions-item>
</el-descriptions>
</el-card>
</el-col>
</el-row>
</el-main>
</template>
<script>
import packageJson from '../../../package.json'
export default {
name: 'about',
data() {
return {
data: packageJson
}
},
mounted() {
},
methods: {
}
}
</script>
<style scoped>
.aboutTop {border:0;background: linear-gradient(to right, #8E54E9, #4776E6);color: #fff;}
.aboutTop-info {text-align: center}
.aboutTop-info img {width: 100px;}
.aboutTop-info h2 {font-size: 26px;margin-top: 15px;}
.aboutTop-info p {font-size: 16px;margin-top: 10px;}
</style>

View File

@@ -1,54 +0,0 @@
<template>
<el-main>
<el-card shadow="never" header="v-auth 高精度权限控制">
<el-button v-auth="'user.add'" type="primary">v-auth="'user.add'"</el-button>
<el-button v-auth="['user.no','user.add']" type="primary">v-auth="['user.no','user.add']"</el-button>
<el-alert title="v-auth指令 是$AUTH的语法糖, 原先需要使用v-if来判断是否有权限, 使用指令将减少代码冗余. 并且支持传入数组,有一项满足就判断有权限" style="margin-top: 20px;"></el-alert>
</el-card>
<el-card shadow="never" header="v-role 角色权限控制" style="margin-top: 15px;">
<el-button v-role="'admin'" type="primary">v-role="'admin'"</el-button>
<el-button v-role="['SA','admin']" type="primary">v-role="['SA','admin']"</el-button>
<el-alert title="v-role指令 是$ROLE的语法糖, 原理是判断是否含有用户所在的角色别名" style="margin-top: 20px;"></el-alert>
</el-card>
<el-card shadow="never" header="v-time 时间转换" style="margin-top: 15px;">
<p>
<el-tag v-time="1630117968295" format="yyyy-MM-dd hh:mm:ss"></el-tag>
</p>
<p style="margin-top: 15px;">
<el-tag v-time.tip="time1"></el-tag>
</p>
<p style="margin-top: 15px;">
<el-tag v-time.tip="time2"></el-tag>
</p>
<p style="margin-top: 15px;">
<el-tag v-time.tip="time3"></el-tag>
</p>
<el-alert title="指令方式日期时间转换,如设置'tip'修饰符将会转换成相对时间,并且每60秒自动更新" style="margin-top: 20px;"></el-alert>
</el-card>
<el-card shadow="never" header="v-copy 一键复制" style="margin-top: 15px;">
<el-input type="textarea" :rows="2" placeholder="请输入内容" v-model="copyText"></el-input>
<el-button v-copy="copyText" type="primary" style="margin-top: 15px;">复制</el-button>
<el-alert title="点击复制按钮会将文本框绑定的值复制到剪切板, 试着粘贴到其他地方看看效果" style="margin-top: 20px;"></el-alert>
</el-card>
</el-main>
</template>
<script>
export default {
name: 'directive',
data() {
return {
time1: new Date(),
time2: new Date().setMinutes(new Date().getMinutes()-1),
time3: new Date().setMinutes(new Date().getMinutes()-120),
copyText: '测试复制内容'
}
},
created() {
}
}
</script>
<style>
</style>

View File

@@ -1,32 +0,0 @@
<template>
<el-container>
<el-header>
<el-page-header content="FullPage" @back="goBack" />
</el-header>
<el-main>
<el-empty description="FullPageMain"></el-empty>
</el-main>
</el-container>
</template>
<script>
export default {
name: 'fullpage',
data() {
return {
}
},
mounted() {
},
methods: {
goBack(){
this.$router.go(-1)
}
}
}
</script>
<style>
</style>

View File

@@ -1,82 +0,0 @@
<template>
<el-main>
<el-card shadow="never" header="打开">
<el-button type="primary" plain @click="open1">打开个人信息</el-button>
<el-button type="primary" plain @click="open2">打开后执行</el-button>
<el-alert title="打开后执行原理: 路由push时,在当前路由对象中插入一个特殊标识, 在目标视图中beforeRouteEnter获取判断是否需要执行特殊方法" style="margin-top: 20px;"></el-alert>
</el-card>
<el-card shadow="never" header="刷新" style="margin-top: 15px;">
<el-button type="primary" plain @click="refresh1">刷新当前</el-button>
</el-card>
<el-card shadow="never" header="关闭" style="margin-top: 15px;">
<el-button type="primary" plain @click="close1">关闭当前</el-button>
<el-button type="primary" plain @click="close2">关闭其他</el-button>
<el-button type="primary" plain @click="close3">关闭后执行</el-button>
</el-card>
<el-card shadow="never" header="设置" style="margin-top: 15px;">
<el-form :inline="true">
<el-form-item>
<el-input v-model="input" placeholder="请输入内容"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="set1">设置标题</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" header="整页路由" style="margin-top: 15px;">
<el-button type="primary" plain @click="fullpage">fullpage</el-button>
<el-alert title="变更路由的层级关系,向上推至顶级达到在layout视图中显示. 只需要在路由中设置 meta.fullpage 即可" style="margin-top: 20px;"></el-alert>
</el-card>
</el-main>
</template>
<script>
import useTabs from '@/utils/useTabs'
export default {
name: 'viewTags',
data() {
return {
input: "newTabName"
}
},
mounted() {
},
methods: {
open1(){
this.$router.push('/usercenter')
},
open2(){
this.$router.push('/usercenter')
this.$route.is = true
},
refresh1(){
useTabs.refresh()
},
close1(){
useTabs.close()
},
close2(){
useTabs.closeOther()
},
close3(){
useTabs.closeNext((tags)=>{
//回调返回所有标签的数组,这里其实是需要判断是否含有'/usercenter',含有再操作的,这里为了演示就直接打开了。
console.log(tags)
this.$router.push('/usercenter')
this.$route.is = true
})
},
set1(){
useTabs.setTitle(this.input)
},
fullpage(){
this.$router.push('/other/fullpage')
}
}
}
</script>
<style>
</style>

View File

@@ -1,330 +0,0 @@
<template>
<el-container>
<el-aside width="300px" v-loading="showDicloading">
<el-container>
<el-header>
<el-input placeholder="输入关键字进行过滤" v-model="dicFilterText" clearable></el-input>
</el-header>
<el-main class="nopadding">
<el-tree ref="dic" class="menu" node-key="id" :data="dicList" :props="dicProps" :highlight-current="true" :expand-on-click-node="false" :filter-node-method="dicFilterNode" @node-click="dicClick">
<template #default="{node, data}">
<span class="custom-tree-node">
<span class="label">{{ node.label }}</span>
<span class="code">{{ data.code }}</span>
<span class="do">
<el-icon @click.stop="dicEdit(data)"><el-icon-edit /></el-icon>
<el-icon @click.stop="dicDel(node, data)"><el-icon-delete /></el-icon>
</span>
</span>
</template>
</el-tree>
</el-main>
<el-footer style="height:51px;">
<el-button type="primary" size="small" icon="el-icon-plus" style="width: 100%;" @click="addDic">字典分类</el-button>
</el-footer>
</el-container>
</el-aside>
<el-container class="is-vertical">
<el-header>
<div class="left-panel">
<el-button type="primary" icon="el-icon-plus" @click="addInfo"></el-button>
<el-button type="danger" plain icon="el-icon-delete" :disabled="selection.length==0" @click="batch_del"></el-button>
</div>
</el-header>
<el-main class="nopadding">
<scTable ref="table" :apiObj="listApi" row-key="id" :params="listApiParams" @selection-change="selectionChange" stripe :paginationLayout="'prev, pager, next'">
<el-table-column type="selection" width="50"></el-table-column>
<el-table-column label="" width="60">
<template #default>
<el-tag class="move" style="cursor: move;"><el-icon-d-caret style="width: 1em; height: 1em;"/></el-tag>
</template>
</el-table-column>
<el-table-column label="名称" prop="name" width="150"></el-table-column>
<el-table-column label="键值" prop="key" width="150"></el-table-column>
<el-table-column label="是否有效" prop="yx" width="100">
<template #default="scope">
<el-switch v-model="scope.row.yx" @change="changeSwitch($event, scope.row)" :loading="scope.row.$switch_yx" active-value="1" inactive-value="0"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" align="right" width="140">
<template #default="scope">
<el-button type="text" size="small" @click="table_edit(scope.row, scope.$index)">编辑</el-button>
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
<template #reference>
<el-button type="text" size="small">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</scTable>
</el-main>
</el-container>
</el-container>
<dic-dialog v-if="dialog.dic" ref="dicDialog" @success="handleDicSuccess" @closed="dialog.dic=false"></dic-dialog>
<list-dialog v-if="dialog.list" ref="listDialog" @success="handleListSuccess" @closed="dialog.list=false"></list-dialog>
</template>
<script>
import dicDialog from './dic'
import listDialog from './list'
import Sortable from 'sortablejs'
export default {
name: 'dic',
components: {
dicDialog,
listDialog
},
data() {
return {
dialog: {
dic: false,
info: false
},
showDicloading: true,
dicList: [],
dicFilterText: '',
dicProps: {
label: 'name'
},
listApi: null,
listApiParams: {},
selection: []
}
},
watch: {
dicFilterText(val) {
this.$refs.dic.filter(val);
}
},
mounted() {
this.getDic()
this.rowDrop()
},
methods: {
//加载树数据
async getDic(){
var res = await this.$API.system.dic.tree.get();
this.showDicloading = false;
this.dicList = res.data;
//获取第一个节点,设置选中 & 加载明细列表
var firstNode = this.dicList[0];
if(firstNode){
this.$nextTick(() => {
this.$refs.dic.setCurrentKey(firstNode.id)
})
this.listApiParams = {
code: firstNode.code
}
this.listApi = this.$API.system.dic.list;
}
},
//树过滤
dicFilterNode(value, data){
if (!value) return true;
var targetText = data.name + data.code;
return targetText.indexOf(value) !== -1;
},
//树增加
addDic(){
this.dialog.dic = true
this.$nextTick(() => {
this.$refs.dicDialog.open()
})
},
//编辑树
dicEdit(data){
this.dialog.dic = true
this.$nextTick(() => {
var editNode = this.$refs.dic.getNode(data.id);
var editNodeParentId = editNode.level==1?undefined:editNode.parent.data.id
data.parentId = editNodeParentId
this.$refs.dicDialog.open('edit').setData(data)
})
},
//树点击事件
dicClick(data){
this.$refs.table.reload({
code: data.code
})
},
//删除树
dicDel(node, data){
this.$confirm(`确定删除 ${data.name} 项吗?`, '提示', {
type: 'warning'
}).then(() => {
this.showDicloading = true;
//删除节点是否为高亮当前 是的话 设置第一个节点高亮
var dicCurrentKey = this.$refs.dic.getCurrentKey();
this.$refs.dic.remove(data.id)
if(dicCurrentKey == data.id){
var firstNode = this.dicList[0];
if(firstNode){
this.$refs.dic.setCurrentKey(firstNode.id);
this.$refs.table.upData({
code: firstNode.code
})
}else{
this.listApi = null;
this.$refs.table.tableData = []
}
}
this.showDicloading = false;
this.$message.success("操作成功")
}).catch(() => {
})
},
//行拖拽
rowDrop(){
const _this = this
const tbody = this.$refs.table.$el.querySelector('.el-table__body-wrapper tbody')
Sortable.create(tbody, {
handle: ".move",
animation: 300,
ghostClass: "ghost",
onEnd({ newIndex, oldIndex }) {
const tableData = _this.$refs.table.tableData
const currRow = tableData.splice(oldIndex, 1)[0]
tableData.splice(newIndex, 0, currRow)
_this.$message.success("排序成功")
}
})
},
//添加明细
addInfo(){
this.dialog.list = true
this.$nextTick(() => {
var dicCurrentKey = this.$refs.dic.getCurrentKey();
const data = {
dic: dicCurrentKey
}
this.$refs.listDialog.open().setData(data)
})
},
//编辑明细
table_edit(row){
this.dialog.list = true
this.$nextTick(() => {
this.$refs.listDialog.open('edit').setData(row)
})
},
//删除明细
async table_del(row, index){
var reqData = {id: row.id}
var res = await this.$API.demo.post.post(reqData);
if(res.code == 200){
this.$refs.table.tableData.splice(index, 1);
this.$message.success("删除成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
}
},
//批量删除
async batch_del(){
this.$confirm(`确定删除选中的 ${this.selection.length} 项吗?`, '提示', {
type: 'warning'
}).then(() => {
const loading = this.$loading();
this.selection.forEach(item => {
this.$refs.table.tableData.forEach((itemI, indexI) => {
if (item.id === itemI.id) {
this.$refs.table.tableData.splice(indexI, 1)
}
})
})
loading.close();
this.$message.success("操作成功")
}).catch(() => {
})
},
//提交明细
saveList(){
this.$refs.listDialog.submit(async (formData) => {
this.isListSaveing = true;
var res = await this.$API.demo.post.post(formData);
this.isListSaveing = false;
if(res.code == 200){
//这里选择刷新整个表格 OR 插入/编辑现有表格数据
this.listDialogVisible = false;
this.$message.success("操作成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
}
})
},
//表格选择后回调事件
selectionChange(selection){
this.selection = selection;
},
//表格内开关事件
changeSwitch(val, row){
//1.还原数据
row.yx = row.yx == '1'?'0':'1'
//2.执行加载
row.$switch_yx = true;
//3.等待接口返回后改变值
setTimeout(()=>{
delete row.$switch_yx;
row.yx = val;
this.$message.success(`操作成功id:${row.id} val:${val}`)
}, 500)
},
//本地更新数据
handleDicSuccess(data, mode){
if(mode=='add'){
data.id = new Date().getTime()
if(this.dicList.length > 0){
this.$refs.table.upData({
code: data.code
})
}else{
this.listApiParams = {
code: data.code
}
this.listApi = this.$API.dic.info;
}
this.$refs.dic.append(data, data.parentId[0])
this.$refs.dic.setCurrentKey(data.id)
}else if(mode=='edit'){
var editNode = this.$refs.dic.getNode(data.id);
//判断是否移动?
var editNodeParentId = editNode.level==1?undefined:editNode.parent.data.id
if(editNodeParentId != data.parentId){
var obj = editNode.data;
this.$refs.dic.remove(data.id)
this.$refs.dic.append(obj, data.parentId[0])
}
Object.assign(editNode.data, data)
}
},
//本地更新数据
handleListSuccess(data, mode){
if(mode=='add'){
data.id = new Date().getTime()
this.$refs.table.tableData.push(data)
}else if(mode=='edit'){
this.$refs.table.tableData.filter(item => item.id===data.id ).forEach(item => {
Object.assign(item, data)
})
}
}
}
}
</script>
<style scoped>
.custom-tree-node {display: flex;flex: 1;align-items: center;justify-content: space-between;font-size: 14px;padding-right: 24px;height:100%;}
.custom-tree-node .code {font-size: 12px;color: #999;}
.custom-tree-node .do {display: none;}
.custom-tree-node .do i {margin-left:5px;color: #999;padding:5px;}
.custom-tree-node .do i:hover {color: #333;}
.custom-tree-node:hover .code {display: none;}
.custom-tree-node:hover .do {display: inline-block;}
</style>

View File

@@ -1,111 +0,0 @@
<template>
<el-dialog :title="titleMap[mode]" v-model="visible" :width="400" destroy-on-close @closed="$emit('closed')">
<el-form :model="form" :rules="rules" ref="dialogForm" label-width="100px" label-position="left">
<el-form-item label="所属字典" prop="dic">
<el-cascader v-model="form.dic" :options="dic" :props="dicProps" :show-all-levels="false" clearable></el-cascader>
</el-form-item>
<el-form-item label="项名称" prop="name">
<el-input v-model="form.name" clearable></el-input>
</el-form-item>
<el-form-item label="键值" prop="key">
<el-input v-model="form.key" clearable></el-input>
</el-form-item>
<el-form-item label="是否有效" prop="yx">
<el-switch v-model="form.yx" active-value="1" inactive-value="0"></el-switch>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible=false" > </el-button>
<el-button type="primary" :loading="isSaveing" @click="submit()"> </el-button>
</template>
</el-dialog>
</template>
<script>
export default {
emits: ['success', 'closed'],
data() {
return {
mode: "add",
titleMap: {
add: '新增项',
edit: '编辑项'
},
visible: false,
isSaveing: false,
form: {
id: "",
dic: "",
name: "",
key: "",
yx: "1"
},
rules: {
dic: [
{required: true, message: '请选择所属字典'}
],
name: [
{required: true, message: '请输入项名称'}
],
key: [
{required: true, message: '请输入键值'}
]
},
dic: [],
dicProps: {
value: "id",
label: "name",
emitPath: false,
checkStrictly: true
}
}
},
mounted() {
if(this.params){
this.form.dic = this.params.code
}
this.getDic()
},
methods: {
//显示
open(mode='add'){
this.mode = mode;
this.visible = true;
return this;
},
//获取字典列表
async getDic(){
var res = await this.$API.system.dic.tree.get();
this.dic = res.data;
},
//表单提交方法
submit(){
this.$refs.dialogForm.validate(async (valid) => {
if (valid) {
this.isSaveing = true;
var res = await this.$API.demo.post.post(this.form);
this.isSaveing = false;
if(res.code == 200){
this.$emit('success', this.form, this.mode)
this.visible = false;
this.$message.success("操作成功")
}else{
this.$alert(res.message, "提示", {type: 'error'})
}
}
})
},
//表单注入数据
setData(data){
this.form.id = data.id
this.form.name = data.name
this.form.key = data.key
this.form.yx = data.yx
this.form.dic = data.dic
}
}
}
</script>
<style>
</style>

View File

@@ -1,142 +0,0 @@
<template>
<el-container>
<el-aside width="220px">
<el-tree ref="category" class="menu" node-key="label" :data="category" :default-expanded-keys="['系统日志']" current-node-key="系统日志" :highlight-current="true" :expand-on-click-node="false">
</el-tree>
</el-aside>
<el-container>
<el-main class="nopadding">
<el-container>
<el-header>
<div class="left-panel">
<el-date-picker v-model="date" type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
</div>
<div class="right-panel">
</div>
</el-header>
<el-header style="height:150px;">
<scEcharts height="100%" :option="logsChartOption"></scEcharts>
</el-header>
<el-main class="nopadding">
<scTable ref="table" :apiObj="apiObj" stripe highlightCurrentRow @row-click="rowClick">
<el-table-column label="级别" prop="level" width="60">
<template #default="scope">
<el-icon v-if="scope.row.level=='error'" style="color: #F56C6C;"><el-icon-circle-close-filled /></el-icon>
<el-icon v-if="scope.row.level=='warn'" style="color: #E6A23C;"><el-icon-warning-filled /></el-icon>
<el-icon v-if="scope.row.level=='info'" style="color: #409EFF;"><el-icon-info-filled /></el-icon>
</template>
</el-table-column>
<el-table-column label="ID" prop="id" width="180"></el-table-column>
<el-table-column label="日志名" prop="name" width="150"></el-table-column>
<el-table-column label="请求接口" prop="url" width="150"></el-table-column>
<el-table-column label="请求方法" prop="type" width="150"></el-table-column>
<el-table-column label="用户" prop="user" width="150"></el-table-column>
<el-table-column label="客户端IP" prop="cip" width="150"></el-table-column>
<el-table-column label="日志时间" prop="time" width="170"></el-table-column>
</scTable>
</el-main>
</el-container>
</el-main>
</el-container>
</el-container>
<el-drawer v-model="infoDrawer" title="日志详情" :size="600" destroy-on-close>
<info ref="info"></info>
</el-drawer>
</template>
<script>
import info from './info'
import scEcharts from '@/components/scEcharts'
export default {
name: 'log',
components: {
info,
scEcharts
},
data() {
return {
infoDrawer: false,
logsChartOption: {
color: ['#409eff','#e6a23c','#f56c6c'],
grid: {
top: '0px',
left: '10px',
right: '10px',
bottom: '0px'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['2021-07-01', '2021-07-02', '2021-07-03', '2021-07-04', '2021-07-05', '2021-07-06', '2021-07-07', '2021-07-08', '2021-07-09', '2021-07-10', '2021-07-11', '2021-07-12', '2021-07-13', '2021-07-14', '2021-07-15']
},
yAxis: {
show: false,
type: 'value'
},
series: [{
data: [120, 200, 150, 80, 70, 110, 130, 120, 200, 150, 80, 70, 110, 130, 70, 110],
type: 'bar',
stack: 'log',
barWidth: '15px'
},
{
data: [15, 26, 7, 12, 13, 9, 21, 15, 26, 7, 12, 13, 9, 21, 12, 3],
type: 'bar',
stack: 'log',
barWidth: '15px'
},
{
data: [0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
type: 'bar',
stack: 'log',
barWidth: '15px'
}]
},
category: [
{
label: '系统日志',
children: [
{label: 'debug'},
{label: 'info'},
{label: 'warn'},
{label: 'error'},
{label: 'fatal'}
]
},
{
label: '应用日志',
children: [
{label: 'selfHelp'},
{label: 'WechatApp'}
]
}
],
date: [],
apiObj: this.$API.system.log.list,
search: {
keyword: ""
}
}
},
methods: {
upsearch(){
},
rowClick(row){
this.infoDrawer = true
this.$nextTick(() => {
this.$refs.info.setData(row)
})
}
}
}
</script>
<style>
</style>

View File

@@ -1,54 +0,0 @@
<template>
<el-main style="padding:0 20px;">
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="请求接口">{{data.url}}</el-descriptions-item>
<el-descriptions-item label="请求方法">{{data.type}}</el-descriptions-item>
<el-descriptions-item label="状态代码">{{data.code}}</el-descriptions-item>
<el-descriptions-item label="日志名">{{data.name}}</el-descriptions-item>
<el-descriptions-item label="日志时间">{{data.time}}</el-descriptions-item>
</el-descriptions>
<el-collapse v-model="activeNames" style="margin-top: 20px;">
<el-collapse-item title="常规" name="1">
<el-alert title="在没有配置的 DNS 服务器响应之后,名称 update-khd.2345.cc 的名称解析超时。" :type="typeMap[data.level]" :closable="false"></el-alert>
</el-collapse-item>
<el-collapse-item title="详细" name="2">
<div class="code">
Request: {
User-Agent: "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
},
Response: {
Content-Type: "application/json; charset=utf-8",
Date: "Fri, 25 Jun 2021 03:02:14 GMT",
Server: "nginx/1.17.8"
}
</div>
</el-collapse-item>
</el-collapse>
</el-main>
</template>
<script>
export default {
data() {
return {
data: {},
activeNames: ['1'],
typeMap: {
'info': "info",
'warn': "warning",
'error': "error"
}
}
},
methods: {
setData(data){
this.data = data
}
}
}
</script>
<style scoped>
.code {background: #848484;padding:15px;color: #fff;font-size: 12px;border-radius: 4px;}
</style>

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