276d8e3e by 赵千

init

0 parents
Showing 106 changed files with 4799 additions and 0 deletions
# https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
# just a flag
ENV = 'development'
NODE_ENV=development
# base api
VUE_APP_BASE_API = '/dev-api'
# 开发环境
VUE_APP_API_BASE_URL = 'http://192.168.2.38:18010'
# just a flag
ENV = 'demonstration'
NODE_ENV=development
# base api
VUE_APP_BASE_API = '/dev-api'
# 演示,正式后端
VUE_APP_API_BASE_URL = 'http://39.103.195.0/dev-api'
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.history
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
package-lock.json
MIT License
Copyright (c) 2017-present PanJiaChen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# 安装依赖
npm install
# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org
# 启动服务
npm run dev
```
浏览器访问 http://localhost:8888
## 发布
# 构建生产环境
npm run build
```
## 其它
```bash
# 预览发布环境效果
npm run preview
# 预览发布环境效果 + 静态资源分析
npm run preview -- --report
module.exports = {
presets: [
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
// '@vue/cli-plugin-babel/preset'
[
"@vue/app",
{
"useBuiltIns": "entry",
polyfills: [
'es6.promise',
'es6.symbol'
]
}
]
],
'env': {
'development': {
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
// https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
'plugins': ['dynamic-import-node']
}
}
}
{
"name": "js.rent",
"version": "4.4.0",
"description": "HOUTAI",
"author": "psh",
"scripts": {
"dev": "vue-cli-service serve --mode development",
"dev-test": "vue-cli-service serve --mode test",
"dev-demonstration": "vue-cli-service serve --mode demonstration",
"build": "vue-cli-service build --mode build-development",
"build-test": "vue-cli-service build --mode build-test",
"build-demonstration": "vue-cli-service build --mode build-demonstration"
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"axios": "^0.21.1",
"babel-polyfill": "^6.26.0",
"core-js": "^3.6.5",
"js-cookie": "2.2.0",
"lodash": "^4.17.21",
"node-sass": "^4.14.1",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"vue": "2.6.10",
"vue-pdf": "^4.3.0",
"vue-router": "3.0.2",
"vuedraggable": "2.20.0",
"vuex": "3.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-plugin-unit-jest": "4.4.4",
"@vue/cli-service": "4.4.4",
"@vue/test-utils": "1.0.0-beta.29",
"autoprefixer": "9.5.1",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "2.4.2",
"connect": "3.6.6",
"element-ui": "^2.15.6",
"html-webpack-plugin": "3.2.0",
"runjs": "4.3.2",
"sass-loader": "8.0.2",
"sass-resources-loader": "^2.2.1",
"script-ext-html-webpack-plugin": "2.1.3",
"serve-static": "1.13.2",
"style-resources-loader": "^1.4.1",
"svg-sprite-loader": "4.1.3",
"svgo": "^2.3.1",
"vue-template-compiler": "2.6.10"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"bugs": {
"url": "https://github.com/PanJiaChen/vue-element-admin/issues"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"keywords": [
"vue",
"admin",
"dashboard",
"element-ui",
"boilerplate",
"admin-template",
"management-system"
],
"license": "MIT",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/PanJiaChen/vue-element-admin.git"
}
}
module.exports = {
plugins: {
autoprefixer: {}
},
}
No preview for this file type
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="referrer" content="no-referrer" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<div id="app">
<router-view />
</div>
</template>
export const httpStatus = (() => {
const status = {
OK: { code: 200, text: 'OK', description: '' },
CREATED: { code: 201, text: 'CREATED', description: '' },
DELETED: { code: 204, text: 'DELETED', description: '' },
NOT_MODIFIED: { code: 304, text: 'NOT MODIFIED', description: '' },
BAD_REQUEST: { code: 400, text: 'BAD REQUEST', description: '不正确的请求信息' },
METHOD_NOT_ALLOWED: { code: 405, text: 'METHOD NOT ALLOWED', description: '请求方法不支持' },
UNSUPPORTED_MEDIA_TYPE: { code: 415, text: 'UNSUPPORTED MEDIA TYPE', description: '媒体类型不支持' },
INTERNAL_SERVER_ERROR: { code: 500, text: 'INTERNAL SERVER_ERROR', description: '服务器内部错误' },
PAGE_NOT_FOUND: { code: 404, text: 'PAGE NOT FOUND', description: '网络资源无法访问' },
NOT_AUTHORIZED: { code: 401, text: 'NOT AUTHORIZED', description: '未经授权的访问' },
FORBIDDEN: { code: 403, text: 'FORBIDDEN', description: '禁止访问' },
UNPROCESSABLE_ENTITY: { code: 422, text: 'UNPROCESSABLE ENTITY', description: '' },
SESSION_TIME_OUT: { code: 419, text: 'SESSION_TIME_OUT', description: '会话超时' },
LOGIN_FAILURE:{ code: 11001, text: 'LOGIN_FAILURE', description: '登录失效' },
}
status.CODES = {
/**
* 成功
*/
SUCCESS: {
200: status.OK,
201: status.CREATED,
204: status.DELETED,
304: status.NOT_MODIFIED
},
/**
* 程序错误或恶意攻击
*/
PROGRAM_ERROR: {
400: status.BAD_REQUEST,
405: status.METHOD_NOT_ALLOWED,
415: status.UNSUPPORTED_MEDIA_TYPE,
500: status.INTERNAL_SERVER_ERROR
},
/**
* 网络访问错误
*/
NETWORK_ERROR: {
404: status.PAGE_NOT_FOUND
},
/**
* 权限错误
* TODO 419未确定
*/
AUTH_ERROR: {
401: status.NOT_AUTHORIZED,
403: status.FORBIDDEN,
419: status.SESSION_TIME_OUT
},
/**
* 正常交互错误
*/
COMMUNICATION_ERROR: {
422: status.UNPROCESSABLE_ENTITY
},
/**
* 登录失效
*/
LOGIN_FAILURE: {
11001: status.LOGIN_FAILURE
}
}
return status
})()
import request from '@/utils/request'
class dictionaries {
// 获取所有字典接口
async getDictionary(data){
return request({
url: '/system/sysDictionary/getDictionary',
method: 'get',
params: data,
})
}
// 获取字典根据字典标识码
async getDdicById (id) {
return request({
url: '/system/sysDictionary/getDdicById',
method: 'get',
params: {
id: id
},
})
}
//入住管理 导出
async checkexportData(data){
return request({
url:'/rent/zLiveManage/exportData',
method:'post',
data,
responseType: 'blob'
})
}
// 获取字典根据字典名称
async getDdicByMC (mc) {
return request({
url: '/system/sysDictionary/getDdicByMC',
method: 'get',
params: {
mc: mc
},
})
}
//
async getDetail () {
return request({
url: '/system/sysUser/getAddressInfo',
method: 'get',
})
}
// 根据市id,获取行政区-县区
async getDistByCity () {
return request({
url: '/xzq/getDistByCity',
method: 'get',
params: {
id: '350200000000'
},
})
}
// 行政区-县区-街道
async getXzq () {
return request({
url: '/xzq/getXzq',
method: 'get'
})
}
// 联想查询接口
async searchDepartmentByKeys () {
return request({
url: '/resource/fDepartment/searchDepartmentByKeys',
method: 'get',
})
}
async getAllDepartment(){
return request({
url:'/resource/fDepartment/getAllDepartment',
method:'get'
})
}
}
export default new dictionaries()
import request from '@/utils/request'
export function doLogin (data) {
return request({
method: 'post',
url: '/doLogin',
data
})
}
export function verifyCode () {
return request({
method: 'get',
url: '/verifyCode',
})
}
// 获取当前用户登录的信息
export function getCurrentUserInfo () {
return request({
method: 'get',
url: '/getSysUserDo',
})
}
// 忘记密码
export function forgetPassword (data) {
return request({
method: 'post',
url: '/system/sysUser/forgetPassword',
data
})
}
// 入驻
export function settle (data) {
return request({
method: 'post',
url: '/settle',
data: data
})
}
// 获取当前登录用户有权限的分店列表
export function getOrganizationUserList () {
return request({
url: '/getOrganizationUserList',
method: 'get'
})
}
// 重新设置当前登录用户信息
export function setOrganizationId (organizationId) {
return request({
url: '/setOrganizationId',
method: 'get',
params: {
organizationId: organizationId
}
})
}
import request from '@/utils/request'
export function login (data) {
return request({
url: '/doLogin',
method: 'post',
data
})
}
export function getMenuInfo () {
return request({
url: '/getWebMenuRole',
method: 'get',
})
}
export function logout () {
return request({
url: '/admin/logout',
method: 'post'
})
}
<!--面包屑 -->
<template>
<el-breadcrumb class="app-breadcrumb" separator=">">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
<span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{
item.meta.title
}}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
export default {
data () {
return {
levelList: null
}
},
watch: {
$route (route) {
// if you go to the redirect page, do not update the breadcrumbs
if (route.path.startsWith('/redirect/')) {
return
}
this.getBreadcrumb()
}
},
created () {
this.getBreadcrumb()
},
methods: {
getBreadcrumb () {
// only show routes with meta.title
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
const first = matched[0]
if (!this.isDashboard(first)) {
matched = [{ path: '/home', meta: { title: '首页' } }].concat(matched)
}
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
},
isDashboard (route) {
const name = route && route.name
if (!name) {
return false
}
return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
},
pathCompile (path) {
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
const { params } = this.$route
var toPath = pathToRegexp.compile(path)
return toPath(params)
},
handleLink (item) {
const { redirect, path } = item
if (redirect) {
this.$router.push(redirect)
return
}
this.$router.push(this.pathCompile(path))
}
}
}
</script>
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 43px;
margin-left: 8px;
.no-redirect {
cursor: text;
}
/deep/.el-breadcrumb__separator {
font-weight: 500;
}
}
</style>
<!--控制左侧菜单收缩显示-->
<template>
<div style="padding: 0 15px;" @click="toggleClick">
<svg-icon :class="{'is-active':isActive}" class="hamburg" icon-class="hamburg" />
</div>
</template>
<script>
export default {
name: 'Hamburger',
props: {
isActive: {
type: Boolean,
default: false
}
},
methods: {
toggleClick() {
this.$emit('toggleClick')
}
}
}
</script>
<style scoped>
.hamburg {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
transition: all 0.2s ease-in;
}
.hamburg.is-active {
transform: rotate(180deg);
}
</style>
<!--显示svg文件图标-->
<template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
import { isExternal } from '@/utils/validate'
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover!important;
display: inline-block;
}
</style>
.dialogBox {
border-radius: 8px;
overflow: hidden;
background: #FFFFFF;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.10);
.dialog_title {
display: flex;
position: relative;
top: -2px;
b {
flex: 1;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
.el-dialog__header {
height: 50px;
background: #FCFDFD;
border-radius: 4px 4px 0 0;
position: relative;
}
.dialog_full {
position: absolute;
top: 0;
right: 6%;
}
.el-dialog__body {
max-height: 88vh;
overflow-y: scroll;
overflow-x: hidden;
}
.dialog_footer {
flex-direction: column;
.dialog_button {
margin-top: 8px;
}
}
}
.el-dialog__wrapper {
overflow: hidden;
}
\ No newline at end of file
<template>
<el-dialog :visible.sync="dialogVisible" v-dialogDrag :width="width" @close="closeDialog('ruleForm', !showEnter)"
:fullscreen="fullscreen" top="0" :append-to-body="true" :lock-scroll="true" :close-on-click-modal="false"
custom-class="dialogBox" :destroy-on-close="true" :class="[customClass]" id="dialogBox" ref="dialogBox">
<div slot="title" class="dialog_title" ref="dialogTitle">
<b>{{ title }}</b>
<div v-if="isFullscreen" class="dialog_full">
<i class="el-icon-rank" v-if="fullscreen" @click="handleFullscreen"></i>
<i class="el-icon-full-screen" v-else @click="handleFullscreen" />
</div>
</div>
<div class="dialogBox-content" :style="{ height: scrollerHeight ? scrollerHeight : 'auto' }" :key="key">
<slot></slot>
</div>
<div slot="footer" class="dialog_footer" ref="dialogFooter" v-if="isButton">
<div class="dialog_button" v-if="normal">
<el-button @click="closeDialog('ruleForm',)" v-if="isReset && !isSave && showEnter">确定</el-button>
<el-button @click="closeDialog('ruleForm', showEnter)" v-if="isReset">取消</el-button>
<el-button type="primary" plain @click="submitForm('ruleForm')" v-if="isSave" :loading="saveloding">
{{ saveButton }}</el-button>
</div>
<div class="dialog_button" v-else>
<el-button @click="closeDiaActivity(true)">确定</el-button>
<el-button @click="closeDiaActivity(false)">取消</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
activity: {
type: Boolean,
default: false,
},
normal: {
type: Boolean,
default: true,
},
showEnter: {
type: Boolean,
default: true,
},
isButton: {
type: Boolean,
default: true,
},
multiple: {
type: Boolean,
default: false,
},
width: {
type: String,
default: '70%',
},
title: {
type: String,
default: '',
},
customClass: {
type: String,
default: '',
},
topHeight: {
type: String,
default: '0',
},
isFullscreen: {
type: Boolean,
default: true,
},
isSave: {
type: Boolean,
default: true,
},
saveButton: {
type: String,
default: '提交',
},
isReset: {
type: Boolean,
default: true,
},
saveloding: {
type: Boolean,
default: false,
},
},
data () {
return {
key: 0,
dialogVisible: false,
fullscreen: false,
scrollerHeight: '',
}
},
methods: {
isShow () {
this.dialogVisible = true
},
isHide () {
this.dialogVisible = false
this.key++
},
handleFullscreen () {
this.fullscreen = !this.fullscreen
let height = document.getElementById('dialogBox').clientHeight
if (!this.fullscreen) {
this.scrollerHeight = false
} else {
this.scrollerHeight = (window.innerHeight - 180) + 'px'
}
},
submitForm (ruleForm) {
if (!this.multiple) {
this.$parent.submitForm(ruleForm)
} else {
this.$emit('submitForm', ruleForm);
}
},
closeDialog (ruleForm, flag) {
console.log(456789, this.multiple)
this.key++
if (!this.multiple) {
if (this.$parent.closeDialog) {
// console.log(1)
this.$parent.closeDialog(ruleForm)
} else {
// console.log(2)
this.dialogVisible = false;
}
} else {
this.$emit('closeDialog', ruleForm, flag);
}
},
closeDiaActivity (flag) {
this.$emit('closeDialog', flag);
}
},
}
</script>
<style rel="stylesheet/scss" lang="scss" >
@import "./index.scss";
</style>
\ No newline at end of file
## 这个是弹框组件,对于element自带的组件进行封装,方便修改全局样式做统一操作
### 使用时在组件中引用
```
import dialogBox from '@/components/dialogBox/index'
<dialogBox ref="dialog" title="标题">
**在这里面写弹框内容**
</dialogBox>
*在父组件中使用 的按钮提交方法*
submitForm(ruleForm) {
}
```
##### 如果有多个弹框
```
import dialogBox from '@/components/dialogBox/index'
<dialogBox ref="dialog" @submitForm="自定义方法" title="标题" :multiple="true">
**在这里面写弹框内容**
</dialogBox>
*在父组件中使用 的按钮提交方法*
自定义方法(ruleForm) {
}
```
## 打开该dialog:
this.$refs.dialog.isShow();
## 隐藏该dialog:
this.$refs.dialog.isHide();
\ No newline at end of file
export default {
selection: {
renderHeader: (h, { store }) => {
return (
<el-checkbox
disabled={store.states.data && store.states.data.length === 0}
indeterminate={
store.states.selection.length > 0 && !store.states.isAllSelected
}
nativeOn-click={store.toggleAllSelection}
value={store.states.isAllSelected}
/>
)
},
renderCell: (h, { row, column, store, $index }) => {
return (
<el-checkbox
nativeOn-click={event => event.stopPropagation()}
value={store.isSelected(row)}
disabled={
column.selectable
? !column.selectable.call(null, row, $index)
: false
}
on-input={() => {
store.commit('rowSelectedChanged', row)
}}
/>
)
},
sortable: false,
resizable: false
},
index: {
renderHeader: (h, scope) => {
return <span>{scope.column.label || '#'}</span>
},
renderCell: (h, { $index, column }) => {
let i = $index + 1
const index = column.index
if (typeof index === 'number') {
i = $index + index
} else if (typeof index === 'function') {
i = index($index)
}
return <div>{i}</div>
},
sortable: false
},
expand: {
renderHeader: (h, scope) => {
return <span>{scope.column.label || ''}</span>
},
renderCell: (h, { row, store }, proxy) => {
const expanded = store.states.expandRows.indexOf(row) > -1
return (
<div
class={
'el-table__expand-icon ' +
(expanded ? 'el-table__expand-icon--expanded' : '')
}
on-click={e => proxy.handleExpandClick(row, e)}
>
<i class='el-icon el-icon-arrow-right' />
</div>
)
},
sortable: false,
resizable: false,
className: 'el-table__expand-column'
}
}
import lbTable from './lb-table.vue'
export default {
install:(Vue) => {
Vue.component('lbTable',lbTable);
}
}
\ No newline at end of file
/*
* FileName: lb-column.vue
* Remark: element-column
* Project: lb-element-table
* Author: 任超
* File Created: Tuesday, 19th March 2019 9:58:23 am
* Last Modified: Tuesday, 19th March 2019 10:14:42 am
* Modified By: 任超
*/
<template>
<el-table-column v-bind="$attrs"
v-on="$listeners"
:prop="column.prop"
:label="column.label"
:type="column.type"
:index="column.index"
:column-key="column.columnKey"
:width="column.width"
:min-width="setColumnWidth(column.label)"
:fixed="column.fixed"
:scoped-slot="column.renderHeader"
:sortable="column.sortable || false"
:sort-method="column.sortMethod"
:sort-by="column.sortBy"
:sort-orders="column.sortOrders"
:resizable="column.resizable || true"
:formatter="column.formatter"
:show-overflow-tooltip="column.showOverflowTooltip != null ? column.showOverflowTooltip : true"
:align="column.align!=null ? column.align : 'left'"
:header-align="column.headerAlign || headerAlign || column.align || align || 'left'"
:class-name="column.className"
:label-class-name="column.labelClassName"
:selectable="column.selectable"
:reserve-selection="column.reserveSelection || false"
:filters="column.filters"
:filter-placement="column.filterPlacement"
:filter-multiple="column.filterMultiple"
:filter-method="column.filterMethod"
:filtered-value="column.filteredValue">
<template slot="header"
slot-scope="scope">
<lb-render v-if="column.renderHeader"
:scope="scope"
:render="column.renderHeader">
</lb-render>
<span v-else>{{ scope.column.label }}</span>
</template>
<template slot-scope="scope">
<lb-render :scope="scope"
:render="column.render">
</lb-render>
</template>
<template v-if="column.children">
<lb-column v-for="(col, index) in column.children"
:key="index"
:column="col">
</lb-column>
</template>
</el-table-column>
</template>
<script>
import LbRender from './lb-render'
import forced from './forced.js'
export default {
name: 'LbColumn',
props: {
column: Object,
headerAlign: String,
align: String
},
components: {
LbRender
},
methods: {
setColumnWidth(str) {
let columnWidth = 0;
for (let char of str) {
if ((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z')) {
// 如果是英文字符,为字符分配10个单位宽度,单位宽度可根据字体大小调整
columnWidth += 10
} else if (char >= '\u4e00' && char <= '\u9fa5') {
// 如果是中文字符,为字符分配14个单位宽度,单位宽度可根据字体大小调整
columnWidth += 14
} else {
// 其他种类字符,为字符分配10个单位宽度,单位宽度可根据字体大小调整
columnWidth += 10
}
}
if (columnWidth < 120) {
// 设置最小宽度
columnWidth = 120
}
return columnWidth + 'px'
},
setColumn () {
if (this.column.type) {
this.column.renderHeader = forced[this.column.type].renderHeader
this.column.render = this.column.render || forced[this.column.type].renderCell
}
if (this.column.formatter) {
this.column.render = (h, scope) => {
return <span>{ scope.column.formatter(scope.row, scope.column, scope.row, scope.$index) }</span>
}
}
if (!this.column.render) {
this.column.render = (h, scope) => {
return <span>{ scope.row[scope.column.property] }</span>
}
}
}
},
watch: {
column: {
handler () {
this.setColumn()
},
immediate: true
}
}
}
</script>
/*
* FileName: lb-render.vue
* Remark: 自定义render
* Project: lb-element-table
* Author: 任超
* File Created: Tuesday, 19th March 2019 10:15:30 am
* Last Modified: Tuesday, 19th March 2019 10:15:32 am
* Modified By: 任超
*/
<script>
export default {
name: 'LbRender',
functional: true,
props: {
scope: Object,
render: Function
},
render: (h, ctx) => {
return ctx.props.render ? ctx.props.render(h, ctx.props.scope) : ''
}
}
</script>
/*
* FileName: lb-table.vue
* Remark: element table
* Project: lb-element-table
* Author: 任超
* File Created: Tuesday, 19th March 2019 9:55:27 am
* Last Modified: Tuesday, 19th March 2019 9:55:34 am
* Modified By: 任超
*/
<template>
<div :class="['lb-table', customClass]">
<el-table ref="elTable" :row-class-name="tableRowClassName" :show-header='showHeader'
:header-cell-style="{ background: '#F8FAFA' }" v-bind="$attrs" :height="tableHeight" v-on="$listeners" :data="data"
style="width: 100%" :span-method="this.merge ? this.mergeMethod : this.spanMethod">
<lb-column v-bind="$attrs" v-for="(item, index) in column" :key="index" :column="item">
</lb-column>
</el-table>
<br>
<el-pagination class="lb-table-pagination" v-if="pagination" v-bind="$attrs" v-on="$listeners" background
:page-sizes="[10, 15, 20, 50]" layout="total, sizes, prev, pager, next" @current-change="paginationCurrentChange"
:style="{ 'margin-top': paginationTop, 'text-align': paginationAlign }">
</el-pagination>
</div>
</template>
<script>
import LbColumn from './lb-column'
export default {
props: {
column: Array,
data: Array,
spanMethod: Function,
pagination: {
type: Boolean,
default: true,
},
border: {
type: Boolean,
default: true,
},
showHeader: {
type: Boolean,
default: true,
},
paginationTop: {
type: String,
default: '0',
},
heightNum: {
type: Number,
default: 405,
},
heightNumSetting: {
type: Boolean,
default: false,
},
customClass: {
type: String,
default: '',
},
paginationAlign: {
type: String,
default: 'left',
},
merge: Array,
},
components: {
LbColumn,
},
data () {
return {
tableHeight: '100%',
mergeLine: {},
mergeIndex: {},
}
},
created () {
this.getMergeArr(this.data, this.merge)
this.getHeight()
},
mounted () {
},
computed: {
dataLength () {
return [] || this.data.length
},
},
methods: {
tableRowClassName ({ row, rowIndex }) {
if (rowIndex % 2 === 1) {
return 'interlaced';
}
},
getHeight () {
if (this.heightNumSetting) {
this.tableHeight = this.heightNum + 'px'
} else {
this.tableHeight = window.innerHeight - this.heightNum + 'px'
}
},
changeHeight (heightNum) {
this.tableHeight = heightNum + 'px'
},
clearSelection () {
this.$refs.elTable.clearSelection()
},
toggleRowSelection (row, selected) {
this.$refs.elTable.toggleRowSelection(row, selected)
},
toggleAllSelection () {
this.$refs.elTable.toggleAllSelection()
},
toggleRowExpansion (row, expanded) {
this.$refs.elTable.toggleRowExpansion(row, expanded)
},
setCurrentRow (row) {
this.$refs.elTable.setCurrentRow(row)
},
clearSort () {
this.$refs.elTable.clearSort()
},
clearFilter (columnKey) {
this.$refs.elTable.clearFilter(columnKey)
},
doLayout () {
this.$refs.elTable.doLayout()
},
sort (prop, order) {
this.$refs.elTable.sort(prop, order)
},
paginationCurrentChange (val) {
this.$emit('p-current-change', val)
},
getMergeArr (tableData, merge) {
if (!merge) return
this.mergeLine = {}
this.mergeIndex = {}
merge.forEach((item, k) => {
tableData.forEach((data, i) => {
if (i === 0) {
this.mergeIndex[item] = this.mergeIndex[item] || []
this.mergeIndex[item].push(1)
this.mergeLine[item] = 0
} else {
if (data[item] === tableData[i - 1][item]) {
this.mergeIndex[item][this.mergeLine[item]] += 1
this.mergeIndex[item].push(0)
} else {
this.mergeIndex[item].push(1)
this.mergeLine[item] = i
}
}
})
})
},
mergeMethod ({ row, column, rowIndex, columnIndex }) {
const index = this.merge.indexOf(column.property)
if (index > -1) {
const _row = this.mergeIndex[this.merge[index]][rowIndex]
const _col = _row > 0 ? 1 : 0
return {
rowspan: _row,
colspan: _col,
}
}
},
},
watch: {
merge () {
this.getMergeArr(this.data, this.merge)
},
dataLength () {
this.getMergeArr(this.data, this.merge)
}
},
}
</script>
<style rel="stylesheet/scss" lang="scss" >
.lb-table {
.interlaced {
background: #FCFDFD;
;
border: 1px solid #ECECEE;
}
.el-table {
border: 1px solid #ECECEE;
border-radius: 4px 4px 0 0;
}
.el-table::before {
display: none;
}
.el-table--enable-row-hover .el-table__body tr:hover>td {
background: #FBFCFD !important;
}
}
.el-table th>.cell {
padding-left: 20px;
}
.el-table .cell {
padding-left: 20px;
}
</style>
## 这是对于element-table 进行的二次封装
### 文档地址
<!-- table 已经全局注册不需要每个页面单独注册 -->
[Windows/Mac/Linux 全平台客户端](https://github.liubing.me/lb-element-table/zh/guide/)
import Vue from "vue";
/*
* 使用方法
* 将以下代码复制到一个js文件中,然后在入口文件main.js中import引入即可;
* 给elementUI的dialog上加上 v-dialogDrag 指令就可以实现弹窗的全屏和拉伸了。
* 给dialog设置 :close-on-click-modal="false" , 禁止点击遮罩层关闭弹出层
* 如果是form表单,不要将提交等按钮放置el-form-item,以免在上下拉伸时被隐藏
*/
// v-dialogDrag: 弹窗拖拽+水平方向伸缩
Vue.directive('dialogDrag', {
bind(el, binding, vnode, oldVnode) {
const dialogHeaderEl = el.querySelector('.el-dialog__header');
const dragDom = el.querySelector('.el-dialog');
//dialogHeaderEl.style.cursor = 'move';
dialogHeaderEl.style.cssText += ';cursor:move;'
dragDom.style.cssText += ';top:0px;'
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const sty = (function () {
if (window.document.currentStyle) {
return (dom, attr) => dom.currentStyle[attr];
} else {
return (dom, attr) => getComputedStyle(dom, false)[attr];
}
})()
dialogHeaderEl.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft;
const disY = e.clientY - dialogHeaderEl.offsetTop;
const screenWidth = document.body.clientWidth; // body当前宽度
const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
const dragDomheight = dragDom.offsetHeight; // 对话框高度
const minDragDomLeft = dragDom.offsetLeft;
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
const minDragDomTop = dragDom.offsetTop;
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
// 获取到的值带px 正则匹配替换
let styL = sty(dragDom, 'left');
let styT = sty(dragDom, 'top');
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100);
styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100);
} else {
styL = +styL.replace(/\px/g, '');
styT = +styT.replace(/\px/g, '');
};
document.onmousemove = function (e) {
// 通过事件委托,计算移动的距离
let left = e.clientX - disX;
let top = e.clientY - disY;
// 边界处理
if (-(left) > minDragDomLeft) {
left = -(minDragDomLeft);
} else if (left > maxDragDomLeft) {
left = maxDragDomLeft;
}
if (-(top) > minDragDomTop) {
top = -(minDragDomTop);
} else if (top > maxDragDomTop) {
top = maxDragDomTop;
}
// 移动当前元素
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
}
}
})
\ No newline at end of file
import onlyNumber from './onlyNumber'
const install = Vue => {
Vue.directive('onlyNumber', onlyNumber)
}
/*
Vue.use( plugin )
安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。
如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。
该方法需要在调用 new Vue() 之前被调用。
当 install 方法被同一个插件多次调用,插件将只会被安装一次。
*/
if (window.Vue) {
window['onlyNumber'] = onlyNumber
Vue.use(install); // eslint-disable-line
}
onlyNumber.install = install
export default onlyNumber
\ No newline at end of file
export default {
inserted (el, vDir, vNode) {
// vDir.value 有指令的参数
let content;
//按键按下=>只允许输入 数字/小数点
el.addEventListener("keypress", event => {
let e = event || window.event;
let inputKey = String.fromCharCode(typeof e.charCode === 'number' ? e.charCode : e.keyCode);
let re = /\d|\./;
content = e.target.value;
//定义方法,阻止输入
function preventInput () {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
}
if (!re.test(inputKey) && !e.ctrlKey) {
preventInput();
} else if (content.indexOf(".") > 0 && inputKey == ".") {
//已有小数点,再次输入小数点
preventInput();
}
});
//按键弹起=>并限制最大最小
el.addEventListener("keyup", event => {
let e = event || window.event;
content = parseFloat(e.target.value);
let arg_max = "";
let arg_min = "";
if (vDir.value) {
arg_max = parseFloat(vDir.value.max);
arg_min = parseFloat(vDir.value.min);
}
if (arg_max && content > arg_max) {
e.target.value = arg_max;
content = arg_max;
}
if (arg_min && content < arg_min) {
e.target.value = arg_min;
content = arg_min;
}
if (!content) {
content = '';
}
});
//失去焦点=>保留指定位小数
el.addEventListener("keydown", event => {//此处会在 el-input 的 @change 后执行
let e = event || window.event;
let key = e.key
if (vDir.value.precision == 0) {
// 不允许输入'e'和'.'
if (key === 'e' || key === '.') {
e.returnValue = false
return false
}
}else {
if (e.target.value != '') {
let arg_precision = 0;//默认保留至整数
if (vDir.value.precision) {
arg_precision = parseFloat(vDir.value.precision);
}
let reg = new RegExp(`^\\d*(\\.?\\d{0,${arg_precision-1}})`, 'g')
e.target.value = (e.target.value.match(reg)[0]) || null
}
}
// content = parseFloat(e.target.value);
// if (!content) {
// content = 0.00;
// }
// if (e.target.value != '') {
// let arg_precision = 0;//默认保留至整数
// if (vDir.value.precision) {
// arg_precision = parseFloat(vDir.value.precision);
// }
// e.target.value = content.toFixed(arg_precision);
// }
// -- callback写法1
// vNode.data.model.callback = ()=>{
// e.target.value = content.toFixed(arg_precision)
// }
// vNode.data.model.callback();
// -- callback 写法2
// vNode.data.model.callback(
// e.target.value = content.toFixed(arg_precision)
// )
})
}
}
\ No newline at end of file
import store from '@/store'
import dictionaries from '@/api/dictionaries'
// 证件种类全局过滤器
export function cardTypeFilter (value) {
var name
if (store.getters.cardTypeOption.length == 0) {
dictionaries.getDdicByMC('证件种类').then((res) => {
store.dispatch('dictionaries/getCardType', res.result)
})
}
store.getters.cardTypeOption.map((item) => {
if (item.id == value) {
name = item.name
}
});
if (name) {
return name
} else {
return '暂无'
}
}
// 全部字典
export function stsInfo (value) {
if (store.getters.stsInfo.length == 0) {
dictionaries.getDictionary().then(res => {
store.dispatch('dictionariesAll/getDicData', res.result)
})
}
}
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629181827644" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5380" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M170.666667 156.444444h682.666666a42.666667 42.666667 0 0 1 0 85.333334H170.666667a42.666667 42.666667 0 1 1 0-85.333334zM448.056889 440.888889H853.333333a42.666667 42.666667 0 0 1 0 85.333333H448.056889a42.666667 42.666667 0 0 1 0-85.333333zM170.666667 725.333333h682.666666a42.666667 42.666667 0 1 1 0 85.333334H170.666667a42.666667 42.666667 0 1 1 0-85.333334z" p-id="5381" fill="#686666"></path><path d="M290.304 493.738667L156.501333 560.64a11.377778 11.377778 0 0 1-16.497777-10.183111V416.654222a11.377778 11.377778 0 0 1 16.497777-10.24l133.802667 66.958222a11.377778 11.377778 0 0 1 0 20.366223z" p-id="5382" fill="#686666"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1628839541389" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2080" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M408 442h480a8 8 0 0 0 8-8v-56a8 8 0 0 0-8-8H408a8 8 0 0 0-8 8v56a8 8 0 0 0 8 8z m-8 204a8 8 0 0 0 8 8h480a8 8 0 0 0 8-8v-56a8 8 0 0 0-8-8H408a8 8 0 0 0-8 8v56z m504-486H120a8 8 0 0 0-8 8v56a8 8 0 0 0 8 8h784a8 8 0 0 0 8-8v-56a8 8 0 0 0-8-8z m0 632H120a8 8 0 0 0-8 8v56a8 8 0 0 0 8 8h784a8 8 0 0 0 8-8v-56a8 8 0 0 0-8-8zM142.4 642.1L298.7 519a8.8 8.8 0 0 0 0-13.9L142.4 381.9a8.9 8.9 0 0 0-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" p-id="2081" fill="#686666"></path></svg>
\ No newline at end of file
# replace default config
# multipass: true
# full: true
plugins:
# - name
#
# or:
# - name: false
# - name: true
#
# or:
# - name:
# param1: 1
# param2: 2
- removeAttrs:
attrs:
- 'fill'
- 'fill-rule'
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</transition>
</section>
</template>
<script>
export default {
name: 'AppMain',
computed: {
cachedViews () {
return this.$store.state.tagsView.cachedViews
},
key () {
return this.$route.path
},
},
}
</script>
<style lang="scss" scoped>
.hasTagsView {
.app-main {
height: calc(100% - 88px);
overflow-x: auto;
}
}
</style>
\ No newline at end of file
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container"
@toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
<div class="right-menu">
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click" @command="handleCommand">
<div class="avatar-wrapper">
<span style="padding-right:10px">{{ name }}</span>
<img :src="avatar + '?imageView2/1/w/80/h/80'" class="user-avatar" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="a">个人中心</el-dropdown-item>
<el-dropdown-item command="f">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
import { getSession } from '@/utils/session'
export default {
components: {
Breadcrumb,
Hamburger
},
created () {
this.init();
},
computed: {
...mapGetters(['sidebar', 'avatar', 'name'])
},
methods: {
init () {
let userInfo = getSession('userInfo');
if (userInfo && userInfo.userValid === 0) {
this.userValid = 0;
}
},
toggleSideBar () {
this.$store.dispatch('app/toggleSideBar')
},
searchMessageCenter () {
this.$router.push({ name: 'messagecenter' })
},
handleCommand (command) {
if (command == 'a') {
//个人中心
this.$router.push({ name: 'personal' })
} else if (command == 'f') {
// 退出
this.$store.dispatch('user/logout')
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
window.sessionStorage.clear()
}
}
}
}
</script>
<style lang="scss" scoped>
.el-dropdown-menu {
padding: 0 !important;
border: 1px solid #EBEEF5;
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.12);
border-radius: 4px 0 0 4px 4px;
.el-dropdown-menu__item {
text-align: center;
margin-top: 0 !important;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #4A4A4A;
width: 140px;
height: 36px;
line-height: 36px;
}
.el-dropdown-menu__item:nth-child(6) {
border-top: 1px solid #EBEEF5;
}
.popper__arrow {
top: -11px !important;
left: 110px !important;
transform: rotate(0deg) scale(2);
}
.el-dropdown-menu__item:not(.is-disabled):hover,
.el-dropdown-menu__item:focus {
background: #F6F7F9;
color: #4A4A4A;
}
}
.navbar {
height: 40px;
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 0px rgba(0, 21, 41, 0.08);
.hamburger-container {
line-height: 43px;
height: 100%;
float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.breadcrumb-container {
float: left;
}
.right-menu {
float: right;
height: 100%;
line-height: 50px;
.organization-item {
margin-right: 40px;
margin-top: -40px !important;
}
.item {
margin-right: 40px;
margin-top: -20px;
line-height: 18.4px;
cursor: pointer;
position: relative;
.item-box {
position: absolute;
top: -5px;
left: 3px;
width: 100%;
min-width: 25px;
height: 25px;
cursor: pointer;
z-index: 100;
}
}
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
}
.avatar-container {
margin-right: 20px;
.avatar-wrapper {
position: relative;
display: flex;
height: 40px;
align-items: center;
.user-avatar {
cursor: pointer;
width: 35px;
height: 35px;
border-radius: 50%;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -15px;
top: 17px;
font-size: 12px;
}
}
}
}
/*2.隐藏滚动条,太丑了*/
.el-drawer__container ::-webkit-scrollbar {
display: none;
}
.el-form-item--small .el-form-item__label {
width: 80px !important;
}
}
</style>
export default {
computed: {
device() {
return this.$store.state.app.device
}
},
mounted() {
// In order to fix the click on menu on the ios device will trigger the mouseleave bug
// https://github.com/PanJiaChen/vue-element-admin/issues/1135
this.fixBugIniOS()
},
methods: {
fixBugIniOS() {
const $subMenu = this.$refs.subMenu
if ($subMenu) {
const handleMouseleave = $subMenu.handleMouseleave
$subMenu.handleMouseleave = (e) => {
if (this.device === 'mobile') {
return
}
handleMouseleave(e)
}
}
}
}
}
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
render(h, context) {
const { icon, title } = context.props
const vnodes = []
if (icon) {
if (icon.includes('el-icon')) {
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
} else {
vnodes.push(<svg-icon icon-class={icon}/>)
}
}
if (title) {
vnodes.push(<span slot='title'>{(title)}</span>)
}
return vnodes
}
}
</script>
<style scoped>
.sub-el-icon {
color: currentColor;
width: 1em;
height: 1em;
}
</style>
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '@/utils/validate'
export default {
props: {
to: {
type: String,
required: true
}
},
computed: {
isExternal() {
return isExternal(this.to)
},
type() {
if (this.isExternal) {
return 'a'
}
return 'router-link'
}
},
methods: {
linkProps(to) {
if (this.isExternal) {
return {
href: to,
target: '_blank',
rel: 'noopener'
}
}
return {
to: to
}
}
}
}
</script>
<template>
<div class="sidebar-logo-container"
:class="{'collapse':collapse}">
<transition name="sidebarLogoFade">
<router-link v-if="collapse"
key="collapse"
class="sidebar-logo-link"
to="/">
<img v-if="logo"
:src="logo"
class="sidebar-logo">
<h1 v-else
class="sidebar-title">
{{ title }}
</h1>
</router-link>
<router-link v-else
key="expand"
class="sidebar-logo-link"
to="/">
<img v-if="logo"
:src="logo"
class="sidebar-logo">
<h1 class="sidebar-title">
厦门市房屋租赁<br>管理服务系统
</h1>
</router-link>
</transition>
</div>
</template>
<script>
import logonImg from '@/assets/global_images/logo.png'
export default {
name: 'SidebarLogo',
props: {
collapse: {
type: Boolean,
required: true
}
},
data() {
return {
title: '厦门市房屋数据服务平台',
logo: logonImg
}
}
}
</script>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
}
.sidebar-logo-container {
position: relative;
width: 100%;
text-align: center;
overflow: hidden;
height: 150px;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo {
width: 41px;
height: 39px;
vertical-align: middle;
// margin-left: 47px;
// margin-right: 48px;
margin-top: 22px;
}
& .sidebar-title {
// display: inline-block;
margin: 0;
margin-top: 10px;
margin-bottom: 20px;
color: #fff;
font-weight: 600;
line-height: 25px;
font-size: 16px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
width: 32.8px;
height: 31.2px;
}
}
}
</style>
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" class="menu-icon" />
</el-menu-item>
</app-link>
</template>
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-submenu>
</div>
</template>
<script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
export default {
name: 'SidebarItem',
components: { Item, AppLink },
mixins: [FixiOSBug],
props: {
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
},
data() {
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
// TODO: refactor with render function
this.onlyOneChild = null
return {}
},
methods: {
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
return true
}
return false
},
resolvePath(routePath) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(this.basePath)) {
return this.basePath
}
return path.resolve(this.basePath, routePath)
}
}
}
</script>
\ No newline at end of file
<template>
<div :class="{'has-logo':showLogo}">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu :default-active="activeMenu" :collapse="isCollapse"
:background-color="variables.menuBg" :text-color="variables.menuText" :unique-opened="true"
:active-text-color="variables.menuActiveText" :collapse-transition="false" mode="vertical">
<!-- 权限菜单 -->
<sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
<!-- 菜单全部展示 -->
<!-- <sidebar-item v-for="route in asyncRoutes" :key="route.path" :item="route"
:base-path="route.path" /> -->
</el-menu>
</el-scrollbar>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'
import { asyncRoutes } from '@/router'
export default {
components: { SidebarItem, Logo },
computed: {
...mapGetters(['permission_routes', 'sidebar']),
activeMenu() {
const route = this.$route
const { meta, path } = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
showLogo() {
return this.$store.state.settings.sidebarLogo
},
variables() {
return variables
},
isCollapse() {
return !this.sidebar.opened
},
asyncRoutes() {
return asyncRoutes
},
},
}
</script>
<template>
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
<slot />
</el-scrollbar>
</template>
<script>
const tagAndTagSpacing = 4 // tagAndTagSpacing
export default {
name: 'ScrollPane',
data() {
return {
left: 0
}
},
computed: {
scrollWrapper() {
return this.$refs.scrollContainer.$refs.wrap
}
},
mounted() {
this.scrollWrapper.addEventListener('scroll', this.emitScroll, true)
},
beforeDestroy() {
this.scrollWrapper.removeEventListener('scroll', this.emitScroll)
},
methods: {
handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 40
const $scrollWrapper = this.scrollWrapper
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
},
emitScroll() {
this.$emit('scroll')
},
moveToTarget(currentTag) {
const $container = this.$refs.scrollContainer.$el
const $containerWidth = $container.offsetWidth
const $scrollWrapper = this.scrollWrapper
const tagList = this.$parent.$refs.tag
let firstTag = null
let lastTag = null
// find first tag and last tag
if (tagList.length > 0) {
firstTag = tagList[0]
lastTag = tagList[tagList.length - 1]
}
if (firstTag === currentTag) {
$scrollWrapper.scrollLeft = 0
} else if (lastTag === currentTag) {
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
} else {
// find preTag and nextTag
const currentIndex = tagList.findIndex(item => item === currentTag)
const prevTag = tagList[currentIndex - 1]
const nextTag = tagList[currentIndex + 1]
// the tag's offsetLeft after of nextTag
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
// the tag's offsetLeft before of prevTag
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
}
}
}
}
}
</script>
<style lang="scss" scoped>
.scroll-container {
white-space: nowrap;
position: relative;
overflow: hidden;
width: 100%;
::v-deep {
.el-scrollbar__bar {
bottom: 0px;
width: 0;
}
.el-scrollbar__wrap {
height: 48px;
line-height: 48px;
}
}
}
</style>
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
<p class="pane-mask-left"></p>
<router-link v-for="tag in visitedViews" ref="tag" :key="tag.path" :class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" tag="span" class="tags-view-item"
@click.middle.native="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent.native="openMenu(tag, $event)">
{{ tag.title }}
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
<p class="pane-mask-right"></p>
</scroll-pane>
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
<li @click="closeOthersTags">关闭其他</li>
<li @click="closeAllTags(selectedTag)">关闭全部</li>
</ul>
</div>
</template>
<script>
import ScrollPane from './ScrollPane'
import path from 'path'
export default {
components: { ScrollPane },
data () {
return {
visible: false,
top: 0,
left: 0,
selectedTag: {},
affixTags: []
}
},
computed: {
visitedViews () {
return this.$store.state.tagsView.visitedViews
},
routes () {
return this.$store.state.permission.routes
}
},
watch: {
$route () {
this.addTags()
this.moveToCurrentTag()
},
visible (value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
} else {
document.body.removeEventListener('click', this.closeMenu)
}
}
},
mounted () {
this.initTags()
this.addTags()
},
methods: {
isActive (route) {
return route.path === this.$route.path
},
isAffix (tag) {
return tag.meta && tag.meta.affix
},
filterAffixTags (routes, basePath = '/') {
let tags = []
routes.forEach(route => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
}
if (route.children) {
const tempTags = this.filterAffixTags(route.children, route.path)
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags]
}
}
})
return tags
},
initTags () {
const affixTags = (this.affixTags = this.filterAffixTags(this.routes))
for (const tag of affixTags) {
// Must have tag name
if (tag.name) {
this.$store.dispatch('tagsView/addVisitedView', tag)
}
}
},
addTags () {
const { name } = this.$route
if (name) {
this.$store.dispatch('tagsView/addView', this.$route)
console.log(this.visitedViews, name);
}
return false
},
moveToCurrentTag () {
const tags = this.$refs.tag
this.$nextTick(() => {
for (const tag of tags) {
if (tag.to.path === this.$route.path) {
this.$refs.scrollPane.moveToTarget(tag)
// when query is different then update
if (tag.to.fullPath !== this.$route.fullPath) {
this.$store.dispatch('tagsView/updateVisitedView', this.$route)
}
break
}
}
})
},
refreshSelectedTag (view) {
this.$store.dispatch('tagsView/delCachedView', view).then(() => {
const { fullPath } = view
this.$nextTick(() => {
this.$router.replace({
path: '/redirect' + fullPath
})
})
})
},
closeSelectedTag (view) {
this.$store
.dispatch('tagsView/delView', view)
.then(({ visitedViews }) => {
if (this.isActive(view)) {
this.toLastView(visitedViews, view)
}
})
},
closeOthersTags () {
this.$router.push(this.selectedTag)
this.$store
.dispatch('tagsView/delOthersViews', this.selectedTag)
.then(() => {
this.moveToCurrentTag()
})
},
closeAllTags (view) {
this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
if (this.affixTags.some(tag => tag.path === view.path)) {
return
}
this.toLastView(visitedViews, view)
})
},
toLastView (visitedViews, view) {
const latestView = visitedViews.slice(-1)[0]
if (latestView) {
this.$router.push(latestView.fullPath)
} else {
// now the default is to redirect to the home page if there is no tags-view,
// you can adjust it according to your needs.
if (view.name === 'Dashboard') {
// to reload home page
this.$router.replace({ path: '/redirect' + view.fullPath })
} else {
this.$router.push('/')
}
}
},
openMenu (tag, e) {
const menuMinWidth = 95
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 210 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
this.top = e.clientY
this.visible = true
this.selectedTag = tag
},
closeMenu () {
this.visible = false
},
handleScroll () {
this.closeMenu()
}
}
}
</script>
<style lang="scss" scoped>
.tags-view-container {
height: 48px;
line-height: 48px;
width: 100%;
background: #fff;
border-bottom: 1px solid #E4EBF4;
box-shadow: 0 1px 3px 0 rgba(147, 173, 209, 0.12);
.tags-view-wrapper {
.el-scrollbar__view {
position: relative;
.pane-mask-left,
.pane-mask-right {
position: absolute;
top: 8px;
height: 33px;
z-index: 1;
}
.pane-mask-left {
width: 15px;
left: 0;
background-image: linear-gradient(270deg, rgba(255, 255, 255, 0.00) 20%, #FFFFFF 33%);
}
.pane-mask-right {
width: 30px;
right: 0;
background-image: linear-gradient(90deg, rgba(255, 255, 255, 0.00) 5%, #FFFFFF 30%);
}
}
.tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
height: 31px;
line-height: 29px;
border: 1px solid #e4ebf4;
color: #686666;
background: #fff;
padding: 0 10px 0 10px;
font-size: 16px;
margin-top: 5px;
border-radius: 4px;
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&.active {
border-color: #0f93f6;
color: #4a4a4a;
// &::before {
// content: '';
// background: #0F93F6;
// display: inline-block;
// width: 8px;
// height: 8px;
// border-radius: 50%;
// position: relative;
// margin-right: 2px;
// }
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
padding: 5px 0;
font-size: 12px;
font-weight: 400;
color: #333;
border: 1px solid #EBEEF5;
box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.12);
border-radius: 4px 0 0 4px 4px;
li {
height: 29px;
line-height: 29px;
margin: 0;
padding: 0px 16px;
cursor: pointer;
&:hover {
background: #F6F7F9;
}
}
}
}
</style>
<style lang="scss">
//reset element css of el-icon-close
.tags-view-wrapper {
.tags-view-item {
text-align: justify;
margin-right: 8px;
.el-icon-close {
height: 16px;
width: 16px;
position: relative;
border-radius: 50%;
text-align: center;
line-height: 16px;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transform-origin: 100% 50%;
color: #686666;
font-size: 14px;
&:before {
transform: scale(0.8);
display: inline-block;
}
&:hover {
background-color: #b4bccc;
color: #fff;
}
}
}
}
</style>
export { default as AppMain } from './AppMain'
export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar/index.vue'
export { default as TagsView } from './TagsView/index.vue'
<template>
<div :class="classObj" class="app-wrapper">
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div :class="{ hasTagsView: needTagsView }" class="main-container">
<div :class="{ 'fixed-header': fixedHeader }">
<navbar />
<tags-view v-if="needTagsView" />
</div>
<app-main />
</div>
</div>
</template>
<script>
// import RightPanel from '@/components/RightPanel'
import { AppMain, Navbar, Sidebar, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
export default {
name: 'Layout',
components: {
AppMain,
Navbar,
Sidebar,
TagsView
},
mixins: [ResizeMixin],
computed: {
...mapState({
sidebar: state => state.app.sidebar,
device: state => state.app.device,
needTagsView: state => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader
}),
classObj () {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
}
},
methods: {
handleClickOutside () {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/styles/mixin.scss";
@import "~@/styles/variables.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header {
width: 100%;
transition: width 0.28s;
}
.el-dropdown-menu--small {
padding: 0;
width: 5px;
}
</style>
import store from '@/store'
const { body } = document
const WIDTH = 992 // refer to Bootstrap's responsive design
export default {
watch: {
$route(route) {
if (this.device === 'mobile' && this.sidebar.opened) {
store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
},
beforeMount() {
window.addEventListener('resize', this.$_resizeHandler)
},
beforeDestroy() {
window.removeEventListener('resize', this.$_resizeHandler)
},
mounted() {
const isMobile = this.$_isMobile()
if (isMobile) {
store.dispatch('app/toggleDevice', 'mobile')
store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_isMobile() {
const rect = body.getBoundingClientRect()
return rect.width - 1 < WIDTH
},
$_resizeHandler() {
if (!document.hidden) {
const isMobile = this.$_isMobile()
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
if (isMobile) {
store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
}
}
}
}
import 'babel-polyfill'
import Vue from 'vue'
import App from './App'
import 'normalize.css/normalize.css' // a modern alternative to CSS resets
import './directive/dragDialog';
import Element from 'element-ui'
import './styles/element-variables.scss'
import '@/styles/index.scss' // global css
import onlyNumber from '@/directive/el-input'; //添加此行=>自定义全局指令
import lbTable from './components/lb-table/index';
import store from './store'
import router from './router'
import _ from 'lodash'
import './icons' // icon
import './permission' // permission control
import * as filters from './filters' // global filters
Vue.use(lbTable);
Vue.use(onlyNumber);
Vue.use(Element, {
size: 'small' // set element-ui default size
})
Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])
})
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
import router from './router'
import store from './store'
import { getMenuInfo } from '@/api/user'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import getPageTitle from '@/utils/get-page-title'
import Cookies from 'js-cookie'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
router.beforeEach(async (to, from, next) => {
NProgress.start()
document.title = getPageTitle(to.meta.title)
const hasInfo = Cookies.get("userInfo")
if (hasInfo) {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
let hasAddRoute = store.state.permission.addRoutes
if (hasAddRoute) {
next()
} else {
const { result: getMenuData } = await getMenuInfo()
const accessRoutes = await store.dispatch('permission/generateRoutes', getMenuData)
router.addRoutes(accessRoutes)
// 解决页面刷新报404问题,过滤登录页重复访问
const routeTo = Cookies.get('routerTo')
if (routeTo.indexOf('login') > -1) {
// 解决登录成功进入404界面
next('/')
} else {
next(routeTo)
}
}
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(to => {
// 解决刷新页面报404问题
sessionStorage.setItem('routerTo', to.fullPath)
Cookies.set("routerTo", to.fullPath)
NProgress.done()
})
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
/* Router Modules */
// import componentsRouter from './modules/components'
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index')
}
]
},
{
path: '/login',
component: () => import('@/views/login/login'),
hidden: true
},
{
path: '/auth-redirect',
component: () => import('@/views/login/auth-redirect'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error-page/404'),
hidden: true
},
{
path: '/401',
component: () => import('@/views/error-page/401'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/home',
hidden: true,
children: [
{
path: 'home',
component: () => import('@/views/home/index'),
name: 'Dashboard',
meta: { title: '工作台', icon: 'dashboard', affix: true }
}
]
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
/**
* asyncRoutes
* the routes that need to be dynamically loaded based on user roles
*/
export const asyncRoutes = [
{
path: '/housedev',
id: '3',
parentId: null,
component: Layout,
meta: { title: '房源开发', icon: 'fykf' },
redirect: '/housedev/yzfyxsgl',
alwaysShow: true,
name: 'housedev',
children: [
{
path: 'yzfyxsgl',
id: '4',
parentId: '3',
component: () => import('@/views/housedev/yzfyxsgl/index.vue'),
name: 'yzfyxsgl',
meta: { title: '业主房源线索' }
}
]
}
]
const createRouter = () =>
new Router({
scrollBehavior: () => ({ y: 0 }),
routes: [...constantRoutes]
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter () {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
module.exports = {
title: '管理系统',
/**
* @type {boolean} true | false
* @description Whether show the settings right-panel
*/
showSettings: true,
/**
* @type {boolean} true | false
* @description Whether need tagsView
*/
tagsView: true,
/**
* @type {boolean} true | false
* @description Whether fix the header
*/
fixedHeader: true,
/**
* @type {boolean} true | false
* @description Whether show the logo in sidebar
*/
sidebarLogo: true,
/**
* @type {string | array} 'production' | ['production', 'development']
* @description Need show err logs component.
* The default is only used in the production env
* If you want to also use it in dev, you can pass ['production', 'development']
*/
errorLog: 'production'
}
const getters = {
sidebar: state => state.app.sidebar,
size: state => state.app.size,
visitedViews: state => state.tagsView.visitedViews,
cachedViews: state => state.tagsView.cachedViews,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
introduction: state => state.user.introduction,
permission_routes: state => state.permission.routes,
addRoutes: state => state.permission.addRoutes,
errorLogs: state => state.errorLog.logs,
fdglSaveInfo: state => state.organizationmanage.fdgl,
cardTypeOption: state => state.dictionaries.cardTypeOption,
xingzuoTypeOption: state => state.dictionaries.xingzuoTypeOption,
sysInfo: state => state.dictionariesAll.sysInfo,
centralizationData: state => state.housemap.centralizationData,
distributedData: state => state.housemap.distributedData,
}
export default getters
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
Vue.use(Vuex)
// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context('./modules', true, /\.js$/)
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// set './app.js' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
const store = new Vuex.Store({
modules,
getters
})
export default store
import Cookies from 'js-cookie'
const state = {
sidebar: {
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
withoutAnimation: false
},
device: 'desktop',
// size: Cookies.get('size') || 'default'
size: 'small'
}
const mutations = {
TOGGLE_SIDEBAR: state => {
state.sidebar.opened = !state.sidebar.opened
state.sidebar.withoutAnimation = false
if (state.sidebar.opened) {
Cookies.set('sidebarStatus', 1)
} else {
Cookies.set('sidebarStatus', 0)
}
},
CLOSE_SIDEBAR: (state, withoutAnimation) => {
Cookies.set('sidebarStatus', 0)
state.sidebar.opened = false
state.sidebar.withoutAnimation = withoutAnimation
},
TOGGLE_DEVICE: (state, device) => {
state.device = device
},
SET_SIZE: (state, size) => {
state.size = size
Cookies.set('size', size)
}
}
const actions = {
toggleSideBar ({ commit }) {
commit('TOGGLE_SIDEBAR')
},
closeSideBar ({ commit }, { withoutAnimation }) {
commit('CLOSE_SIDEBAR', withoutAnimation)
},
toggleDevice ({ commit }, device) {
commit('TOGGLE_DEVICE', device)
},
setSize ({ commit }, size) {
commit('SET_SIZE', size)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
import { asyncRoutes, constantRoutes, resetRouter } from '@/router'
const state = {
routes: [],
addRoutes: false,
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = true
state.routes = constantRoutes.concat(routes)
},
RESET_ROUTE: (state) => {
state.addRoutes = false
}
}
const actions = {
// 添加全部菜单
generateRoutes ({ commit }, getMenuInfo) {
return new Promise(resolve => {
// 将路由树数据转成数组结构
let arr1 = []
dfs(_.cloneDeep(asyncRoutes), node => arr1.push(node))
_.each(arr1, i => {
i.parentId = i.parentId ? i.parentId : null
})
//lodash intersectionBy方法取交集,并以参数1的数据返回
let permission_arr = _.intersectionBy(arr1, getMenuInfo, 'id')
// 将权限菜单数组转成路由树数据结构
let permission_tree = array2Tree(permission_arr, null)
commit('SET_ROUTES', permission_tree)
resolve(permission_tree)
})
},
// 重置路由
resetRoutes ({ commit }) {
commit('RESET_ROUTE')
}
}
// 树转数组
function dfs (root, fVisit) {
let stack = Array.isArray(root) ? [...root] : [root];
while (stack.length) {
let node = stack.pop();
fVisit && fVisit(node);
let children = node.children;
if (children && children.length) {
for (let i = children.length - 1; i >= 0; i--) stack.push(children[i]);
}
}
}
// 数组转树
//需要插入父节点id,pid为null或'',就是找root节点,然后root节点再去找自己的子节点
function array2Tree (data, pid) {
let res = [];
data.forEach(item => {
if (item.parentId === pid) {
let itemChildren = array2Tree(data, item.id);
if (itemChildren.length) item.children = itemChildren;
res.push(item);
}
});
// 菜单数据反转,保持一致
res.reverse()
_.each(res, c => {
if (c.children && c.children.length > 0) {
c.children.reverse()
}
})
return res;
}
export default {
namespaced: true,
state,
mutations,
actions
}
import variables from '@/styles/element-variables.scss'
import defaultSettings from '@/settings'
const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings
const state = {
theme: variables.theme,
showSettings: showSettings,
tagsView: tagsView,
fixedHeader: fixedHeader,
sidebarLogo: sidebarLogo
}
const mutations = {
CHANGE_SETTING: (state, { key, value }) => {
if (state.hasOwnProperty(key)) {
state[key] = value
}
}
}
const actions = {
changeSetting ({ commit }, data) {
commit('CHANGE_SETTING', data)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
const state = {
visitedViews: [],
cachedViews: ['leaseDetails']
}
const mutations = {
ADD_VISITED_VIEW: (state, view) => {
if (state.visitedViews.some(v => v.path === view.path)) return
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name'
})
)
},
ADD_CACHED_VIEW: (state, view) => {
if (state.cachedViews.includes(view.name)) return
if (!view.meta.noCache) {
state.cachedViews.push(view.name)
}
},
DEL_VISITED_VIEW: (state, view) => {
for (const [i, v] of state.visitedViews.entries()) {
if (v.path === view.path) {
state.visitedViews.splice(i, 1)
break
}
}
},
DEL_CACHED_VIEW: (state, view) => {
const index = state.cachedViews.indexOf(view.name)
index > -1 && state.cachedViews.splice(index, 1)
},
DEL_OTHERS_VISITED_VIEWS: (state, view) => {
state.visitedViews = state.visitedViews.filter(v => {
return v.meta.affix || v.path === view.path
})
},
DEL_OTHERS_CACHED_VIEWS: (state, view) => {
const index = state.cachedViews.indexOf(view.name)
if (index > -1) {
state.cachedViews = state.cachedViews.slice(index, index + 1)
} else {
// if index = -1, there is no cached tags
state.cachedViews = []
}
},
DEL_ALL_VISITED_VIEWS: state => {
// keep affix tags
const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
state.visitedViews = affixTags
},
DEL_ALL_CACHED_VIEWS: state => {
state.cachedViews = []
},
UPDATE_VISITED_VIEW: (state, view) => {
for (let v of state.visitedViews) {
if (v.path === view.path) {
v = Object.assign(v, view)
break
}
}
}
}
const actions = {
addView ({ dispatch }, view) {
dispatch('addVisitedView', view)
dispatch('addCachedView', view)
},
addVisitedView ({ commit }, view) {
commit('ADD_VISITED_VIEW', view)
},
addCachedView ({ commit }, view) {
commit('ADD_CACHED_VIEW', view)
},
delView ({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delVisitedView', view)
dispatch('delCachedView', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delVisitedView ({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_VISITED_VIEW', view)
resolve([...state.visitedViews])
})
},
delCachedView ({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_CACHED_VIEW', view)
resolve([...state.cachedViews])
})
},
delOthersViews ({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delOthersVisitedViews', view)
dispatch('delOthersCachedViews', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delOthersVisitedViews ({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_OTHERS_VISITED_VIEWS', view)
resolve([...state.visitedViews])
})
},
delOthersCachedViews ({ commit, state }, view) {
return new Promise(resolve => {
commit('DEL_OTHERS_CACHED_VIEWS', view)
resolve([...state.cachedViews])
})
},
delAllViews ({ dispatch, state }, view) {
return new Promise(resolve => {
dispatch('delAllVisitedViews', view)
dispatch('delAllCachedViews', view)
resolve({
visitedViews: [...state.visitedViews],
cachedViews: [...state.cachedViews]
})
})
},
delAllVisitedViews ({ commit, state }) {
return new Promise(resolve => {
commit('DEL_ALL_VISITED_VIEWS')
resolve([...state.visitedViews])
})
},
delAllCachedViews ({ commit, state }) {
return new Promise(resolve => {
commit('DEL_ALL_CACHED_VIEWS')
resolve([...state.cachedViews])
})
},
updateVisitedView ({ commit }, view) {
commit('UPDATE_VISITED_VIEW', view)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
import { login } from '@/api/user'
import store from '../index'
import router, { resetRouter } from '@/router'
import { Message } from "element-ui";
import Cookies from 'js-cookie';
const state = {
name: Cookies.get('username'),
avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
introduction: '',
userInfo: null
}
const mutations = {
SET_INTRODUCTION: (state, introduction) => {
state.introduction = introduction
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_USERINFO: (state, userInfo) => {
state.userInfo = userInfo
}
}
const actions = {
// user login
login ({ commit }, userInfo) {
const { username, password, code, num, location } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password, code: code, location: location }).then(response => {
const { result, code, msg } = response
if (code === 200) {
Cookies.set('token', result.token)
commit('SET_USERINFO', result.userInfo)
Cookies.set("userInfo", result.userInfo.id, { expires: 720 })
Cookies.set("username", result.userInfo.username, { expires: 720 })
commit('SET_NAME', result.userInfo.username)
} else {
Message.error(msg);
}
resolve(response)
}).catch(error => {
reject(error)
})
})
},
// user logout
logout ({ commit, state, dispatch }) {
return new Promise((resolve, reject) => {
// logout(state.token).then(() => {
commit('SET_USERINFO', null)
Cookies.remove("userInfo")
Cookies.remove("routerTo")
Cookies.remove('token')
resetRouter()
store.dispatch('permission/resetRoutes')
// reset visited views and cached views
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
dispatch('tagsView/delAllViews', null, { root: true })
resolve()
})
},
// remove token
resetToken ({ commit }) {
return new Promise(resolve => {
commit('SET_USERINFO', null)
Cookies.remove("userInfo")
resolve()
})
},
}
export default {
namespaced: true,
state,
mutations,
actions
}
@import './variables.scss';
@mixin colorBtn($color) {
background: $color;
color:#fff;
&:hover {
color: #fff;
background-color: $color;
&:before,
&:after {
background: $color;
}
}
}
.blue-btn {
@include colorBtn($blue)
}
.light-blue-btn {
@include colorBtn($light-blue)
}
.red-btn {
@include colorBtn($red)
}
.pink-btn {
@include colorBtn($pink)
}
.green-btn {
@include colorBtn($green)
}
.tiffany-btn {
@include colorBtn($tiffany)
}
.yellow-btn {
@include colorBtn($yellow)
}
.pan-btn {
font-size: 14px;
color: #fff;
padding: 14px 36px;
border-radius: 8px;
border: none;
outline: none;
transition: 600ms ease all;
position: relative;
display: inline-block;
&:hover {
background: #fff;
&:before,
&:after {
width: 100%;
transition: 600ms ease all;
}
}
&:before,
&:after {
content: '';
position: absolute;
top: 0;
right: 0;
height: 2px;
width: 0;
transition: 400ms ease all;
}
&::after {
right: inherit;
top: inherit;
left: 0;
bottom: 0;
}
}
.custom-button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
color: #fff;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
padding: 10px 15px;
font-size: 14px;
border-radius: 4px;
}
// 搜索框按钮的位置
.btnCol{
box-sizing: border-box;
text-align: right;
}
// 默认白色按钮样式
.el-button--default{
background: #FBFCFD;
border: 1px solid #E4EBF4;
border-radius: 4px;
}
.dialog_title {
@flex();
justify-content: space-between;
width: 100%;
}
.el-dialog__header {
margin-bottom: 10px;
color: #4A4A4A;
border-bottom: 1px solid #E4EBF4;
}
.el-dialog__body {
padding-top: 10px;
padding-bottom: 0;
}
.el-dialog__headerbtn .el-dialog__close {
color: #6B7A99 !important;
position: relative;
top: -2px;
}
.dialog_footer {
@flex-center();
}
.el-form-item {
@flex();
width: 100%;
}
.el-dialog__wrapper {
@flex-center();
}
.el-dialog {
margin: 0;
}
// cover some element-ui styles
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
color: #686666;
}
.el-upload {
input[type="file"] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
.cell {
.el-tag {
margin-right: 0px;
}
}
.small-padding {
.cell {
padding-left: 5px;
padding-right: 5px;
}
}
.fixed-width {
.el-button--mini {
padding: 7px 10px;
min-width: 60px;
}
}
.status-col {
.cell {
padding: 0 10px;
text-align: center;
.el-tag {
margin-right: 0px;
}
}
}
// to fixed https://github.com/ElemeFE/element/issues/2461
.el-dialog {
transform: none;
left: 0;
position: relative;
margin: 0 auto;
}
// refine element ui upload
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}
// dropdown
.el-dropdown-menu {
a {
display: block
}
}
// fix date-picker ui bug in filter-item
.el-range-editor.el-input__inner {
display: inline-flex !important;
}
// to fix el-date-picker css style
.el-range-separator {
box-sizing: content-box;
}
.el-submenu__icon-arrow {
margin-top: -5px;
}
/* --------------进度条美化---------------- */
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-track {
width: 7px;
background-color: rgba(255, 255, 255, 0);
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
::-webkit-scrollbar-thumb {
background-color: rgb(207, 208, 209);
background-clip: padding-box;
min-height: 28px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgb(162, 164, 167);
}
// element 样式补丁
.el-menu--horizontal {
border-bottom: none !important;
}
// 按钮型tab样式
.btnTab {
.el-tabs__nav.is-top {
line-height: 36px;
}
.el-tabs__item {
height: 36px;
line-height: 36px;
}
.el-tabs__item:first-child {
border-radius: 4px 0 0 4px;
}
.el-tabs__item:last-child {
border-radius: 0 4px 4px 0;
}
}
.el-radio-group {
.el-radio-button__inner {
height: 36px;
line-height: 36px;
padding: 0 20px;
font-size: 14px;
}
.el-radio-button:first-child {
border-radius: 4px 0 0 4px;
}
.el-radio-button:last-child {
border-radius: 0 4px 4px 0;
}
}
.el-tabs__item:focus.is-active.is-focus:not(:active) {
box-shadow: none !important;
}
// element table 不能对齐
.el-table th.gutter {
display: table-cell !important;
}
// input type=number 上下箭头
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
input[type="number"] {
-moz-appearance: textfield;
}
// Radio 单选框升级之后的问题
.el-radio {
margin-right: 0 !important;
}
// Divider 分割线 样式的修改
.el-divider--horizontal {
margin: 10px 0 !important;
}
.el-row {
margin-bottom: 0 !important;
}
// form
.el-form-item__content {
margin-left: 0 !important;
}
.el-icon-full-screen,
.el-icon-rank {
cursor: pointer;
}
// element table 选中 颜色
.el-table--enable-row-hover .el-table__body tr:hover>td {
background-color: #FCFDFD;
}
.el-table__body .el-table__row.hover-row td {
background-color: #FCFDFD;
}
.el-table tbody tr:hover>td {
background-color: #FCFDFD;
}
// 表格样式
.el-table th {
background-color: #FBFCFD !important;
height: 48px !important;
font-size: 14px;
color: #4A4A4A;
}
.el-table th.is-leaf,
.el-table td {
border-bottom: 1px solid #E4EBF4;
}
.el-table tr:nth-child(even) {
background: #FCFDFD !important;
}
.el-table tr td {
font-size: 14px;
color: #7A7A7A;
}
.lb-table .el-table {
border: 1px solid #E4EBF4;
border-radius: 4px 4px 0 0;
}
// 表格暂无数据样式
.el-table__empty-text {
height: 200px;
line-height: 300px;
background: url('./images/table-bg.png') 50% 50% no-repeat;
background-size: 100px;
letter-spacing: 2px;
color: #aaa;
}
.el-upload-list__item.is-success:focus:not(:hover) .el-icon-close-tip {
display: none !important
}
.el-message-box__btns {
display: flex;
flex-direction: row-reverse;
}
.el-message-box__btns .el-button--primary {
margin-right: 10px;
}
// 全局修改button颜色
.el-button--primary {
background-color: #2FA5FF;
}
.el-button--primary.is-plain {
background: rgba(15, 147, 246, 0.12);
border: 1px solid #0F93F6;
color: #0F93F6;
}
.el-button--warning {
background-color: #FFB135;
}
.el-form-item__content {
//width: 100%;
flex: 1;
}
.el-submenu__title {
.svg-icon {
position: relative;
top: 2px;
}
}
.el-date-editor .el-input__prefix,
.el-select .el-input .el-select__caret,
.el-date-editor .el-range__icon {
color: #686666;
}
// 日期选择-日历图标位置
.el-date-editor .el-input__prefix {
left: auto;
right: 5px;
}
.el-input--prefix .el-input__inner {
padding-left: 17px;
}
.el-input__suffix-inner .el-icon-circle-close {
position: absolute;
right: 20px;
}
// 日期范围-日历图标位置
.el-date-editor .el-range__icon {
position: absolute;
right: 5px;
}
.el-date-editor .el-icon-circle-close {
position: absolute;
right: 20px;
}
.el-date-editor.el-date-editor--daterange .el-icon-circle-close {
position: absolute;
right: 25px;
}
// 表格分页
.el-pagination.is-background .btn-prev,
.el-pagination.is-background .btn-next,
.el-pagination.is-background .el-pager li,
.el-pagination .el-select .el-input .el-input__inner {
background-color: #fff !important;
border: 1px solid #E4EBF4 !important;
border-radius: 5px !important;
}
.el-pagination.is-background .el-pager li.active {
background-color: #0F93F6 !important;
border: 1px solid #0F93F6 !important;
color: #fff !important;
}
\ No newline at end of file
/**
* I think element-ui's default theme color is too light for long-term use.
* So I modified the default color and you can modify it to your liking.
**/
/* theme color */
$--color-primary: #0F93F6;
$--color-success: #67C23B;
$--color-warning: #E6A23C;
$--color-danger: #F46C6C;
// $--color-info: #1E1E1E;
$--button-font-weight: 400;
$--border-color-light: #dfe4ed;
$--border-color-lighter: #e6ebf5;
$--table-border: 1px solid #dfe6ec;
/* icon font path, required */
$--font-path: "~element-ui/lib/theme-chalk/fonts";
@import "~element-ui/packages/theme-chalk/src/index";
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
theme: $--color-primary;
}
@import './variables.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';
@import './dialog.scss';
@import './btn.scss';
/* CSS 初始化 */
html,
body,
div,
span,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
abbr,
address,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
samp,
small,
strong,
sub,
sup,
var,
b,
i,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
}
body {
height: 100%;
min-height: 100% !important;
-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 {
height: 100%;
box-sizing: border-box;
}
#app {
height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
.no-padding {
padding: 0px !important;
}
.padding-content {
padding: 4px 0;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
}
.fr {
float: right;
}
.fl {
float: left;
}
.pr-5 {
padding-right: 5px;
}
.pl-5 {
padding-left: 5px;
}
.block {
display: block;
}
.pointer {
cursor: pointer;
}
.inlineBlock {
display: block;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
aside {
background: #eef1f6;
padding: 8px 24px;
margin-bottom: 20px;
border-radius: 2px;
display: block;
line-height: 32px;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
color: #2c3e50;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
a {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
}
//main-container全局样式
.app-container {
padding: 20px;
}
.components-container {
margin: 30px 50px;
position: relative;
}
.pagination-container {
margin-top: 30px;
}
.text-center {
text-align: center
}
.sub-navbar {
height: 50px;
line-height: 50px;
position: relative;
width: 100%;
text-align: right;
padding-right: 20px;
transition: 600ms ease position;
background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
.subtitle {
font-size: 20px;
color: #fff;
}
&.draft {
background: #d0d0d0;
}
&.deleted {
background: #d0d0d0;
}
}
.link-type,
.link-type:focus {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
.filter-container {
padding-bottom: 10px;
.filter-item {
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
}
}
//refine vue-multiselect plugin
.multiselect {
line-height: 16px;
}
.multiselect--active {
z-index: 1000 !important;
}
.delete-button {
line-height: 32px;
cursor: pointer;
color: #FF7115;
font-size: 20px;
margin-right: 5px;
}
.add-button {
line-height: 32px;
color: #2FA5FF;
cursor: pointer;
font-size: 20PX;
}
// 遮罩层样式设置
.dialog_Style {
.el-dialog {
width: 40% !important;
}
}
.description {
.el-form-item__content {
width: 75%;
}
}
// 弹窗组件 input样式
.header-title {
margin-bottom: 18px;
line-height: 20px;
height: 20px;
font-size: 15px;
font-weight: bolder;
width: 200px;
margin-left: 45px;
}
.detailTree {
width: 188px !important;
}
// 禁用
.disabled {
color: #87adf3;
background-color: #fff;
cursor: not-allowed
}
.bad {
color: #f00;
background-color: #fff;
cursor: not-allowed
}
\ No newline at end of file
@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%;
}
@mixin pct($pct) {
width: #{$pct};
position: relative;
margin: 0 auto;
}
@mixin triangle($width, $height, $color, $direction) {
$width: $width/2;
$color-border-style: $height solid $color;
$transparent-border-style: $width solid transparent;
height: 0;
width: 0;
@if $direction==up {
border-bottom: $color-border-style;
border-left: $transparent-border-style;
border-right: $transparent-border-style;
} @else if $direction==right {
border-left: $color-border-style;
border-top: $transparent-border-style;
border-bottom: $transparent-border-style;
} @else if $direction==down {
border-top: $color-border-style;
border-left: $transparent-border-style;
border-right: $transparent-border-style;
} @else if $direction==left {
border-right: $color-border-style;
border-top: $transparent-border-style;
border-bottom: $transparent-border-style;
}
}
@mixin flex {
display: flex;
}
@mixin flex-center-row {
display: flex;
align-items: center;
flex-direction: row;
}
@mixin flex-center-col {
display: flex;
align-items: center;
flex-direction: column;
}
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
#app {
.main-container {
height: 100%;
transition: margin-left 0.28s;
margin-left: $sideBarWidth;
}
.sidebar-container {
transition: width 0.28s;
width: $sideBarWidth !important;
// background-color: $menuBg;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
background-image: linear-gradient(180deg, #25A0FE 2%, #43BDFF 100%);
border-radius: 0 12px 12px 0;
overflow: hidden;
// reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out,
0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
overflow-y: auto;
margin-right: 0 !important;
&::-webkit-scrollbar {
display: none;
}
}
.el-scrollbar__bar.is-vertical {
right: 0px;
}
.el-scrollbar {
height: 100%;
&::-webkit-scrollbar {
display: none;
}
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 150px);
}
}
.is-horizontal {
display: none;
}
a {
display: inline-block;
width: 100%;
overflow: hidden;
}
.svg-icon {
margin-right: 10px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
.el-menu {
background-color: transparent !important;
border: none;
height: 100%;
// overflow-y: auto;
width: 100% !important;
}
// menu hover
.el-menu--collapse .el-submenu__title,
.el-menu--collapse .submenu-title-noDropdown {
margin-left: 0px !important;
}
// 有子级
.el-submenu__title {
margin-left: 13px;
padding-left: 10px !important;
color: $menuText;
background-color: transparent !important;
&:hover {
color: #1ea6f8 !important;
background-color: #fff !important;
.svg-icon,
i,
span {
color: #1ea6f8 !important;
}
}
}
// 没有子级
.submenu-title-noDropdown {
background-color: transparent !important;
margin-left: 20px;
padding-left: 10px !important;
color: $menuText;
&:hover {
color: #1ea6f8 !important;
background-color: #fff !important;
.svg-icon {
color: #1ea6f8 !important;
}
i {
color: #1ea6f8 !important;
}
}
}
.submenu-title-noDropdown.is-active,
.el-submenu__title.is-active {
color: #1ea6f8 !important;
background-color: #fff !important;
.svg-icon {
color: #1ea6f8 !important;
}
i {
color: #1ea6f8 !important;
}
}
.submenu-title-noDropdown,
.el-submenu__title {
font-weight: 600;
font-size: $sideBarFontSize;
border-radius: 10px 0px 0px 10px;
>i {
color: $subMenuActiveText !important;
transform: rotate(90deg);
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-ms-transform: rotate(90deg);
-o-transform: rotate(90deg);
margin-right: 10px;
}
.svg-icon {
font-size: 18px;
}
}
.el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow {
transform: rotateZ(0deg) !important;
-webkit-transform: rotateZ(0deg) !important;
-moz-transform: rotateZ(0deg) !important;
-ms-transform: rotateZ(0deg) !important;
-o-transform: rotateZ(0deg) !important;
}
.is-active>.el-submenu__title {
color: #fff !important;
}
& .nest-menu .el-submenu>.el-submenu__title,
& .el-submenu .el-menu-item {
&.is-active {
background-color: #fff !important;
color: #1ea6f8 !important;
}
&:hover {
background-color: #fff !important;
color: #1ea6f8 !important;
}
// min-width: $sideBarWidth !important;
min-width: 152px !important;
background-color: transparent !important;
font-weight: 600;
font-size: $sideBarFontSize;
padding: 0 0 0 45px !important;
border-radius: 10px 0px 0px 10px;
margin-left: 13px;
&:hover {
background-color: #fff !important;
}
}
}
.hideSidebar {
.sidebar-container {
width: 54px !important;
}
.main-container {
margin-left: 54px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
.svg-icon {
margin-left: 16px;
}
.sub-el-icon {
margin-left: 19px;
}
}
}
.el-submenu {
overflow: hidden;
&>.el-submenu__title {
padding: 0 !important;
.svg-icon {
margin-left: 16px;
}
.sub-el-icon {
margin-left: 19px;
}
.el-submenu__icon-arrow {
display: none;
}
}
}
.el-menu--collapse {
.el-submenu {
&>.el-submenu__title {
&>span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
.el-menu--collapse .el-menu .el-submenu {
min-width: $sideBarWidth !important;
}
// mobile responsive
.mobile {
.main-container {
margin-left: 0px;
}
.sidebar-container {
transition: transform 0.28s;
width: $sideBarWidth !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-$sideBarWidth, 0, 0);
}
}
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}
// when menu collapsed
.el-menu--vertical {
&>.el-menu {
.svg-icon {
margin-right: 16px;
}
.sub-el-icon {
margin-right: 12px;
margin-left: -2px;
}
}
.nest-menu .el-submenu>.el-submenu__title,
.el-menu-item {
width: calc(100% - 12px);
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
font-weight: 600 !important;
font-size: 15px !important;
background-color: #32ACFE !important;
color: #fff !important;
&:hover {
// you can use $subMenuHover
// background-color: $menuHover !important;
// opacity: .9;
background-color: #fff !important;
color: #32ACFE !important;
.svg-icon,
i,
span {
color: #32ACFE !important;
}
}
}
// the scroll bar appears when the subMenu is too long
>.el-menu--popup {
max-height: 100vh;
overflow-y: auto;
background-color: #32ACFE !important;
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
min-width: 140px;
padding: 12px 0;
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}
\ No newline at end of file
// global transition css
/* fade */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all .5s;
}
.fade-transform-enter {
opacity: 0;
transform: translateX(-30px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* breadcrumb transition */
.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;
}
// base color
$blue:#324157;
$light-blue:#3A71A8;
$red:#C03639;
$pink: #E65D6E;
$green: #30B08F;
$tiffany: #4AB7BD;
$yellow:#FEC171;
$panGreen: #30B08F;
// sidebar
$menuText:#ffffff;
$menuActiveText:#ffffff;
$subMenuActiveText:#ffffff; // https://github.com/ElemeFE/element/issues/12951
$menuBg:#3983fc;
$menuHover:#2e69ca;
$subMenuBg:#3983fc;
$subMenuHover:#2e69ca;
$sideBarWidth: 160px;
$sideBarFontSize:15px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
menuText: $menuText;
menuActiveText: $menuActiveText;
subMenuActiveText: $subMenuActiveText;
menuBg: $menuBg;
menuHover: $menuHover;
subMenuBg: $subMenuBg;
subMenuHover: $subMenuHover;
sideBarWidth: $sideBarWidth;
sideBarFontSize:$sideBarFontSize;
}