初始化

This commit is contained in:
24kycj
2024-11-04 11:40:06 +08:00
commit e109b28eec
71 changed files with 14000 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style lang="scss">
@import './styles/index.scss'; // 全局自定义的css样式
</style>
+27
View File
@@ -0,0 +1,27 @@
import request from '@/utils/request'
export function login(username, password) {
return request({
url: '/user/login',
method: 'post',
data: {
username,
password
}
})
}
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
}
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

+128
View File
@@ -0,0 +1,128 @@
<template>
<div id="wrapper">
<img id="logo" src="~@/assets/logo.png" alt="electron-vue">
<main>
<div class="left-side">
<span class="title">
Welcome to your new project!
</span>
<system-information></system-information>
</div>
<div class="right-side">
<div class="doc">
<div class="title">Getting Started</div>
<p>
electron-vue comes packed with detailed documentation that covers everything from
internal configurations, using the project structure, building your application,
and so much more.
</p>
<button @click="open('https://simulatedgreg.gitbooks.io/electron-vue/content/')">Read the Docs</button><br><br>
</div>
<div class="doc">
<div class="title alt">Other Documentation</div>
<button class="alt" @click="open('https://electron.atom.io/docs/')">Electron</button>
<button class="alt" @click="open('https://vuejs.org/v2/guide/')">Vue.js</button>
</div>
</div>
</main>
</div>
</template>
<script>
import SystemInformation from './LandingPage/SystemInformation'
export default {
name: 'landing-page',
components: { SystemInformation },
methods: {
open (link) {
this.$electron.shell.openExternal(link)
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro');
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body { font-family: 'Source Sans Pro', sans-serif; }
#wrapper {
background:
radial-gradient(
ellipse at top left,
rgba(255, 255, 255, 1) 40%,
rgba(229, 229, 229, .9) 100%
);
height: 100vh;
padding: 60px 80px;
width: 100vw;
}
#logo {
height: auto;
margin-bottom: 20px;
width: 420px;
}
main {
display: flex;
justify-content: space-between;
}
main > div { flex-basis: 50%; }
.left-side {
display: flex;
flex-direction: column;
}
.welcome {
color: #555;
font-size: 23px;
margin-bottom: 10px;
}
.title {
color: #2c3e50;
font-size: 20px;
font-weight: bold;
margin-bottom: 6px;
}
.title.alt {
font-size: 18px;
margin-bottom: 10px;
}
.doc p {
color: black;
margin-bottom: 10px;
}
.doc button {
font-size: .8em;
cursor: pointer;
outline: none;
padding: 0.75em 2em;
border-radius: 2em;
display: inline-block;
color: #fff;
background-color: #4fc08d;
transition: all 0.15s ease;
box-sizing: border-box;
border: 1px solid #4fc08d;
}
.doc button.alt {
color: #42b983;
background-color: transparent;
}
</style>
@@ -0,0 +1,73 @@
<template>
<div>
<div class="title">Information</div>
<div class="items">
<div class="item">
<div class="name">Path:</div>
<div class="value">{{ path }}</div>
</div>
<div class="item">
<div class="name">Route Name:</div>
<div class="value">{{ name }}</div>
</div>
<div class="item">
<div class="name">Vue.js:</div>
<div class="value">{{ vue }}</div>
</div>
<div class="item">
<div class="name">Electron:</div>
<div class="value">{{ electron }}</div>
</div>
<div class="item">
<div class="name">Node:</div>
<div class="value">{{ node }}</div>
</div>
<div class="item">
<div class="name">Platform:</div>
<div class="value">{{ platform }}</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
electron: process.versions.electron,
name: this.$route.name,
node: process.versions.node,
path: this.$route.path,
platform: require('os').platform(),
vue: require('vue/package.json').version
}
}
}
</script>
<style scoped>
.title {
color: #888;
font-size: 18px;
font-weight: initial;
letter-spacing: .25px;
margin-top: 10px;
}
.items { margin-top: 8px; }
.item {
display: flex;
margin-bottom: 6px;
}
.item .name {
color: #6a6a6a;
margin-right: 6px;
}
.item .value {
color: #35495e;
font-weight: bold;
}
</style>
@@ -0,0 +1,34 @@
<template>
<el-dropdown trigger="click" class="international" @command="handleSetLanguage">
<div>
<svg-icon class-name="international-icon" icon-class="language" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :disabled="language==='zhCN'" command="zhCN">
简体中文
</el-dropdown-item>
<el-dropdown-item :disabled="language==='en'" command="en">
English
</el-dropdown-item>
<el-dropdown-item :disabled="language==='zhTW'" command="zhTW">
繁体中文
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
computed: {
language() {
return this.$store.getters.language
}
},
methods: {
handleSetLanguage(lang) {
this.$i18n.locale = lang
this.$store.dispatch('app/setLanguage', lang)
}
}
}
</script>
+23
View File
@@ -0,0 +1,23 @@
export default {
route: {
dashboard: 'Dashboard',
documentation: 'Documentation',
},
navbar: {
dashboard: 'Dashboard',
github: 'Github',
logOut: 'Log Out',
profile: 'Profile',
theme: 'Theme',
size: 'Global Size'
},
login: {
title: 'Login Form',
logIn: 'Login',
username: 'Username',
password: 'Password',
any: 'any',
thirdparty: 'Or connect with',
thirdpartyTips: 'Can not be simulated on local, so please combine you own business simulation! ! !'
},
}
+49
View File
@@ -0,0 +1,49 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import Cookies from 'js-cookie'
import elementEnLocale from 'element-ui/lib/locale/lang/en' // element-ui lang
import elementZhCNLocale from 'element-ui/lib/locale/lang/zh-CN'// element-ui lang
import elementZhTWLocale from 'element-ui/lib/locale/lang/zh-TW'// element-ui lang
import enLocale from './en'
import zhCNLocale from './zh-CN'
import zhTWLocale from './zh-TW'
Vue.use(VueI18n)
const messages = {
en: {
...enLocale,
...elementEnLocale
},
zhCN: {
...zhCNLocale,
...elementZhCNLocale
},
zhTW: {
...zhTWLocale,
...elementZhTWLocale
}
}
export function getLanguage() {
const chooseLanguage = Cookies.get('language')
if (chooseLanguage) return chooseLanguage
// if has not choose language
const language = (navigator.language || navigator.browserLanguage).toLowerCase()
const locales = Object.keys(messages)
for (const locale of locales) {
if (language.indexOf(locale) > -1) {
return locale
}
}
return 'zhCN'
}
const i18n = new VueI18n({
// set locale
// options: en | zhCN | zhTW
locale: getLanguage(),
// set locale messages
messages
})
export default i18n
+21
View File
@@ -0,0 +1,21 @@
export default {
route: {
dashboard: '首页',
},
navbar: {
dashboard: '首页',
github: '项目地址',
logOut: '退出登录',
profile: '个人中心',
theme: '换肤',
size: '布局大小'
},
login: {
title: '系统登录',
logIn: '登录',
username: '账号',
password: '密码',
any: '随便填',
thirdparty: '第三方登录',
},
}
+21
View File
@@ -0,0 +1,21 @@
export default {
route: {
dashboard: '首页',
},
navbar: {
dashboard: '首页',
github: '项目地址',
logOut: '退出登录',
profile: '个人中心',
theme: '换肤',
size: '布局大小'
},
login: {
title: '系统登录',
logIn: '登录',
username: '账号',
password: '密码',
any: '随便填',
thirdparty: '第三方登录',
},
}
+32
View File
@@ -0,0 +1,32 @@
import Vue from 'vue'
import Cookies from 'js-cookie'
import axios from 'axios'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App'
import router from './router'
import store from './store'
import i18n from './lang' // internationalization
import './permission' // permission control
if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.http = Vue.prototype.$http = axios
Vue.config.productionTip = false
Vue.use(ElementUI, {
size: Cookies.get('size') || 'medium', // set element-ui default size
i18n: (key, value) => i18n.t(key, value)
})
/* eslint-disable no-new */
new Vue({
components: { App },
router,
store,
i18n,
template: '<App/>'
}).$mount('#app')
+40
View File
@@ -0,0 +1,40 @@
import router from './router'
import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'// Progress 进度条样式
import { Message } from 'element-ui'
const whiteList = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
NProgress.start()
if (!store.getters.token) {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
} else {
if (store.getters.roles.length === 0) {
store.dispatch('GetInfo').then(res => { // 拉取用户信息
next()
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next('/login')
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done() // 结束Progress
})
+12
View File
@@ -0,0 +1,12 @@
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{ path: '/login', component: () => import('@/views/login/index') },
{ path: '/404', component: () => import('@/views/404') },
{ path: '/', component: () => import('@/views/dashboard/index'), name: '首页' }
]
})
+10
View File
@@ -0,0 +1,10 @@
const getters = {
language: state => state.app.language,
size: state => state.app.size,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
roles: state => state.user.roles,
}
export default getters
+19
View File
@@ -0,0 +1,19 @@
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import { createPersistedState, createSharedMutations } from 'vuex-electron'
import modules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
modules,
getters,
plugins: [
createPersistedState(),
createSharedMutations()
],
strict: process.env.NODE_ENV !== 'production'
})
+25
View File
@@ -0,0 +1,25 @@
const state = {
main: 0
}
const mutations = {
DECREMENT_MAIN_COUNTER (state) {
state.main--
},
INCREMENT_MAIN_COUNTER (state) {
state.main++
}
}
const actions = {
someAsyncTask ({ commit }) {
// do something async
commit('INCREMENT_MAIN_COUNTER')
}
}
export default {
state,
mutations,
actions
}
+40
View File
@@ -0,0 +1,40 @@
import Cookies from 'js-cookie'
import { getLanguage } from '@/lang/index'
const state = {
device: 'desktop',
language: getLanguage(),
size: Cookies.get('size') || 'medium'
}
const mutations = {
TOGGLE_DEVICE: (state, device) => {
state.device = device
},
SET_LANGUAGE: (state, language) => {
state.language = language
Cookies.set('language', language)
},
SET_SIZE: (state, size) => {
state.size = size
Cookies.set('size', size)
}
}
const actions = {
toggleDevice({ commit }, device) {
commit('TOGGLE_DEVICE', device)
},
setLanguage({ commit }, language) {
commit('SET_LANGUAGE', language)
},
setSize({ commit }, size) {
commit('SET_SIZE', size)
}
}
export default {
state,
mutations,
actions
}
+14
View File
@@ -0,0 +1,14 @@
/**
* The file enables `@/store/index.js` to import all vuex modules
* in a one-shot manner. There should not be any reason to edit this file.
*/
const files = require.context('.', false, /\.js$/)
const modules = {}
files.keys().forEach(key => {
if (key === './index.js') return
modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
})
export default modules
+87
View File
@@ -0,0 +1,87 @@
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
const user = {
state: {
token: getToken(),
name: '',
avatar: '',
roles: []
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
}
},
actions: {
// 登录
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
return new Promise((resolve, reject) => {
login(username, userInfo.password).then(response => {
const data = response.data
setToken(data.token)
commit('SET_TOKEN', data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const data = response.data
if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', data.roles)
} else {
reject('getInfo: roles must be a non-null array !')
}
commit('SET_NAME', data.name)
commit('SET_AVATAR', data.avatar)
resolve(response)
}).catch(error => {
reject(error)
})
})
},
// 登出
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resolve()
}).catch(error => {
reject(error)
})
})
},
// 前端 登出
FedLogOut({ commit }) {
return new Promise(resolve => {
removeToken()
commit('SET_TOKEN', '')
resolve()
})
}
}
}
export default user
+29
View File
@@ -0,0 +1,29 @@
//to reset element-ui default css
.el-upload {
input[type="file"] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
.el-dialog {
transform: none;
left: 0;
position: relative;
margin: 0 auto;
}
//element ui upload
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}
+122
View File
@@ -0,0 +1,122 @@
@import './variables.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';
body {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
div:focus{
outline: none;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
//main-container全局样式
.app-main{
min-height: 100%
}
.app-container {
padding: 20px;
}
/* flex布局 */
.flex_box {
display: -webkit-flex;
display: flex;
align-items: center;
}
.flex_row_between {
justify-content: space-between;
}
.flex_row_center {
justify-content: center;
}
.flex_row_around {
justify-content: space-around;
}
.flex_row_end {
justify-content: flex-end;
}
.flex_col {
flex-direction: column;
}
.flex_col_top {
align-items: flex-start;
}
.flex_col_bottom {
align-items: flex-end;
}
.flex_wrap {
flex-wrap: wrap;
}
.flex_shrink {
flex-shrink: 0;
}
/* 文字溢出隐藏 */
.text_hidden {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.text_more {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
.text_three {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
+27
View File
@@ -0,0 +1,27 @@
@mixin clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
@mixin scrollBar {
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
@mixin relative {
position: relative;
width: 100%;
height: 100%;
}
+105
View File
@@ -0,0 +1,105 @@
#app {
// 主体区域
.main-container {
min-height: 100%;
transition: margin-left .28s;
margin-left: 180px;
}
// 侧边栏
.sidebar-container {
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
transition: width .28s;
width: 180px !important;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
a {
display: inline-block;
width: 100%;
}
.svg-icon {
margin-right: 16px;
}
.el-menu {
border: none;
width: 100% !important;
}
}
.hideSidebar {
.sidebar-container {
width: 36px !important;
}
.main-container {
margin-left: 36px;
}
.submenu-title-noDropdown {
padding-left: 10px !important;
position: relative;
.el-tooltip {
padding: 0 10px !important;
}
}
.el-submenu {
&>.el-submenu__title {
padding-left: 10px !important;
&>span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
.el-submenu__icon-arrow {
display: none;
}
}
}
}
.sidebar-container .nest-menu .el-submenu>.el-submenu__title,
.sidebar-container .el-submenu .el-menu-item {
min-width: 180px !important;
background-color: $subMenuBg !important;
&:hover {
background-color: $menuHover !important;
}
}
.el-menu--collapse .el-menu .el-submenu {
min-width: 180px !important;
}
//适配移动端
.mobile {
.main-container {
margin-left: 0px;
}
.sidebar-container {
top: 50px;
transition: transform .28s;
width: 180px !important;
}
&.hideSidebar {
.sidebar-container {
transition-duration: 0.3s;
transform: translate3d(-180px, 0, 0);
}
}
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}
+32
View File
@@ -0,0 +1,32 @@
//globl transition css
/*fade*/
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
/*fade*/
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all .5s;
}
.breadcrumb-enter,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(20px);
}
.breadcrumb-move {
transition: all .5s;
}
.breadcrumb-leave-active {
position: absolute;
}
+4
View File
@@ -0,0 +1,4 @@
//sidebar
$menuBg:#304156;
$subMenuBg:#1f2d3d;
$menuHover:#001528;
+15
View File
@@ -0,0 +1,15 @@
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
+12
View File
@@ -0,0 +1,12 @@
import i18n from '@/lang'
const title = 'SOONWORKERDISK'
export default function getPageTitle(key) {
const hasKey = i18n.te(`route.${key}`)
if (hasKey) {
const pageName = i18n.t(`route.${key}`)
return `${pageName} - ${title}`
}
return `${title}`
}
+12
View File
@@ -0,0 +1,12 @@
// translate router.meta.title, be used in breadcrumb sidebar tagsview
export function generateTitle(title) {
const hasKey = this.$te('route.' + title)
if (hasKey) {
// $t :this method from vue-i18n, inject in @/lang/index.js
const translatedTitle = this.$t('route.' + title)
return translatedTitle
}
return title
}
+357
View File
@@ -0,0 +1,357 @@
/**
* Created by PanJiaChen on 16/11/18.
*/
/**
* Parse the time to string
* @param {(Object|string|number)} time
* @param {string} cFormat
* @returns {string | null}
*/
export function parseTime(time, cFormat) {
if (arguments.length === 0 || !time) {
return null
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string')) {
if ((/^[0-9]+$/.test(time))) {
// support "1548221490638"
time = parseInt(time)
} else {
// support safari
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
time = time.replace(new RegExp(/-/gm), '/')
}
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
const value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
return value.toString().padStart(2, '0')
})
return time_str
}
/**
* @param {number} time
* @param {string} option
* @returns {string}
*/
export function formatTime(time, option) {
if (('' + time).length === 10) {
time = parseInt(time) * 1000
} else {
time = +time
}
const d = new Date(time)
const now = Date.now()
const diff = (now - d) / 1000
if (diff < 30) {
return '刚刚'
} else if (diff < 3600) {
// less 1 hour
return Math.ceil(diff / 60) + '分钟前'
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前'
} else if (diff < 3600 * 24 * 2) {
return '1天前'
}
if (option) {
return parseTime(time, option)
} else {
return (
d.getMonth() +
1 +
'月' +
d.getDate() +
'日' +
d.getHours() +
'时' +
d.getMinutes() +
'分'
)
}
}
/**
* @param {string} url
* @returns {Object}
*/
export function getQueryObject(url) {
url = url == null ? window.location.href : url
const search = url.substring(url.lastIndexOf('?') + 1)
const obj = {}
const reg = /([^?&=]+)=([^?&=]*)/g
search.replace(reg, (rs, $1, $2) => {
const name = decodeURIComponent($1)
let val = decodeURIComponent($2)
val = String(val)
obj[name] = val
return rs
})
return obj
}
/**
* @param {string} input value
* @returns {number} output value
*/
export function byteLength(str) {
// returns the byte length of an utf8 string
let s = str.length
for (var i = str.length - 1; i >= 0; i--) {
const code = str.charCodeAt(i)
if (code > 0x7f && code <= 0x7ff) s++
else if (code > 0x7ff && code <= 0xffff) s += 2
if (code >= 0xDC00 && code <= 0xDFFF) i--
}
return s
}
/**
* @param {Array} actual
* @returns {Array}
*/
export function cleanArray(actual) {
const newArray = []
for (let i = 0; i < actual.length; i++) {
if (actual[i]) {
newArray.push(actual[i])
}
}
return newArray
}
/**
* @param {Object} json
* @returns {Array}
*/
export function param(json) {
if (!json) return ''
return cleanArray(
Object.keys(json).map(key => {
if (json[key] === undefined) return ''
return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
})
).join('&')
}
/**
* @param {string} url
* @returns {Object}
*/
export function param2Obj(url) {
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
if (!search) {
return {}
}
const obj = {}
const searchArr = search.split('&')
searchArr.forEach(v => {
const index = v.indexOf('=')
if (index !== -1) {
const name = v.substring(0, index)
const val = v.substring(index + 1, v.length)
obj[name] = val
}
})
return obj
}
/**
* @param {string} val
* @returns {string}
*/
export function html2Text(val) {
const div = document.createElement('div')
div.innerHTML = val
return div.textContent || div.innerText
}
/**
* Merges two objects, giving the last one precedence
* @param {Object} target
* @param {(Object|Array)} source
* @returns {Object}
*/
export function objectMerge(target, source) {
if (typeof target !== 'object') {
target = {}
}
if (Array.isArray(source)) {
return source.slice()
}
Object.keys(source).forEach(property => {
const sourceProperty = source[property]
if (typeof sourceProperty === 'object') {
target[property] = objectMerge(target[property], sourceProperty)
} else {
target[property] = sourceProperty
}
})
return target
}
/**
* @param {HTMLElement} element
* @param {string} className
*/
export function toggleClass(element, className) {
if (!element || !className) {
return
}
let classString = element.className
const nameIndex = classString.indexOf(className)
if (nameIndex === -1) {
classString += '' + className
} else {
classString =
classString.substr(0, nameIndex) +
classString.substr(nameIndex + className.length)
}
element.className = classString
}
/**
* @param {string} type
* @returns {Date}
*/
export function getTime(type) {
if (type === 'start') {
return new Date().getTime() - 3600 * 1000 * 24 * 90
} else {
return new Date(new Date().toDateString())
}
}
/**
* @param {Function} func
* @param {number} wait
* @param {boolean} immediate
* @return {*}
*/
export function debounce(func, wait, immediate) {
let timeout, args, context, timestamp, result
const later = function() {
// 据上一次触发时间间隔
const last = +new Date() - timestamp
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
} else {
timeout = null
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args)
if (!timeout) context = args = null
}
}
}
return function(...args) {
context = this
timestamp = +new Date()
const callNow = immediate && !timeout
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
if (callNow) {
result = func.apply(context, args)
context = args = null
}
return result
}
}
/**
* This is just a simple version of deep copy
* Has a lot of edge cases bug
* If you want to use a perfect deep copy, use lodash's _.cloneDeep
* @param {Object} source
* @returns {Object}
*/
export function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'deepClone')
}
const targetObj = source.constructor === Array ? [] : {}
Object.keys(source).forEach(keys => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys])
} else {
targetObj[keys] = source[keys]
}
})
return targetObj
}
/**
* @param {Array} arr
* @returns {Array}
*/
export function uniqueArr(arr) {
return Array.from(new Set(arr))
}
/**
* @returns {string}
*/
export function createUniqueString() {
const timestamp = +new Date() + ''
const randomNum = parseInt((1 + Math.random()) * 65536) + ''
return (+(randomNum + timestamp)).toString(32)
}
/**
* Check if an element has a class
* @param {HTMLElement} elm
* @param {string} cls
* @returns {boolean}
*/
export function hasClass(ele, cls) {
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
}
/**
* Add class to element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function addClass(ele, cls) {
if (!hasClass(ele, cls)) ele.className += ' ' + cls
}
/**
* Remove class from element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function removeClass(ele, cls) {
if (hasClass(ele, cls)) {
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
ele.className = ele.className.replace(reg, ' ')
}
}
+25
View File
@@ -0,0 +1,25 @@
/**
*Created by PanJiaChen on 16/11/29.
* @param {Sting} url
* @param {Sting} title
* @param {Number} w
* @param {Number} h
*/
export default function openWindow(url, title, w, h) {
// Fixes dual-screen position Most browsers Firefox
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top
const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width
const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height
const left = ((width / 2) - (w / 2)) + dualScreenLeft
const top = ((height / 2) - (h / 2)) + dualScreenTop
const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left)
// Puts focus on the newWindow
if (window.focus) {
newWindow.focus()
}
}
+21
View File
@@ -0,0 +1,21 @@
import store from '@/store'
/**
* @param {Array} value
* @returns {Boolean}
* @example see @/views/permission/directive.vue
*/
export default function checkPermission(value) {
if (value && value instanceof Array && value.length > 0) {
const roles = store.getters && store.getters.roles
const permissionRoles = value
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
})
return hasPermission
} else {
console.error(`need roles! Like v-permission="['admin','editor']"`)
return false
}
}
+85
View File
@@ -0,0 +1,85 @@
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// create an axios instance
const service = axios.create({
baseURL: process.env.BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
+58
View File
@@ -0,0 +1,58 @@
Math.easeInOutQuad = function(t, b, c, d) {
t /= d / 2
if (t < 1) {
return c / 2 * t * t + b
}
t--
return -c / 2 * (t * (t - 2) - 1) + b
}
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
var requestAnimFrame = (function() {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
})()
/**
* Because it's so fucking difficult to detect the scrolling element, just move them all
* @param {number} amount
*/
function move(amount) {
document.documentElement.scrollTop = amount
document.body.parentNode.scrollTop = amount
document.body.scrollTop = amount
}
function position() {
return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
}
/**
* @param {number} to
* @param {number} duration
* @param {Function} callback
*/
export function scrollTo(to, duration, callback) {
const start = position()
const change = to - start
const increment = 20
let currentTime = 0
duration = (typeof (duration) === 'undefined') ? 500 : duration
var animateScroll = function() {
// increment the time
currentTime += increment
// find the value with the quadratic in-out easing function
var val = Math.easeInOutQuad(currentTime, start, change, duration)
// move the document.body
move(val)
// do the animation unless its over
if (currentTime < duration) {
requestAnimFrame(animateScroll)
} else {
if (callback && typeof (callback) === 'function') {
// the animation is done so lets callback
callback()
}
}
}
animateScroll()
}
+87
View File
@@ -0,0 +1,87 @@
/**
* Created by PanJiaChen on 16/11/18.
*/
/**
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUsername(str) {
const valid_map = ['admin', 'editor']
return valid_map.indexOf(str.trim()) >= 0
}
/**
* @param {string} url
* @returns {Boolean}
*/
export function validURL(url) {
const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validLowerCase(str) {
const reg = /^[a-z]+$/
return reg.test(str)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUpperCase(str) {
const reg = /^[A-Z]+$/
return reg.test(str)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validAlphabets(str) {
const reg = /^[A-Za-z]+$/
return reg.test(str)
}
/**
* @param {string} email
* @returns {Boolean}
*/
export function validEmail(email) {
const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return reg.test(email)
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function isString(str) {
if (typeof str === 'string' || str instanceof String) {
return true
}
return false
}
/**
* @param {Array} arg
* @returns {Boolean}
*/
export function isArray(arg) {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]'
}
return Array.isArray(arg)
}
+229
View File
@@ -0,0 +1,229 @@
<template>
<div style="background:#f0f2f5;margin-top: -20px;">
<div class="wscn-http404">
<div class="pic-404">
<img class="pic-404__parent" :src="img_404" alt="404">
<img class="pic-404__child left" :src="img_404_cloud" alt="404">
<img class="pic-404__child mid" :src="img_404_cloud" alt="404">
<img class="pic-404__child right" :src="img_404_cloud" alt="404">
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__info">版权所有<a class="link-type" href="https://wallstreetcn.com" target='_blank'>华尔街见闻</a></div>
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">请检查您输入的网址是否正确请点击以下按钮返回主页或者发送错误报告</div>
<a href="/" class="bullshit__return-home">返回首页</a>
</div>
</div>
</div>
</template>
<script>
import img_404 from '@/assets/404_images/404.png'
import img_404_cloud from '@/assets/404_images/404_cloud.png'
export default {
data() {
return {
img_404,
img_404_cloud
}
},
computed: {
message() {
return '特朗普说这个页面你不能进......'
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.wscn-http404 {
position: relative;
width: 1200px;
margin: 20px auto 60px;
padding: 0 100px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
padding: 150px 0;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 150px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #1482f0;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>
+662
View File
@@ -0,0 +1,662 @@
<template>
<div class="dashboard-container">
<img class="login_bg" src="@/assets/images/index.png" />
<div style="height: 100%; position: relative; display: none;">
<div
v-if="beginStep && parseInt(currentStep) >= 0 && parseInt(currentStep) < 8"
class="bg_box"
style="position: absolute; top: 0; left: 0; z-index: 10; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5)"
></div>
<div style="height: 100%; width: 100%; display: flex">
<div :span="3" style="height: 100%; width: 164px">
<div class="grid-content bg-purple left">
<div class="title">
{{ $t('index.wordSpace') }}
</div>
<!-- @click="() => (printData = index)" -->
<div class="card" v-for="item of infoData" :key="item.PrinterID" style="background-color: #f5f5f5; text-align: left">
<span style="font-size: 18px; margin-left: 40px">{{ getPrintName(item.PrinterID, true) }}</span>
<span style="font-size: 9px">{{ item.PrinterType == '-1' ? $t('index.workspacenodata') : item.PrinterType }}</span>
</div>
</div>
</div>
<div :span="18" style="height: 100%; width: calc(100% - 328px)">
<div class="grid-content bg-purple-light mid" id="rte" style="position: relative">
<div class="title" style="position: relative; height: 45px; display: flex; justify-content: space-between">
<el-popover v-if="guideStep" :placement="guideStep[0].placement" width="250" trigger="manual" v-model="guideStep[0].show">
<div class="guide_box">
<div class="guide_title">
{{ $t('guide.title') }}<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ $t(`guide.step1`) }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">{{ $t('guide.skip') }}</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1" size="mini">{{ $t('guide.prev') }}</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ parseInt(currentStep) == guideStep.length - 1 ? $t('guide.complete') : $t('guide.next') }}</el-button>
</div>
</div>
<div slot="reference" :class="{ guide_body: beginStep && currentStep == 0 }" class="switch" style="position: relative; left: 0; top: 3px">
<el-switch active-color="#13ce66" inactive-color="#ff4949" @change="handleOpenSwitch" :value="state"> </el-switch>
</div>
</el-popover>
<el-popover v-if="guideStep" :placement="guideStep[1].placement" width="250" trigger="manual" v-model="guideStep[1].show">
<div class="guide_box">
<div class="guide_title">
{{ $t('guide.title') }}<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ $t(`guide.step2`) }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">{{ $t('guide.skip') }}</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1" size="mini">{{ $t('guide.prev') }}</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep == guideStep.length - 1 ? $t('guide.complete') : $t('guide.next') }}</el-button>
</div>
</div>
<div slot="reference" :class="{ guide_body: beginStep && currentStep == 1 }" class="information" style="font-size: 15px; display: flex; gap: 25px; position: relative; top: 5px">
<div style="display: inline-block">
<div style="text-decoration: underline">
{{ $t('main.doneCopyNum') }}
</div>
<div>{{ doneCopyNum }}</div>
</div>
<div style="display: inline-block">
<div style="text-decoration: underline">
{{ $t('main.cancelCopyNum') }}
</div>
<div>{{ cancelCopyNum }}</div>
</div>
<div style="display: inline-block">
<div style="text-decoration: underline">
{{ $t('main.failNum') }}
</div>
<div>{{ failNum }}</div>
</div>
</div>
</el-popover>
<el-popover v-if="guideStep" :placement="guideStep[2].placement" width="250" trigger="manual" v-model="guideStep[2].show">
<div class="guide_box">
<div class="guide_title">
{{ $t('guide.title') }}<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ $t(`guide.step3`) }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">{{ $t('guide.skip') }}</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1" size="mini">{{ $t('guide.prev') }}</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep == guideStep.length - 1 ? $t('guide.complete') : $t('guide.next') }}</el-button>
</div>
</div>
<div style="display: flex; align-items: center" class="add" slot="reference" :class="{ guide_body: beginStep && currentStep == 2 }">
<span style="font-size: 18px; line-height: 45px">{{ $t('main.submitTask') }}</span>
<el-popover v-if="guideStep" :placement="guideStep[7].placement" width="250" trigger="manual" v-model="guideStep[7].show">
<div class="guide_box">
<div class="guide_title">
{{ $t('guide.title') }}<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ $t(`guide.step8`) }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">{{ $t('guide.skip') }}</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1" size="mini">{{ $t('guide.prev') }}</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep == guideStep.length - 1 ? $t('guide.complete') : $t('guide.next') }}</el-button>
</div>
</div>
<div style="margin-right: 10px" slot="reference" :class="{ guide_body: beginStep && currentStep == 7 }">
<el-button type="primary" @click="newWork" :disabled="infoData.length == 0 && state == false">
{{ $t('index.newWork') }}
</el-button>
</div>
</el-popover>
<el-button type="primary" @click="newNetWork" v-if="false">
{{ $t('index.newWork') }}
</el-button>
<el-button type="primary" @click="openWork()" v-if="true" :disabled="infoData.length == 0 && state == false">
{{ $t('admin.openWork') }}
</el-button>
<!--
<el-select v-model="selectLan">
<el-option
v-for="item in languages"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option> </el-select
>
<el-button icon="el-icon-video-play" @click="start_job">启动任务</el-button>
<el-button icon="el-icon-video-pause" @click="suspend_job">暂停任务</el-button>
<el-button icon="el-icon-circle-close">停止任务</el-button>-->
</div>
</el-popover>
<div class="user">
<span style="margin-right: 20px">{{ $t('admin.hello') }} {{ user }}</span>
<router-link to="/admin" v-if="isAdmin">{{ $t('main.adminMange') }}</router-link>
<router-link to="/">{{ $t('main.out') }}</router-link>
</div>
</div>
<el-popover v-if="guideStep" :placement="guideStep[4].placement" width="250" trigger="manual" v-model="guideStep[4].show">
<div class="guide_box">
<div class="guide_title">
{{ $t('guide.title') }}<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ $t(`guide.step5`) }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">{{ $t('guide.skip') }}</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1" size="mini">{{ $t('guide.prev') }}</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep == guideStep.length - 1 ? $t('guide.complete') : $t('guide.next') }}</el-button>
</div>
</div>
<div class="brief" slot="reference" :class="{ guide_body: beginStep && currentStep == 4 }">
<el-row :gutter="20" style="height: 100%">
<el-col :span="12" style="height: 100%">
<div class="grid-content bg-purple b_left" style="padding-top: 1px !important; display: flex; align-items: center">
<!-- <div class="b_title">{{ $t("main.systemInformation") }}</div> -->
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center">
<img v-if="os.CardsoonModel" :src="os.CardsoonModel.indexOf(`TH80N`) != -1 ? N80N : N800N" width="150" height="95" />
<span style="font-weight: bold; font-size: 18px">{{ $t('main.systemInformation') }}</span>
</div>
<div style="margin-left: 20px">
<div class="item">{{ $t('main.deviceType') }}{{ os.CardsoonModel }}</div>
<div class="item">{{ $t('main.serialNumber') }}{{ os.SystemSn }}</div>
<div class="item">{{ $t('main.hostName') }}{{ os.hostname }}</div>
<div class="item">{{ $t('main.hostIp') }}{{ os.ip }}</div>
</div>
</div>
</el-col>
<el-col :span="12" style="height: 100%">
<div class="grid-content bg-purple b_mid" style="padding-top: 1px !important; display: flex; align-items: center">
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center">
<span style="font-weight: bold; font-size: 18px">{{ $t('main.hardwareConfiguration') }}</span>
</div>
<div style="margin-left: 20px">
<!-- <div class="item">
{{ $t("main.hardwareConfiguration") }}
</div> -->
<div class="item">{{ $t('main.Cpu') }}{{ os.cpu }}</div>
<div class="item">{{ $t('main.Memory') }}{{ os.ram }}</div>
<div class="item">{{ $t('main.hardDrive') }}{{ os.HD }}</div>
<div class="item">
{{ $t('main.cacheSpace') }}{{ os.remainCacheSpace }} /
{{ os.totalCacheSpace }}
</div>
</div>
</div>
</el-col>
</el-row>
</div>
</el-popover>
<!-- <div
class="brief"
v-if="infoData.length != 0 && false"
style="margin-top: 20px; height: 200px"
>
<el-row :gutter="10" style="height: 100%">
<el-col :span="6" style="height: 100%">
<div
class="grid-content bg-purple b_left"
style="padding-top: 1px !important"
>
<div class="b_title">
{{ $t("index.systemState") }}
</div>
<div style="margin-top: 30px">
<div class="item">
{{ $t("index.wordSpaceOnline")
}}{{
printerStatus[infoData[printData].PrinterID]
? $t(
`index.${
printerStatus[infoData[printData].PrinterID]
}`
)
: ""
}}
</div>
<div class="item">
{{ $t("index.equipModel")
}}{{ infoData[printData].ModeName.replace("Fagoo", "") }}
</div>
<div class="item">
{{ $t("index.series") }}{{ infoData[printData].SerialNo }}
</div>
<div class="item">
{{ $t("index.machineVers")
}}{{ infoData[printData].FirewareVersion }}
</div>
</div>
</div>
</el-col>
<el-col :span="5" style="height: 100%">
<div
class="grid-content bg-purple b_left"
style="padding-top: 1px !important; border-right: 0"
>
<div class="b_title"></div>
<div style="margin-top: 30px">
<div class="item">
{{ $t("index.numbers") }}{{ infoData[printData].CardNum }}
</div>
<div class="item">
{{ $t("index.dice")
}}{{
infoData[printData].DualSide == 1
? this.$t("index.twoDice")
: this.infoData[printData] != null
? this.$t("index.sigleDice")
: ""
}}
</div>
<div class="item">
{{ $t("index.ribbonType")
}}{{ infoData[printData].RibbonType }}
</div>
</div>
</div>
</el-col>
<el-col :span="5" style="height: 100%">
<div
class="grid-content bg-purple b_mid"
style="
padding-top: 1px !important;
height: 100%;
display: flex;
align-items: center;
"
>
<el-progress
type="circle"
:format="ribbonFormat"
:key="printData"
width="75"
:percentage="
infoData[printData].RibbonAmount == undefined ||
infoData[printData].RibbonAmount == '0'
? 0
: (
(infoData[printData].RibbonAmount /
getRibbonAmount) *
100
).toFixed(0)
"
:color="'#67C23A'"
></el-progress>
<div style="margin-left: 20px">
<div>{{ $t("index.ribbonNumber") }}</div>
<div>
{{ infoData[printData].RibbonAmount }}/{{
getRibbonAmount
}}
</div>
</div>
</div>
</el-col>
<el-col :span="8" style="height: 100%">
<div
class="grid-content bg-purple b_mid"
style="
padding-top: 1px !important;
height: 100%;
display: flex;
align-items: center;
"
>
<el-progress
type="circle"
:key="printData"
:format="remaCapaFormat"
width="75"
:percentage="
infoData[printData].PrinterRemaCapa == undefined ||
infoData[printData].PrinterRemaCapa == '0'
? 0
: (
(infoData[printData].PrinterRemaCapa /
getPrinterRemaCapaAmount) *
100
).toFixed(0)
"
:color="'#67C23A'"
></el-progress>
<div style="margin-left: 20px">
<div>
{{ $t("index.inCardState") }}
</div>
<div>
<el-button-group>
<el-button
type="primary"
size="mini"
@click="() => printerTypeChange('auto')"
>{{ $t("index.autoRefresh") }}</el-button
>
<el-dropdown @command="handleTypeCommand">
<el-button type="primary" size="mini"
>{{ $t("index.refreshMore")
}}<i class="el-icon-arrow-down el-icon--right"></i
></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="item in _PrinterTypeList"
:command="item.value"
>{{ item.label }}</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
</el-button-group>
</div>
</div>
</div>
</el-col>
</el-row>
</div> -->
<el-popover v-if="guideStep" :placement="guideStep[5].placement" width="250" trigger="manual" v-model="guideStep[5].show">
<div class="guide_box">
<div class="guide_title">
{{ $t('guide.title') }}<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ $t(`guide.step6`) }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">{{ $t('guide.skip') }}</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1" size="mini">{{ $t('guide.prev') }}</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep == guideStep.length - 1 ? $t('guide.complete') : $t('guide.next') }}</el-button>
</div>
</div>
<div slot="reference" :class="{ guide_body: beginStep && currentStep == 5 }" class="machines work" style="height: 130px; margin-top: 10px">
<el-table :data="infoData" max-height="180" size="mini" style="width: 100%" :empty-text="$t('index.nodata')" row-class-name="rowclass">
<el-table-column prop="gzz" :label="$t('main.tableMachine')" min-width="100">
<template slot-scope="scope">
{{ getPrintName(scope.row.PrinterID, true) }}
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('main.tableState')" min-width="80">
<template slot-scope="scope">{{ $t('main.tableOnline') }}</template>
</el-table-column>
<el-table-column prop="SerialNo" :label="$t('main.tableSerialNo')" min-width="80"> </el-table-column>
<el-table-column prop="RibbonType" :label="$t('main.tableRibbonType')" min-width="80"> </el-table-column>
<el-table-column prop="RibbonAmount" :label="$t('main.tableRibbonAmount')" min-width="100">
<template slot-scope="scope">
<el-progress
:color="customColors"
:width="45"
define-back-color="#ebeef5"
:format="() => scope.row.RibbonAmount"
:stroke-width="3"
:percentage="
scope.row.RibbonAmount == undefined || scope.row.RibbonAmount == '0'
? 0
: parseFloat(((scope.row.RibbonAmount / getRibbonAmount(scope.row.ModeName, scope.row.RibbonType)) * 100).toFixed(0))
"
></el-progress>
</template>
</el-table-column>
<el-table-column prop="PrinterType" :label="$t('main.tablePrinterType')" min-width="100">
<template slot-scope="scope">
<el-dropdown trigger="click" @command="(e) => handleMachineCommand(e, scope.row.PrinterID)">
<span class="el-dropdown-link" style="color: #000; font-size: 12px"> {{ scope.row.PrinterType }}<i class="el-icon-arrow-down el-icon--right"></i> </span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="auto">
<span style="color: #000; font-size: 12px">{{ $t('main.autoSetting') }}</span>
</el-dropdown-item>
<el-dropdown-item command="null">
<span style="color: #000; font-size: 12px">{{ $t('main.cancelSetting') }}</span>
</el-dropdown-item>
<el-dropdown-item v-for="item in _PrinterTypeList" :key="item.value" :command="item.value">
<span style="color: #000; font-size: 12px">{{ item.label }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
<el-table-column prop="PrinterRemaCapa" :label="$t('main.tablePrinterRemaCapa')" min-width="100">
<template slot-scope="scope">
<el-progress
:color="customColors"
:width="45"
define-back-color="#ebeef5"
:format="() => (scope.row.PrinterRemaCapa > 0 ? scope.row.PrinterRemaCapa : 0)"
:stroke-width="3"
:percentage="
scope.row.PrinterRemaCapa == undefined || scope.row.PrinterRemaCapa == '0'
? 0
: parseFloat((((scope.row.PrinterRemaCapa > 0 ? scope.row.PrinterRemaCapa : 0) / getPrinterRemaCapaAmount(scope.row.ModeName)) * 100).toFixed(0))
"
></el-progress>
</template>
</el-table-column>
</el-table>
</div>
</el-popover>
<el-popover v-if="guideStep" :placement="guideStep[6].placement" width="250" trigger="manual" v-model="guideStep[6].show">
<div class="guide_box">
<div class="guide_title">
{{ $t('guide.title') }}<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ $t(`guide.step7`) }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">{{ $t('guide.skip') }}</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1" size="mini">{{ $t('guide.prev') }}</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep == guideStep.length - 1 ? $t('guide.complete') : $t('guide.next') }}</el-button>
</div>
</div>
<div slot="reference" :class="{ guide_body: beginStep && currentStep == 6 }">
<el-tabs v-model="activeName" style="background-color: #fff; padding-bottom: 20px; max-height: 240px; margin-top: 10px 20px">
<el-tab-pane :label="$t(`index.workList`)" name="first">
<div class="work">
<el-table :data="tasks" max-height="180" style="width: 100%" size="mini" :empty-text="$t('index.nodata')" row-class-name="rowclass">
<el-table-column label="#" min-width="38">
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="jId" :label="$t('index.wordID')" min-width="130"> </el-table-column>
<el-table-column prop="DataSource" :label="$t('index.wordOrigin')" min-width="85"> </el-table-column>
<el-table-column :label="$t('index.wordSpace')" min-width="60">
<template slot-scope="scope">
{{ getPrintName(scope.row.PrinterID, true) }}
</template>
</el-table-column>
<el-table-column :label="$t('main.USBType')" min-width="90">
<template slot-scope="scope">
{{ scope.row.PrinterType }}
</template>
</el-table-column>
<el-table-column min-width="80" prop="TaskCapacity" :label="$t('main.TaskCapacity')"> </el-table-column>
<el-table-column v-if="false" prop="DataDir" :label="$t('main.taskFile')"> </el-table-column>
<el-table-column prop="CreateTime" :label="$t('index.createTime')" min-width="120"> </el-table-column>
<el-table-column
v-if="false"
prop="usb_version"
:label="$t('main.USBVersion')"
:filters="[
{ text: '2.0', value: 2 },
{ text: '3.0', value: 3 }
]"
:filter-method="filterHandler"
>
</el-table-column>
<el-table-column prop="FinishTime" :label="$t('index.finishedTime')" min-width="120"> </el-table-column>
<el-table-column prop="JobCompletion" min-width="60" :label="$t('index.finishedNumber')"> </el-table-column>
<el-table-column :label="$t('index.state')" prop="JobStatus" width="110">
<template slot-scope="scope">
<el-dropdown trigger="click" @command="(e) => handleTaskCommand(e, scope.row.JobID)" :disabled="scope.row.JobStatus != 'Fail'">
<span class="el-dropdown-link" style="color: #000; font-size: 12px">
{{ jobState(scope.row.JobStatus) }}
<i class="el-icon-arrow-down el-icon--right" v-if="scope.row.JobStatus == 'Fail'"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="refresh">
<span style="color: #000; font-size: 12px">{{ $t('main.commandRefresh') }}</span>
</el-dropdown-item>
<el-dropdown-item command="delete">
<span style="color: #000; font-size: 12px">{{ $t('main.commandDelete') }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-progress
:color="customColors"
:width="45"
define-back-color="#ebeef5"
:stroke-width="3"
:percentage="parseFloat(scope.row.TaskPercentage.replace(`%`, ``))"
v-if="scope.row.JobStatus == `Copying`"
></el-progress>
<!-- <el-button
style="margin-left: 5px; height: 30px; width: 30px"
icon="el-icon-refresh"
circle
v-if="scope.row.JobStatus == 'Fail'"
@click="restart_job(scope.$index)"
type="primary"
size="mini"
>
</el-button>
<el-button
style="margin-left: 5px; height: 30px; width: 30px"
icon="el-icon-delete"
circle
v-if="scope.row.JobStatus == 'Fail'"
@click="remove_job(scope.$index)"
type="danger"
size="mini"
>
</el-button>
<el-button
style="margin-left: 5px; height: 30px; width: 30px"
icon="el-icon-video-pause"
circle
v-else-if="scope.row.JobStatus == 'Printing'"
@click="suspend_job(scope.$index)"
type="primary"
size="mini"
>
</el-button>
<el-button
style="margin-left: 5px; height: 30px; width: 30px"
icon="el-icon-video-play"
circle
v-else-if="scope.row.JobStatus == '暂停'"
@click="start_job(scope.$index)"
type="primary"
>
</el-button> -->
</template>
</el-table-column>
<!--<el-table-column prop="process" label="进度">
</el-table-column>
<el-table-column
prop="info"
label="信息">
</el-table-column>-->
</el-table>
</div>
</el-tab-pane>
<el-tab-pane :label="$t(`index.curLog`)" name="second">
<div class="work">
<div class="w_title">
<div class="w_right">
<el-button type="text" icon="el-icon-document-copy" v-if="isAdmin" @click="getWLogData()">
{{ $t('index.openHistoryLog') }}
</el-button>
</div>
</div>
<div class="c_log" v-html="log"></div></div
></el-tab-pane>
</el-tabs>
</div>
</el-popover>
<s-footer></s-footer>
</div>
</div>
<div :span="3" style="height: 100%; width: 164px">
<div class="grid-content bg-purple right" style="overflow: hidden">
<el-popover v-if="guideStep" :placement="guideStep[3].placement" width="250" trigger="manual" v-model="guideStep[3].show">
<div class="guide_box">
<div class="guide_title">
{{ $t('guide.title') }}<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ $t(`guide.step4`) }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">{{ $t('guide.skip') }}</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1" size="mini">{{ $t('guide.prev') }}</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep == guideStep.length - 1 ? $t('guide.complete') : $t('guide.next') }}</el-button>
</div>
</div>
<div class="title" slot="reference" :class="{ guide_body: beginStep && currentStep == 3 }">
{{ $t('index.notice') }}
<!--<div class="log" v-for="(item, index) of infoData.sysLogs">
{{ item }}
</div>-->
</div>
</el-popover>
<warnCard v-for="item of errList" :data="item" @ok="okWarning" v-if="item.show" />
</div>
</div>
</div>
<div v-if="dialogVisible">
<el-dialog :visible.sync="dialogVisible" width="1200px" :destroy-on-close="true" :close-on-click-modal="false" :title="$t('index.newWork')">
<work
:dice="DualSide == 1 ? 2 : 1"
@jobPost="closeWork"
@addError="addError"
:isNew="isNew"
:saveWorkList="saveWorkList"
:printerList="infoData"
:shareDisk="shareDisk"
:isCopy="isLocal"
:filterPassedType="filterPassedType"
:sizeType="_PrinterTypeList"
>
</work>
</el-dialog>
</div>
<div v-if="dialogNetVisible">
<el-dialog :visible.sync="dialogNetVisible" width="1200px" :destroy-on-close="true" :close-on-click-modal="false" :title="$t('index.newWork')">
<worknet :dice="DualSide == 1 ? 2 : 1" @jobPost="closeNetWork" :printerList="infoData"> </worknet>
</el-dialog>
</div>
<el-dialog :title="$t('index.entireLog')" :visible.sync="dialogWholeLog">
<div class="wLog" v-html="wlog" style="text-align: left"></div>
</el-dialog>
<!-- 开始用户引导 -->
<el-dialog class="guide_dialog" top="30vh" :show-close="false" title="温馨提示" :visible.sync="showStep" width="350px" :close-on-click-modal="false">
<div class="start_box">
<div class="start_content">欢迎使用卡树自动拷贝打印系统我们将引导您使用本系统请您耐心接受引导</div>
<div class="start_hide">
<el-checkbox v-model="hideStep">我已熟悉系统不再显示引导</el-checkbox>
</div>
<div class="start_btn">
<el-button @click="startStep" type="primary">开始引导</el-button>
</div>
</div>
</el-dialog>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'dashboard',
computed: {
...mapGetters(['name', 'roles'])
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-container {
position: fixed;
height: 100%;
width: 100%;
.login_bg {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
}
}
</style>
+253
View File
@@ -0,0 +1,253 @@
<template>
<div class="login-container">
<img class="login_bg" src="@/assets/images/login_bg.png" />
<div class="flex_box form_box">
<el-form class="flex_box flex_col flex_row_center login-form" autoComplete="on" :model="loginForm" :rules="loginRules" ref="loginForm" label-position="left">
<img class="login_logo" src="@/assets/images/logo.png" />
<div class="login_box">
<div class="login_title">用户登录</div>
<div class="login_label">用户名</div>
<el-form-item prop="username">
<el-input name="username" type="text" v-model="loginForm.username" autoComplete="on" placeholder="username" />
</el-form-item>
<div class="login_label">密码</div>
<el-form-item prop="password">
<el-input name="password" :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password" autoComplete="on"
placeholder="password"></el-input>
<span class="show-pwd" @click="showPwd">
</span>
</el-form-item>
<el-form-item>
<div class="login_check">
<el-checkbox v-model="isChecked">记住账号密码</el-checkbox>
</div>
</el-form-item>
<el-form-item>
<el-button class="login_btn" type="primary" :loading="loading" @click.native.prevent="handleLogin">{{ $t('login.logIn') }}</el-button>
</el-form-item>
</div>
</el-form>
</div>
</div>
</template>
<script>
import { validUsername } from '@/utils/validate'
export default {
name: 'login',
data() {
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('请输入正确的用户名'))
} else {
callback()
}
}
const validatePass = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入您的密码'))
} else {
callback()
}
}
return {
loginForm: {
username: 'admin',
password: 'admin'
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePass }]
},
isChecked: true,
loading: false,
pwdType: 'password'
}
},
methods: {
showPwd() {
if (this.pwdType === 'password') {
this.pwdType = ''
} else {
this.pwdType = 'password'
}
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('Login', this.loginForm).then(() => {
this.loading = false
this.$router.push({ path: '/' })
}).catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
$bg:#F1F1F1;
$light_gray:#000000;
/* reset element-ui css */
.login-container {
.el-input {
display: inline-block;
height: 60px;
border-radius: 30px;
width: 100%;
background: #F1F1F1;
border-radius: 30px;
color: #000;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 60px;
font-size: 24px;
border-radius: 30px;
&:-webkit-autofill {
border-radius: 30px;
-webkit-box-shadow: 0 0 0px 1000px $bg inset !important;
-webkit-text-fill-color: #000 !important;
}
}
}
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
$bg:#F1F1F1;
$dark_gray:#889aa4;
$light_gray:#eee;
.login-container {
position: fixed;
height: 100%;
width: 100%;
.login_bg {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
}
.form_box {
position: absolute;
left: 0;
right: 0;
z-index: 9;
width: 100%;
height: 100%;
padding: 0 0 0 120px;
}
.login-form {
height: 100%;
.login_logo {
width: 844px;
height: 158px;
margin-bottom: 48px;
}
.login_box {
width: 450px;
background-color: #fff;
border-radius: 24px;
padding: 48px;
.login_title {
font-weight: 500;
font-size: 30px;
color: #00C325;
line-height: 44px;
text-align: center;
margin-bottom: 48px;
}
.login_label {
font-size: 24px;
color: #B2B2B2;
line-height: 36px;
margin-bottom: 24px;
}
.login_check {
::v-deep {
.el-checkbox__inner {
width: 24px;
height: 24px;
}
.el-checkbox__input.is-checked .el-checkbox__inner {
background-color: #00C325;
border-color: #00C325;
}
.el-checkbox__inner::after {
width: 6px;
height: 14px;
left: 8px;
}
.el-checkbox__label {
font-size: 24px;
line-height: 24px;
}
.el-checkbox__input.is-checked+.el-checkbox__label {
color: #00C325;
}
}
}
.login_btn {
width: 100%;
height: 60px;
background: #F1F1F1;
border-radius: 33px;
border: 3px solid #00BCC3;
font-size: 24px;
color: #00BCC3;
}
}
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
width: 30px;
display: inline-block;
&_login {
font-size: 20px;
}
}
.title {
font-size: 26px;
font-weight: 400;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
}
</style>