移除了主体色替换功能

路由过渡动画的配置由`@/config`移动至`@/layout/store/page.js`

使用新的上传组件替换el-upload

引入了file-saver以替换原有的文件下载代码

上传文件时key的拼接加入用户id

修复列表页自适应高度的问题

代码优化

样式修改

部分文件名称修改
master
toesbieya 6 years ago
parent 3e01d306c3
commit c44753a0ce
  1. 4
      vue/full/element-ui-personal/style/message.scss
  2. 14
      vue/full/package-lock.json
  3. 4
      vue/full/package.json
  4. 3
      vue/full/src/api/request.js
  5. 66
      vue/full/src/asset/style/cover/element-ui.scss
  6. 17
      vue/full/src/asset/style/cover/var.scss
  7. 2
      vue/full/src/asset/style/display.scss
  8. 27
      vue/full/src/asset/style/index.scss
  9. 32
      vue/full/src/asset/style/mixin.scss
  10. 2
      vue/full/src/asset/style/nprogress.scss
  11. 51
      vue/full/src/asset/style/var.scss
  12. 6
      vue/full/src/component/Empty/style.scss
  13. 2
      vue/full/src/component/LinerProgress/style.scss
  14. 42
      vue/full/src/component/RegionSelector/Tab/style.scss
  15. 6
      vue/full/src/component/TreeSelect/index.vue
  16. 309
      vue/full/src/component/Upload/PictureCard.vue
  17. 147
      vue/full/src/component/Upload/Qiniu.vue
  18. 135
      vue/full/src/component/Upload/style.scss
  19. 242
      vue/full/src/component/UploadFile/index.vue
  20. 34
      vue/full/src/component/UploadFile/style.scss
  21. 4
      vue/full/src/component/abstract/Dialog/index.vue
  22. 9
      vue/full/src/component/checkbox/ImgCheckbox.vue
  23. 11
      vue/full/src/component/form/Anchor/index.vue
  24. 6
      vue/full/src/component/form/ValidateInfo/style.scss
  25. 4
      vue/full/src/component/menu/ContextMenu/index.vue
  26. 4
      vue/full/src/component/menu/NavMenu/index.vue
  27. 2
      vue/full/src/component/menu/NavMenu/style.scss
  28. 2
      vue/full/src/component/menu/NavMenu/theme-dark.scss
  29. 2
      vue/full/src/component/menu/NavMenu/theme-light.scss
  30. 12
      vue/full/src/config/index.js
  31. 8
      vue/full/src/globalMethod/Guide/style.scss
  32. 4
      vue/full/src/globalMethod/Verify/style.scss
  33. 4
      vue/full/src/layout/component/Aside/component/MenuSearch.vue
  34. 3
      vue/full/src/layout/component/Aside/style.scss
  35. 6
      vue/full/src/layout/component/Aside/theme-dark.scss
  36. 6
      vue/full/src/layout/component/Aside/theme-light.scss
  37. 2
      vue/full/src/layout/component/Logo.vue
  38. 2
      vue/full/src/layout/component/Navbar/style.scss
  39. 2
      vue/full/src/layout/component/Navbar/theme-dark.scss
  40. 4
      vue/full/src/layout/component/Navbar/theme-light.scss
  41. 4
      vue/full/src/layout/component/Page/component/BackToTop.vue
  42. 2
      vue/full/src/layout/component/Page/component/PageFooter.vue
  43. 6
      vue/full/src/layout/component/Page/component/PageHeader.vue
  44. 8
      vue/full/src/layout/component/Page/component/TagsView/style.scss
  45. 42
      vue/full/src/layout/component/Page/index.vue
  46. 23
      vue/full/src/layout/component/Page/style.scss
  47. 34
      vue/full/src/layout/component/SettingDrawer.vue
  48. 13
      vue/full/src/layout/mixin/decideRouterTransition.js
  49. 6
      vue/full/src/layout/mixin/offline.js
  50. 2
      vue/full/src/layout/store/app.js
  51. 17
      vue/full/src/layout/store/page.js
  52. 10
      vue/full/src/layout/store/tagsView.js
  53. 4
      vue/full/src/main.js
  54. 6
      vue/full/src/mixin/docDetailMixin.js
  55. 2
      vue/full/src/plugin/webgl/fluid/index.js
  56. 2
      vue/full/src/plugin/webgl/fluid/obj.js
  57. 5
      vue/full/src/router/guardian/accessControl.js
  58. 7
      vue/full/src/router/module/dev/child/uploadFileDev.js
  59. 9
      vue/full/src/router/module/dev/index.js
  60. 105
      vue/full/src/store/module/resource.js
  61. 41
      vue/full/src/store/module/user.js
  62. 10
      vue/full/src/store/module/websocket.js
  63. 9
      vue/full/src/util/auth.js
  64. 4
      vue/full/src/util/browser.js
  65. 1
      vue/full/src/util/excel/common.js
  66. 23
      vue/full/src/util/file.js
  67. 36
      vue/full/src/view/_app/login/style.scss
  68. 23
      vue/full/src/view/_common/DetailPage/style.scss
  69. 7
      vue/full/src/view/_common/ExceptionPage/style.scss
  70. 32
      vue/full/src/view/_common/ListPage/autoAdaptHeightMixin.js
  71. 2
      vue/full/src/view/_common/SkeletonPage/style.scss
  72. 5
      vue/full/src/view/admin/index/component/PanelGroup/PanelGroupItem.vue
  73. 10
      vue/full/src/view/admin/index/component/PanelGroup/index.vue
  74. 4
      vue/full/src/view/admin/message/user/MessageStream.vue
  75. 2
      vue/full/src/view/admin/message/user/indexPage.vue
  76. 2
      vue/full/src/view/admin/purchase/inbound/detailPage.vue
  77. 2
      vue/full/src/view/admin/purchase/order/detailPage.vue
  78. 2
      vue/full/src/view/admin/sell/order/detailPage.vue
  79. 2
      vue/full/src/view/admin/sell/outbound/detailPage.vue
  80. 2
      vue/full/src/view/admin/system/menu/indexPage.vue
  81. 4
      vue/full/src/view/admin/userCenter/component/UserCard.vue
  82. 2
      vue/full/src/view/dev/index.vue
  83. 4
      vue/full/src/view/example/component/skeletonPage.vue
  84. 15
      vue/full/src/view/example/component/uploadPage.vue
  85. 4
      vue/full/src/view/example/test/develop/component/ImageCompressTest.vue
  86. 8
      vue/full/vue.config.js

@ -7,7 +7,7 @@
position: absolute;
box-shadow: 0 1px 3px rgba(#000, .2), 0 1px 1px rgba(#000, .14), 0 2px 1px -1px rgba(#000, .12);
background-color: $--color-danger;
color: #ffffff;
color: $--color-white;
border-radius: 4px;
top: -10px;
left: -10px;
@ -23,7 +23,7 @@
left: 10px;
right: 10px;
animation: el-message-progress linear;
background: currentColor;
background-color: currentColor;
opacity: .3;
transform-origin: 0 50%;
transform: scaleX(0);

@ -7552,6 +7552,11 @@
"schema-utils": "^2.5.0"
}
},
"file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npm.taobao.org/file-saver/download/file-saver-2.0.5.tgz?cache=0&sync_timestamp=1605790845476&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffile-saver%2Fdownload%2Ffile-saver-2.0.5.tgz",
"integrity": "sha1-1hz+LOBZ9BTYmendbUEH7iVnDDg="
},
"filesize": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
@ -14275,15 +14280,6 @@
"source-map": "~0.6.1"
}
},
"webpack-theme-color-replacer": {
"version": "1.3.14",
"resolved": "https://registry.npmjs.org/webpack-theme-color-replacer/-/webpack-theme-color-replacer-1.3.14.tgz",
"integrity": "sha512-PKT/OD2+iXxGuGDxH/MVkt36EKYtYckowzPlZBdPOgGIVk8MbvLfdRFYOmWz6vhVJpyIGWtpfpoP9ecWZP8qYg==",
"dev": true,
"requires": {
"webpack-sources": "*"
}
},
"websocket-driver": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",

@ -13,6 +13,7 @@
"core-js": "^3.6.5",
"decimal.js": "^10.2.0",
"element-ui": "^2.13.2",
"file-saver": "^2.0.5",
"js-md5": "^0.7.3",
"normalize.css": "^8.0.1",
"nprogress": "^0.2.0",
@ -34,8 +35,7 @@
"svg-sprite-loader": "^4.3.0",
"svgo": "^1.3.2",
"vue-template-compiler": "^2.6.11",
"webpack-bundle-analyzer": "^3.6.1",
"webpack-theme-color-replacer": "^1.3.14"
"webpack-bundle-analyzer": "^3.6.1"
},
"postcss": {
"plugins": {

@ -3,6 +3,7 @@ import {apiPrefix} from '@/config'
import {MessageBox, Notification} from 'element-ui'
import Message from '@ele/component/Message'
import store from '@/store'
import {isLogin} from '@/util/auth'
const instance = axios.create({
baseURL: apiPrefix,
@ -13,7 +14,7 @@ const instance = axios.create({
instance.interceptors.request.use(
config => {
//登录状态下socket断连时,除登出外中断一切请求
if (store.state.user.id && !store.state.socket.online && config.url !== '/account/logout') {
if (isLogin() && !store.state.websocket.online && config.url !== '/account/logout') {
Message.error('请等待与服务器重新连接')
return Promise.reject('')
}

@ -1,14 +1,6 @@
// cover some element-ui styles
.el-alert {
&--info.is-light {
color: #606266;
background-color: #f0faff;
border: 1px solid #abdcff;
.el-icon-info {
color: $--color-primary;
}
}
.el-backtop {
border-radius: 0;
}
.el-button {
@ -57,7 +49,7 @@
min-width: 384px;
.el-dialog__header {
border-bottom: 1px solid #EBEEF5;
border-bottom: 1px solid $--border-color-lighter;
padding: 17px;
.el-dialog__title {
@ -72,7 +64,7 @@
}
.el-dialog__footer {
border-top: 1px solid #EBEEF5;
border-top: 1px solid $--border-color-lighter;
padding: 10px 16px;
}
@ -89,7 +81,7 @@
}
.el-loading-mask {
background: #ffffff;
background-color: $--color-white;
color: $--color-primary;
}
@ -146,24 +138,25 @@
@include max-width-on-mobile;
border-width: 0;
.el-message__icon, .el-message__content {
color: #ffffff !important;
.el-message__icon,
.el-message__content {
color: $--color-white !important;
}
&.el-message--success {
background: $--color-success;
background-color: $--color-success;
}
&.el-message--info {
background: $--color-primary;
background-color: $--color-primary;
}
&.el-message--warning {
background: $--color-warning;
background-color: $--color-warning;
}
&.el-message--error {
background: $--color-danger;
background-color: $--color-danger;
}
}
@ -175,10 +168,6 @@
.el-notification {
@include deep-shadow;
.el-notification__icon.el-icon-info {
color: $--color-primary;
}
}
.el-pagination {
@ -214,37 +203,8 @@
}
}
.el-table {
thead {
color: #303133
}
thead > tr > th {
background: #fbfbfb;
}
}
.el-textarea {
.el-input__count {
background: transparent;
}
}
.el-upload {
$size: 100px;
&-list__item-thumbnail {
object-fit: cover;
}
&.el-upload--picture-card {
height: $size;
width: $size;
line-height: $size;
}
&-list .el-upload-list__item {
height: $size;
width: $size;
background-color: transparent;
}
}

@ -0,0 +1,17 @@
/*仅覆盖element-ui中的预设变量,不会被使用*/
//返回顶部的背景色
$--backtop-background-color: $--background-color-base;
//返回顶部hover背景色
$--backtop-hover-background-color: $--border-color-lighter;
//el-menu的hover背景颜色
$--menu-item-hover-fill: none;
//el-table的表头字体颜色
$--table-header-font-color: $--color-text-primary;
//el-table的表头背景颜色
$--table-header-background-color: #fbfbfb;
//引入element的scss时需要覆盖字体路径
$--font-path: '~element-ui/lib/theme-chalk/fonts';

@ -1,4 +1,4 @@
@media only screen and (max-width: 768px) {
@media only screen and (max-width: $--sm) {
.hidden-xs {
display: none !important
}

@ -1,11 +1,13 @@
@import "~normalize.css/normalize.css";
@import "./variables";
@import "~element-ui/packages/theme-chalk/src/index";
@import "./var.scss";
@import "./cover/var.scss";
@import "./mixin.scss";
@import "~element-ui/packages/theme-chalk/src/index.scss";
@import "~@ele/style/index.scss";
@import "./element-ui";
@import './display';
@import "./transition";
@import "./nprogress";
@import "./cover/element-ui.scss";
@import './display.scss';
@import "./transition.scss";
@import "./nprogress.scss";
html {
height: 100%;
@ -59,7 +61,7 @@ a:hover {
background-color: rgba($--color-primary, 0.8);
&:hover {
background-color: rgba($--color-primary, 1);
background-color: $--color-primary;
}
}
}
@ -169,10 +171,11 @@ $max-height: '100vh - #{$page-view-margin * 2}';
}
}
//仅用于演示用例
.tip-row {
position: relative;
z-index: 1;
background: #fafafa;
background-color: #fafafa;
padding: 8px 24px;
margin-bottom: 20px;
border-radius: 2px;
@ -206,18 +209,18 @@ $max-height: '100vh - #{$page-view-margin * 2}';
position: relative;
top: -1px;
margin-right: 11px;
background: $--color-info;
background-color: $--color-info;
&.success {
background: $--color-success;
background-color: $--color-success;
}
&.primary {
background: $--color-primary;
background-color: $--color-primary;
}
&.error {
background: $--color-danger;
background-color: $--color-danger;
}
}

@ -0,0 +1,32 @@
@mixin clearfix {
&::after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
@mixin deep-shadow {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
}
@mixin mobile {
@media (max-width: $max-mobile-width) {
@content
}
}
@mixin pc {
@media (min-width: $max-mobile-width) {
@content
}
}
@mixin max-width-on-mobile {
@include mobile {
max-width: 384px;
}
}

@ -2,7 +2,7 @@
#nprogress {
.bar {
background: $--color-primary;
background-color: $--color-primary;
.peg {
box-shadow: 0 0 10px $--color-primary, 0 0 5px $--color-primary;;

@ -1,52 +1,15 @@
@import "~element-ui/packages/theme-chalk/src/common/var";
//设定移动端的最大宽度只能是number类型
$max-mobile-width: 500px;
//覆盖element颜色
$--color-primary: #1890ff; //这里如果有改动需要同时修改vue.config.js中ThemeColorReplacer的配置
$--color-primary: #1890ff;
$--color-success: #21BA45;
$--color-warning: #FF5722;
$--color-danger: #F44336;
$--color-info: #009688;
//覆盖el-menu的hover背景颜色
$--menu-item-hover-fill: none;
//引入element的scss时需要覆盖字体路径
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@mixin clearfix {
&::after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
@mixin deep-shadow {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
}
@mixin mobile {
@media (max-width: $max-mobile-width) {
@content
}
}
@mixin pc {
@media (min-width: $max-mobile-width) {
@content
}
}
@mixin max-width-on-mobile {
@include mobile {
max-width: 384px;
}
}
//菜单中icon的大小
$menu-icon-size: 14px;
//菜单中icon和文字的距离
@ -60,7 +23,7 @@ $sub-menu-background-dark: darken($menu-background-dark, 5%);
//暗色主菜单的背景色用于双层侧边栏的主菜单和顶部导航栏
$root-menu-background-dark: #1F293D;
//亮色菜单的文字颜色
$menu-text-color-light: #303133;
$menu-text-color-light: $--color-text-primary;
//暗色菜单的文字颜色
$menu-text-color-dark: rgba(255, 255, 255, .65);
//亮色菜单的文字hover颜色
@ -100,10 +63,8 @@ $page-view-margin: 24px;
//页脚高度用于max-view-height
$page-footer-height: 64px;
//亮色边框的颜色使用的element-ui原定义
$border-color-light: #dcdfe6;
//暗色边框的颜色
$border-color-dark: darken($root-menu-background-dark, 5%);
//暗色边框的颜色用于nav-menu和侧边栏
$--border-color-dark: darken($root-menu-background-dark, 5%);
//dialog距离顶部的距离用于<el-dialog>的top属性<el-dialog>中滚动区域的最大高度
$dialog-top: 100px;

@ -1,3 +1,5 @@
@import "~@/asset/style/var";
.ant-empty {
padding: 20px;
margin: 0 8px;
@ -29,7 +31,7 @@
&-normal {
margin: 32px 0;
color: rgba(0, 0, 0, .25);
color: $--color-text-secondary;
.ant-empty-image {
height: 40px;
@ -38,7 +40,7 @@
&-small {
margin: 8px 0;
color: rgba(0, 0, 0, .25);
color: $--color-text-secondary;
.ant-empty-image {
height: 35px;

@ -1,4 +1,4 @@
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.liner-progress {
position: relative;

@ -1,13 +1,16 @@
.rg-header, .rg-search {
@import "~@/asset/style/var";
.rg-header,
.rg-search {
padding: 2px 10px 0;
background-color: #ffffff;
background-color: $--color-white;
}
.rg-header {
h3 {
padding: 6px;
margin: 0;
color: #24292e;
color: $--color-text-primary;
font-size: 16px;
}
@ -19,7 +22,7 @@
background: 0 0;
border: 0;
outline: none;
color: #999;
color: $--color-text-secondary;
top: 9px;
i {
@ -27,7 +30,7 @@
}
&:hover {
color: #000000;
color: $--color-text-regular;
}
&.rg-removeall-button {
@ -42,13 +45,12 @@
.rg-level-tabs {
margin-top: 10px;
background-color: #ffffff;
ul {
padding: 0;
margin: 0;
line-height: 1.5;
border-bottom: 1px solid #eee;
border-bottom: $--border-base;
li {
display: inline-block;
@ -58,7 +60,7 @@
display: block;
padding: 0.2rem 1rem 0.6rem;
font-size: 14px;
color: #bbb;
color: $--color-text-secondary;
text-decoration: none;
cursor: pointer;
line-height: 1.43;
@ -66,8 +68,7 @@
&.active {
a {
color: #333;
background-color: #ffffff;
color: $--color-text-primary;
font-weight: 600;
}
@ -78,7 +79,7 @@
bottom: 0;
height: 0.2rem;
width: 100%;
background-color: #bbb;
background-color: $--color-text-placeholder;
}
}
}
@ -89,28 +90,27 @@
.rg-results {
margin: 0;
padding: 5px;
width: 364px;
width: 384px;
line-height: 1.5;
li {
overflow: hidden;
padding: 3px 10px;
font-size: 14px;
color: #000000;
cursor: pointer;
&.rg-item {
display: inline-block;
color: #777;
color: $--color-text-secondary;
&:hover {
color: #000000;
background-color: #F5F5F5;
&:hover:not(.active) {
color: $--color-text-primary;
background-color: rgba($--color-text-placeholder,.2);
}
&.active {
background-color: #E4EAEE;
color: #000000;
color: $--color-text-primary;
background-color: rgba($--color-text-placeholder,.6);
}
}
@ -125,7 +125,7 @@
}
.rg-input {
background-color: #f5f5f5;
background-color: rgba($--color-text-placeholder,.2);
border: 0;
width: 100%;
font-size: 14px;
@ -134,6 +134,6 @@
border-radius: 2px;
&::-webkit-input-placeholder {
color: #999;
color: $--color-text-placeholder;
}
}

@ -199,7 +199,7 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.tree-select-container {
padding: 8px;
@ -209,8 +209,8 @@ export default {
}
.el-tree-node__label.is-disabled {
background-color: #edf2fc;
border-color: $border-color-light;
background-color: $--font-color-disabled-base;
border-color: $--border-color-base;
cursor: not-allowed;
}
}

@ -0,0 +1,309 @@
<template>
<div :class="{'upload-file': true, 'is-disabled': disabled}">
<div
v-for="file in fileList"
:key="file.id"
:class="`upload-file-item is-${file.status}`"
:title="file.name"
>
<img :src="file.thumbnailUrl">
<div v-if="file.status === 'uploading'" class="upload-file-item-loading">
<i class="el-icon-loading"/>
<p>上传中</p>
</div>
<template v-else>
<div class="upload-file-item-action">
<template v-if="file.status === 'success'">
<i class="el-icon-zoom-in" title="预览" @click="handlePreview(file)"/>
<i class="el-icon-download" title="下载" @click="handleDownload(file)"/>
</template>
<i v-if="!disabled" class="el-icon-delete" title="删除" @click="handleRemove(file)"/>
</div>
<label :class="`upload-file-item-status-label`">
<i :class="`el-icon-${file.status==='success'?'check':'close'}`"/>
</label>
</template>
</div>
<div v-if="!hideTrigger" class="upload-file-trigger" @click="handleTriggerClick">
<i class="el-icon-plus"></i>
<input ref="input" type="file" :multiple="multiple" :accept="accept" @change="handleInputChange">
</div>
</div>
</template>
<script>
import ajax from 'element-ui/packages/upload/src/ajax'
export default {
name: "UploadPictureCard",
props: {
//
action: String,
//file
headers: [Object, Function],
//file
data: [Object, Function],
//cookie
withCredentials: Boolean,
//
name: {type: String, default: 'file'},
//
httpRequest: {type: Function, default: ajax},
//
multiple: Boolean,
//inputaccept
accept: String,
//
uploadedFileList: {type: Array, default: () => []},
//
disabled: Boolean,
//
hideTriggerOnDisabled: {type: Boolean, default: true},
//<1
limit: {type: Number, default: 0},
//
hideTriggerOnExceed: {type: Boolean, default: true},
//
autoDelete: {type: Boolean, default: true},
//1NumberBStringKBMB
maxSize: {type: [Number, String], default: 0},
// false Promise reject
beforeUpload: Function,
// false Promise reject
beforeRemove: Function
},
data() {
//id
this.tempId = 1
//<file.id, XHR>
this.requestMap = {}
return {
//
fileList: []
}
},
computed: {
needLimit() {
return this.limit >= 1
},
hideTrigger() {
return this.disabled && this.hideTriggerOnDisabled
|| this.needLimit && this.fileList.length >= this.limit && this.hideTriggerOnExceed
}
},
watch: {
uploadedFileList: {
immediate: true,
handler(arr) {
//
Object.entries(this.requestMap).forEach(([id, xhr]) => {
if (arr.every(file => file.id !== id)) {
//abort
xhr.abort && xhr.abort()
delete this.requestMap[id]
}
})
this.fileList = arr.map(item => ({
...item,
id: item.id || this.tempId++,
status: item.status || 'success',
thumbnailUrl: item.thumbnailUrl || item.url,
downloadUrl: item.thumbnailUrl || item.url
}))
}
}
},
methods: {
parseMaxSize(maxSize) {
if (typeof maxSize === 'number') {
return maxSize
}
const map = {KB: 1024, MB: 1024 * 1024},
upper = maxSize.toUpperCase(),
num = upper.replace(/[^0-9]/ig, ""),
unit = upper.replace(num, "")
return parseFloat(num) * (map[unit] || 1)
},
//
handleTriggerClick() {
if (this.disabled) return
this.$refs.input.value = null
this.$refs.input.click()
},
//input
handleInputChange(e) {
const files = e.target.files
if (!files || files.length === 0) return
if (this.needLimit && this.fileList.length + files.length > this.limit) {
return this.$emit('exceed')
}
files.forEach(this.upload)
},
//
handlePreview(file) {
this.$emit('preview', file)
},
//
handleDownload(file) {
this.$emit('download', file)
},
//
handleRemove(file) {
this.remove(file)
},
//
upload(rawFile) {
//
const maxSize = this.parseMaxSize(this.maxSize)
if (maxSize >= 1 && rawFile.size > maxSize) {
return this.$emit('over-max-size', rawFile)
}
rawFile.id = this.tempId++
const file = {
id: rawFile.id,
name: rawFile.name,
thumbnailUrl: rawFile.type.startsWith('image') && window.URL.createObjectURL(rawFile),
status: 'uploading',
percentage: 0,
raw: rawFile
}
this.fileList.push(file)
if (!this.beforeUpload) {
return this.sendRequest(file)
}
const result = this.beforeUpload(file)
if (!result) {
return this.removeFile(file)
}
result.then
? result.then(() => this.sendRequest(file), () => this.removeFile(file))
: this.sendRequest(file)
},
sendRequest(file) {
const {id, raw} = file
const headers = typeof this.headers === 'function' ? this.headers(file) : this.headers
const data = typeof this.data === 'function' ? this.data(file) : this.data
const options = {
headers,
data,
withCredentials: this.withCredentials,
file: raw,
filename: this.name,
action: this.action,
onProgress: e => {
this.onProgress(e, file)
},
onSuccess: res => {
this.onSuccess(res, file)
delete this.requestMap[id]
},
onError: err => {
this.onError(err, file)
delete this.requestMap[id]
}
}
const req = this.httpRequest(options)
this.requestMap[id] = req
req && req.then && req.then(options.onSuccess, options.onError)
},
abortRequest(file) {
const map = this.requestMap
if (file) {
const req = map[file.id]
req && req.abort && req.abort()
delete map[file.id]
return
}
Object.entries(map).forEach(([id, req]) => {
req && req.abort && req.abort()
delete map[id]
})
},
onProgress(e, file) {
file.status = 'uploading'
file.percentage = e.percent || 0
},
onSuccess(res, file) {
file.status = 'success'
file.response = res
this.$emit('success', res, file)
},
onError(err, file) {
file.status = 'error'
if (!file.response) file.response = {}
file.response.err = err
this.autoDelete && this.removeFile(file)
this.$emit('error', err, file)
},
//
remove(file) {
if (!this.beforeRemove) {
return this.removeFile(file)
}
const result = this.beforeRemove(file)
if (!result) return
result.then
? result.then(() => this.removeFile(file), () => ({}))
: this.removeFile(file)
},
removeFile(file) {
this.revokeBlob(file)
this.fileList.splice(this.fileList.indexOf(file), 1)
this.$emit('remove', file)
},
revokeBlob({thumbnailUrl, downloadUrl}) {
if (thumbnailUrl && thumbnailUrl.startsWith('blob:')) {
window.URL.revokeObjectURL(thumbnailUrl)
}
if (downloadUrl && downloadUrl.startsWith('blob:')) {
window.URL.revokeObjectURL(downloadUrl)
}
}
},
beforeDestroy() {
//
this.abortRequest()
//
this.fileList.forEach(this.revokeBlob)
}
}
</script>
<style lang="scss" src="./style.scss"></style>

@ -0,0 +1,147 @@
<template>
<upload-picture-card
ref="upload"
:uploaded-file-list="uploadedFileList"
:limit="limit"
:multiple="multiple"
:http-request="httpRequest"
:before-upload="beforeUpload"
@exceed="onExceed"
@over-max-size="onOverMaxSize"
@success="onSuccess"
@error="onError"
@preview="onPreview"
@download="onDownload"
@remove="onRemove"
/>
</template>
<script>
/*
* 直传七牛云
* 传入fileList自动拼接七牛云外链前缀
* 非图片类型的文件使用kkFileView预览
* successremove事件中的file地址不带七牛云外链前缀
* */
import UploadPictureCard from "./PictureCard"
import axios from 'axios'
import {deleteUpload} from '@/api/file'
import {file as fileConfig} from '@/config'
import {elError} from "@/util/message"
import {isImage, isDoc, isPdf, isPpt, isRar, isXls, isTxt, isZip} from "@/util/validate"
import {preview, download, upload, autoCompleteUrl} from "@/util/file"
const typeMapper = [
{test: isImage}, {test: isDoc, type: 'doc'}, {test: isPdf, type: 'pdf'}, {test: isPpt, type: 'ppt'},
{test: isRar, type: 'rar'}, {test: isXls, type: 'xls'}, {test: isTxt, type: 'txt'}, {test: isZip, type: 'zip'},
]
function getCoverImage(url, fileType) {
const mapper = typeMapper.find(({test}) => test(fileType))
return mapper && mapper.type ? `${process.env.BASE_URL}static/img/fileType/${mapper.type}.png` : url
}
export default {
name: "QiniuUpload",
components: {UploadPictureCard},
props: {
disabled: Boolean,
multiple: {type: Boolean, default: true},
fileList: {type: Array, default: () => []},
limit: {type: Number, default: 10},
maxSize: {type: [Number, String], default: '10MB'}
},
computed: {
//{url, name}{thumbnailUrl, downloadUrl, name}
uploadedFileList() {
return this.fileList.map(file => {
const downloadUrl = autoCompleteUrl(file.url)
return {...file, thumbnailUrl: getCoverImage(downloadUrl, file.name), downloadUrl}
})
}
},
methods: {
//
onExceed() {
elError(`最多只能上传${this.limit}个文件`)
},
//
onOverMaxSize(file) {
const maxSize = this.$refs.upload.parseMaxSize(file.size)
elError(`${file.name}的大小超出${this.$options.filters.number2StorageUnit(maxSize)}`)
},
//successkey
onSuccess(res, file) {
file.raw.key = res.key
file.downloadUrl = autoCompleteUrl(res.key)
this.$emit('success', file, res)
},
//
onError(err) {
elError('上传文件失败:' + err)
},
//
onPreview(file) {
if (!isImage(file.name)) return preview(file.downloadUrl)
const fileList = this.$refs.upload.fileList
const index = fileList.indexOf(file)
const previewUrlList = fileList.filter(i => isImage(i.name)).map(i => i.downloadUrl)
this.$image({index, urlList: previewUrlList})
},
//
onDownload(file) {
download(file.downloadUrl, file.name)
},
//
onRemove(file) {
//remove
if (!file.raw) {
this.$emit('remove', {url: file.downloadUrl.replace(fileConfig.storePrefix, '')})
}
//OSS
else if (file.response && !file.response.err) {
deleteUpload.request(file.raw.key)
}
},
//
beforeUpload(file) {
if (!typeMapper.some(({test}) => test(file.name))) {
elError('只支持图片、word、pdf、ppt、rar、excel、txt、zip类型的文件')
return false
}
const url = getCoverImage(file.thumbnailUrl, file.name)
//
if (url !== file.thumbnailUrl) {
file.thumbnailUrl = url
}
return true
},
//
httpRequest({file, onProgress}) {
const source = axios.CancelToken.source()
const promise = upload(file, file.name, {
onUploadProgress(e) {
if (e.total > 0) {
e.percent = (Number)((e.loaded / e.total * 100).toFixed(2))
}
onProgress(e)
},
cancelToken: source.token
})
//abort
promise.abort = source.cancel
return promise
}
}
}
</script>

@ -0,0 +1,135 @@
@import "~@/asset/style/var";
$--upload-size: 90px;
.upload-file {
display: inline-block;
&-item,
&-trigger {
height: $--upload-size;
width: $--upload-size;
float: left;
border-radius: 4px;
}
&-item {
position: relative;
margin: 0 8px 8px 0;
border: 1px solid $--border-color-base;
overflow: hidden;
> img {
height: 100%;
width: 100%;
object-fit: cover;
}
&-action,
&-loading {
position: absolute;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .6);
}
&-action {
display: flex;
justify-content: space-around;
align-items: center;
opacity: 0;
> i {
color: $--color-white;
cursor: pointer;
transition: transform .1s;
&:hover {
transform: scale(1.2);
}
}
}
&-loading {
padding-top: ($--upload-size - $--font-size-small * 2 - $--font-size-extra-large) / 2;
text-align: center;
color: $--color-white;
> i {
font-size: $--font-size-extra-large;
}
> p {
font-size: $--font-size-small;
}
}
&-status-label {
position: absolute;
right: -15px;
top: -6px;
width: 40px;
height: 24px;
text-align: center;
transform: rotate(45deg);
> i {
font-size: 12px;
color: $--color-white;
margin-top: 11px;
transform: rotate(-45deg);
}
}
&.is-success > &-status-label {
background-color: $--color-success;
}
&.is-error > &-status-label {
background-color: $--color-danger;
}
&:hover {
> .upload-file-item-action {
opacity: 1;
}
> .upload-file-item-status-label {
display: none;
}
}
}
&-trigger {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1px dashed $--border-color-base;
cursor: pointer;
background-color: $--background-color-base;
> i {
font-size: 20px;
color: $--color-text-regular;
}
> input {
display: none;
}
&:hover {
border-color: $--color-primary;
}
}
&.is-disabled > &-trigger {
border-color: $--disabled-border-base;
cursor: not-allowed;
&:hover {
border-color: $--disabled-border-base;
}
}
}

@ -1,242 +0,0 @@
<template>
<el-upload
ref="upload"
:before-upload="beforeUpload"
:class="{disabled: hideUploader}"
:file-list="tempData"
:http-request="httpRequest"
:limit="limit"
:multiple="multiple"
:on-error="error"
:on-exceed="exceed"
:on-success="success"
action=""
list-type="picture-card"
>
<i class="el-icon-plus"/>
<template v-slot:file="{file}">
<span
class="el-upload-list__item-actions"
@mouseenter="e => handleBlockMouseEnter(e,file)"
@mouseleave="handleBlockMouseLeave"
>
<span class="el-upload-list__item-preview" @click="() => preview(file)">
<i class="el-icon-zoom-in"/>
</span>
<span class="el-upload-list__item-delete" @click="() => download(file)">
<i class="el-icon-download"/>
</span>
<span v-if="!disabled" class="el-upload-list__item-delete" @click="() => remove(file)">
<i class="el-icon-delete"/>
</span>
</span>
<template v-if="file.status === 'success'">
<img :src="file.url" class="el-upload-list__item-thumbnail">
<label class="el-upload-list__item-status-label">
<i class="el-icon-upload-success el-icon-check"/>
</label>
</template>
<div v-if="file.status === 'uploading'" class="progress-mask">
<el-progress :percentage="file.percentage" type="circle"/>
</div>
</template>
<el-tooltip ref="tooltip" :content="tooltipContent" popper-class="upload-tooltip"/>
</el-upload>
</template>
<script>
/*
* 直传七牛云
* 传入fileList自动拼接七牛云外链前缀并增加downloadUrl属性图片类型的文件url与downloadUrl相同
* 非图片类型的文件使用kkFileView预览
* successremove事件中的file.url均不带七牛云外链前缀
* */
import axios from 'axios'
import {deleteUpload} from '@/api/file'
import {file as fileConfig} from '@/config'
import {debounce, isEmpty, deepClone} from '@/util'
import {elError} from "@/util/message"
import {isImage, isDoc, isPdf, isPpt, isRar, isXls, isTxt, isZip} from "@/util/validate"
import {preview, download, upload, autoCompleteUrl} from "@/util/file"
const typeMapper = [
{test: isImage}, {test: isDoc, type: 'doc'}, {test: isPdf, type: 'pdf'}, {test: isPpt, type: 'ppt'},
{test: isRar, type: 'rar'}, {test: isXls, type: 'xls'}, {test: isTxt, type: 'txt'}, {test: isZip, type: 'zip'},
]
function parseMaxSize(maxSize) {
if (typeof maxSize === 'number') {
return maxSize
}
const map = {KB: 1024, MB: 1024 * 1024},
upper = maxSize.toUpperCase(),
num = upper.replace(/[^0-9]/ig, ""),
unit = upper.replace(num, "")
return parseInt(num) * (map[unit] || 1)
}
function getCoverImage(url, fileType) {
const mapper = typeMapper.find(({test}) => test(fileType))
return mapper && mapper.type ? `${process.env.BASE_URL}static/img/fileType/${mapper.type}.png` : url
}
export default {
name: 'UploadFile',
props: {
disabled: Boolean,
multiple: {type: Boolean, default: true},
fileList: {type: Array, default: () => []},
limit: {type: Number, default: 10},
maxSize: {type: [Number, String], default: '10MB'}
},
data() {
return {
tempData: this.fileList,
data: this.fileList,
tooltipContent: null
}
},
computed: {
count() {
return this.data.length
},
hideUploader() {
return this.disabled || this.count >= this.limit
},
previewUrlList() {
return this.data ? this.data.map(i => i.downloadUrl) : []
}
},
watch: {
fileList: {
immediate: true,
handler(fileList) {
if (isEmpty(fileList)) this.data = []
else {
this.data = [...this.fileList.map(file => {
const downloadUrl = autoCompleteUrl(file.url)
return {...file, url: getCoverImage(downloadUrl, file.name), downloadUrl}
})]
}
this.tempData = deepClone(this.data)
}
}
},
methods: {
//仿el-tabletooltip
handleBlockMouseEnter(event, file) {
const tooltip = this.$refs.tooltip
this.tooltipContent = file.name
tooltip.referenceElm = event.target
tooltip.$refs.popper && (tooltip.$refs.popper.style.display = 'none')
tooltip.doDestroy()
tooltip.setExpectedState(true)
this.activateTooltip(tooltip)
},
handleBlockMouseLeave() {
const tooltip = this.$refs.tooltip
if (tooltip) {
tooltip.setExpectedState(false)
tooltip.handleClosePopper()
}
},
//remove
remove(file) {
//tooltip
this.handleBlockMouseLeave()
//
//
if (!file.raw) this.$emit('remove', {url: file.downloadUrl.replace(fileConfig.storePrefix, '')})
//
else if (file.response && !file.response.err) deleteUpload.request(file.raw.key)
this.$refs.upload.handleRemove(file)
const index = this.data.findIndex(i => i.downloadUrl === file.downloadUrl)
if (index > -1) this.data.splice(index, 1)
},
//
preview(file) {
if (!isImage(file.name)) return preview(file.downloadUrl)
const index = this.data.findIndex(i => i.downloadUrl === file.downloadUrl)
this.$image({index, urlList: this.previewUrlList})
},
//
download(file) {
download(file.downloadUrl, file.name)
},
//
exceed() {
elError(`最多只能上传${this.limit}个文件`)
},
//successkey
success(res, file) {
file.url = getCoverImage(file.url, file.name)
file.raw.key = res.key
file.downloadUrl = autoCompleteUrl(res.key)
this.data.push(file)
this.$emit('success', file, res)
},
//
error(err, file) {
elError('上传文件失败,' + err)
file.response.err = err
this.remove(file)
},
//
beforeUpload(file) {
if (!typeMapper.some(({test}) => test(file.name))) {
elError('只支持图片、word、pdf、ppt、rar、excel、txt、zip类型的文件')
return false
}
const maxSize = parseMaxSize(this.maxSize)
if (file.size > maxSize) {
elError(`${file.name}的大小超出${this.$options.filters.number2StorageUnit(maxSize)}`)
return false
}
},
httpRequest({file, onProgress}) {
const source = axios.CancelToken.source()
const promise = upload(file, file.name, {
onUploadProgress(e) {
if (e.total > 0) {
e.percent = (Number)((e.loaded / e.total * 100).toFixed(2))
}
onProgress(e)
},
cancelToken: source.token
})
//eleajaxabort
promise.abort = source.cancel
return promise
}
},
created() {
this.activateTooltip = debounce(tooltip => tooltip.handleShowPopper(), 200)
}
}
</script>
<style lang="scss" src="./style.scss"></style>

@ -1,34 +0,0 @@
@import "~@/asset/style/variables.scss";
.disabled .el-upload--picture-card {
display: none;
}
.el-upload-list--picture-card {
.el-progress {
width: 100%;
height: 100%;
.el-progress-circle {
height: 100% !important;
width: 100% !important;
}
}
.progress-mask {
position: absolute;
top: 0;
height: 100%;
width: 100%;
background-color: #ffffff;
opacity: 0.9;
.el-progress__text {
color: $--color-primary;
}
}
}
.upload-tooltip {
max-width: 146px;
}

@ -1,5 +1,5 @@
<script type="text/jsx">
import cssVariables from '@/asset/style/variables.scss'
import cssVar from '@/asset/style/var.scss'
import LoadingMask from './LoadingMask'
export default {
@ -25,7 +25,7 @@ export default {
v-drag-dialog
visible={value}
custom-class="abstract-dialog"
top={cssVariables.dialogTop}
top={cssVar.dialogTop}
width={width}
on-close={onClose}
{...data}

@ -6,11 +6,14 @@ export default {
mixins: [common],
props: ['img'],
props: {
img: String,
label: String
},
render() {
return (
<div class="img-checkbox" on-click={this.click}>
<div class="img-checkbox" title={this.label} on-click={this.click}>
<img src={this.img}/>
{this.checked && <i class="el-icon-check"/>}
</div>
@ -20,7 +23,7 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.img-checkbox {
width: 55px;

@ -122,7 +122,8 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/mixin.scss";
@import "~@/asset/style/var";
.form-anchor {
position: fixed;
@ -130,8 +131,8 @@ export default {
top: 200px;
min-width: 72px;
border-radius: 4px 0 0 4px;
background: #ffffff;
box-shadow: 0 2px 12px 0 rgba(166, 167, 173, 0.5);
background-color: $--background-color-base;
@include deep-shadow;
z-index: 100;
&-item {
@ -139,7 +140,7 @@ export default {
height: 28px;
line-height: 28px;
text-align: center;
color: #909399;
color: $--color-text-secondary;
padding: 0 10px;
font-size: 12px;
cursor: pointer;
@ -150,7 +151,7 @@ export default {
}
&:not(:last-child) {
border-bottom: 1px solid rgba(166, 167, 173, 0.1);
border-bottom: $--border-base;
}
}
}

@ -1,4 +1,4 @@
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.form-validate-info {
&.el-popper {
@ -27,7 +27,7 @@
&-item {
padding: 8px 16px;
border-top: 1px solid $border-color-light;
border-top: 1px solid $--border-color-base;
cursor: pointer;
&:hover {
@ -43,7 +43,7 @@
&-label {
margin-top: 2px;
color: #909399;
color: $--color-text-secondary;
font-size: 12px;
}
}

@ -86,11 +86,11 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.context-menu {
margin: 0;
background: $menu-background-dark;
background-color: $menu-background-dark;
z-index: 10;
position: fixed;
list-style-type: none;

@ -1,9 +1,9 @@
<script type="text/jsx">
import cssVariables from '@/asset/style/variables.scss'
import cssVar from '@/asset/style/var.scss'
import Item from './item'
import {isEmpty, deepClone, trim} from "@/util"
const inlineIndent = parseFloat(cssVariables.menuPadding)
const inlineIndent = parseFloat(cssVar.menuPadding)
export default {
name: 'NavMenu',

@ -1,4 +1,4 @@
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.el-menu {
//图标的大小

@ -28,7 +28,7 @@
//设置了显示弹出菜单的父级
&.el-menu--popup .popover-menu__title {
color: $menu-text-hover-color-dark;
border-bottom-color: $border-color-dark;
border-bottom-color: $--border-color-dark;
}
}
}

@ -16,7 +16,7 @@
//弹出菜单的父级
&.el-menu--popup .popover-menu__title {
color: $menu-text-hover-color-light;
border-bottom-color: $border-color-light;
border-bottom-color: $--border-color-light;
}
}
}

@ -24,17 +24,7 @@ module.exports = {
mode: 'history',
//是否使用由后端返回的数据作为路由
useBackendDataAsRoute: false,
//路由过渡动画设置,关联transition.css
animate: {
//当未启用多页签时的路由动画
default: 'el-fade-in-linear',
//要访问的tab顺序高于上一个访问的tab时的路由动画
next: 'left-out',
//要访问的tab顺序不高于上一个访问的tab时的路由动画
prev: 'right-out',
}
useBackendDataAsRoute: false
},
//文件配置

@ -44,16 +44,16 @@
#guide-stage {
position: absolute;
background: white;
background-color: white;
transition: all 0.3s;
z-index: 1001 !important;
border-radius: 2px;
/*background: transparent !important;
/*background-color: transparent !important;
outline: 5000px solid rgba(0, 0, 0, 0.75);*/
}
#guide-overlay {
background: black;
background-color: black;
position: fixed;
top: 0;
left: 0;
@ -65,6 +65,6 @@
#guide-highlight-element-cover {
position: absolute;
background: transparent !important;
background-color: transparent !important;
z-index: 1003 !important;
}

@ -1,11 +1,11 @@
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.puzzle-container {
position: absolute;
z-index: 1000;
box-shadow: 0 0 10px #cccccc;
border: 1px solid #cccccc;
background: #ffffff;
background-color: #ffffff;
.puzzle-text-container {
&:hover {

@ -36,7 +36,7 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
//
.collapse .aside-search {
@ -50,7 +50,7 @@ export default {
//
.dark .aside-search .el-input__inner {
border: none;
color: #ffffff;
color: $--color-white;
background-color: lighten($menu-background-dark, 5%);
}
</style>

@ -1,4 +1,5 @@
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
@import "~@/asset/style/mixin.scss";
.aside {
display: flex;

@ -10,15 +10,15 @@
}
.root-sidebar {
border-right-color: $border-color-dark;
border-right-color: $--border-color-dark;
}
.sub-sidebar-title {
color: #ffffff;
color: $--color-white;
}
.hamburger {
border-top-color: $border-color-dark;
border-top-color: $--border-color-dark;
i {
color: $menu-text-color-dark;

@ -1,11 +1,11 @@
.aside.light {
.root-sidebar {
background-color: #ffffff;
border-right-color: $border-color-light;
background-color: $--color-white;
border-right-color: $--border-color-light;
}
.hamburger {
border-top-color: $border-color-light;
border-top-color: $--border-color-light;
i {
color: $menu-text-color-light;

@ -26,7 +26,7 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
$logoSize: 30px;

@ -1,4 +1,4 @@
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.navbar {
display: flex;

@ -5,7 +5,7 @@
color: $navbar-item-color-dark;
&:hover {
background: $navbar-item-hover-color-dark;
background-color: $navbar-item-hover-color-dark;
}
}
}

@ -1,11 +1,11 @@
.navbar.light {
background-color: #ffffff;
background-color: $--color-white;
.navbar-item {
color: $navbar-item-color-light;
&:hover {
background: $navbar-item-hover-color-light;
background-color: $navbar-item-hover-color-light;
}
}
}

@ -5,9 +5,7 @@ export default {
render() {
return (
<el-backtop target=".page-main .scroll-container" visibility-height={400} bottom={66}>
<div class="back-to-top">
<v-icon icon="el-icon-top"/>
</div>
<v-icon icon="el-icon-top"/>
</el-backtop>
)
}

@ -13,7 +13,7 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.page-footer {
height: $page-footer-height;

@ -32,7 +32,7 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.page-header {
display: flex;
@ -44,12 +44,12 @@ export default {
padding: $page-header-padding $page-view-margin $page-header-padding $page-view-margin;
> .el-breadcrumb .no-redirect {
color: #909399;
color: $--color-text-secondary;
cursor: text;
}
&-title {
color: #303133;
color: $--color-text-primary;
font-weight: 600;
font-size: 18px;
overflow: hidden;

@ -1,4 +1,4 @@
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.tags-view-container {
height: $tags-view-height;
@ -21,10 +21,10 @@
cursor: pointer;
height: #{$tags-view-height - $margin * 2};
line-height: #{$tags-view-height - $margin * 2 - $border-width * 2};
color: #909399;
color: $--color-text-secondary;
background-color: #ffffff;
border-radius: 3px;
border: $border-width solid $border-color-light;
border: $border-width solid $--border-color-base;
padding: 0 12px;
font-size: 12px;
margin: $margin;
@ -54,7 +54,7 @@
color: $--color-primary;
&::before {
background: $--color-primary;
background-color: $--color-primary;
}
}

@ -26,8 +26,8 @@ export default {
},
render() {
const {showIframe, iframeList, currentIframe} = pageGetters
const {cachedViews, transitionName, enabled} = tagsViewGetters
const {transition, showIframe, iframeList, currentIframe} = pageGetters
const {cachedViews, enabled} = tagsViewGetters
return (
<main class="page-main">
@ -38,7 +38,7 @@ export default {
<page-view
include={cachedViews}
transition-name={transitionName}
transition-name={transition.curr}
cacheable={enabled}
/>
@ -64,38 +64,4 @@ export default {
}
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
.page-main {
display: flex;
flex-direction: column;
flex: 1;
position: relative;
overflow: hidden;
background-color: #f0f2f5;
> .scroll-container {
display: flex;
flex: 1;
flex-direction: column;
overflow-y: overlay;
overflow-x: inherit;
> .page-view {
margin: $page-view-margin;
flex: 1;
}
}
.back-to-top {
height: 100%;
width: 100%;
background-color: #f2f5f6;
box-shadow: 0 0 6px rgba(0, 0, 0, .12);
text-align: center;
line-height: 40px;
color: #1989fa;
}
}
</style>
<style lang="scss" src="./style.scss"></style>

@ -0,0 +1,23 @@
@import "~@/asset/style/var";
.page-main {
display: flex;
flex-direction: column;
flex: 1;
position: relative;
overflow: hidden;
background-color: #f0f2f5;
> .scroll-container {
display: flex;
flex: 1;
flex-direction: column;
overflow-y: overlay;
overflow-x: inherit;
> .page-view {
margin: $page-view-margin;
flex: 1;
}
}
}

@ -7,20 +7,13 @@
@close="$emit('input', false)"
>
<div class="drawer-container">
<el-divider>主题色</el-divider>
<div class="drawer-item">
<checkbox-group :value="appGetters.color" @input="changeThemeColor">
<color-checkbox v-for="i in colors" :key="i" :value="i"/>
</checkbox-group>
</div>
<el-divider>导航模式</el-divider>
<div class="drawer-item">
<checkbox-group :value="appGetters.navMode" @input="changeNavMode">
<img-checkbox
v-for="{title, value, img} in navModes"
v-for="{label, value, img} in navModes"
:key="value"
:title="title"
:label="label"
:value="value"
:img="img"
/>
@ -131,8 +124,6 @@
</template>
<script>
import client from 'webpack-theme-color-replacer/client'
import forElementUI from 'webpack-theme-color-replacer/forElementUI'
import CheckboxGroup from "@/component/checkbox/Group"
import ColorCheckbox from "@/component/checkbox/ColorCheckbox"
import ImgCheckbox from "@/component/checkbox/ImgCheckbox"
@ -155,25 +146,24 @@ export default {
return {
appGetters, asideGetters, navbarGetters, pageGetters, tagsViewGetters,
colors: ['#f5222d', '#fa541c', '#fadb14', '#3eaf7c', '#13c2c2', '#1890ff', '#722ed1', '#eb2f96'],
navModes: [
{
title: '侧边栏导航',
label: '侧边栏导航',
value: 'aside',
img: 'https://gw.alipayobjects.com/zos/antfincdn/XwFOFbLkSM/LCkqqYNmvBEbokSDscrm.svg'
},
{
title: '顶部导航',
label: '顶部导航',
value: 'head',
img: 'https://gw.alipayobjects.com/zos/antfincdn/URETY8%24STp/KDNDBbriJhLwuqMoxcAr.svg'
},
{
title: '混合导航',
label: '混合导航',
value: 'mix',
img: 'https://gw.alipayobjects.com/zos/antfincdn/x8Ob%26B8cy8/LCkqqYNmvBEbokSDscrm.svg'
},
{
title: '双层侧边栏导航',
label: '双层侧边栏导航',
value: 'aside-two-part',
img: 'https://gw.alipayobjects.com/zos/antfincdn/XwFOFbLkSM/LCkqqYNmvBEbokSDscrm.svg'
}
@ -181,7 +171,6 @@ export default {
setting: {
app: {
color: '#1890ff',
navMode: 'mix'
},
page: {
@ -238,10 +227,6 @@ export default {
setLocalPersonalSettings(this.setting)
},
changeThemeColor(v) {
client.changer.changeColor({newColors: forElementUI.getElementUISeries(v)})
appMutations.color(v)
},
changeNavMode(v) {
appMutations.navMode(v)
},
@ -315,14 +300,13 @@ export default {
setLocalPersonalSettings(this.setting)
this.syncLayoutStore()
//
this.changeThemeColor(this.setting.app.color)
}
}
</script>
<style lang="scss" scoped>
@import "~@/asset/style/var";
.drawer-container {
padding: 24px;
font-size: 14px;
@ -332,7 +316,7 @@ export default {
.drawer-item {
display: flex;
justify-content: space-between;
color: #606266;
color: $--color-text-regular;
font-size: 14px;
padding: 12px 0;
}

@ -1,7 +1,4 @@
import {route as routeConfig} from '@/config'
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
const {animate} = routeConfig
import {getters as pageGetters, mutations as pageViewMutations} from "@/layout/store/page"
export default {
watch: {
@ -13,7 +10,9 @@ export default {
methods: {
//根据访问的tab页的左右顺序来确定路由动画
decideRouteTransition(to, from) {
let transitionName = animate.prev
const {next, prev} = pageGetters.transition
let transitionName = prev
//这里认为页签数量不会太多,所以为了可读性使用两次循环查找
const fromIndex = this.visitedViews.findIndex(i => i.path === from.path)
@ -21,10 +20,10 @@ export default {
//新开tab也认为顺序高于上一个tab
if (toIndex === -1 || fromIndex < toIndex) {
transitionName = animate.next
transitionName = next
}
tagsViewMutations.transitionName(transitionName)
pageViewMutations.transition({curr: transitionName})
},
}
}

@ -1,11 +1,9 @@
import {isEmpty} from "@/util"
import {isLogin} from "@/util/auth"
export default {
computed: {
showOfflineTip() {
const state = this.$store.state
const isLogin = !isEmpty(state.user.id, state.user.token)
return !state.socket.online && isLogin
return !this.$store.state.websocket.online && isLogin()
},
},

@ -17,8 +17,6 @@ const state = {
//所有的树形菜单,每个元素为顶部菜单,顶部菜单的子级(如果有)为侧边栏菜单
menus: [],
//主题色
color: '#1890ff',
//导航模式,'aside'、'aside-two-part'、'head'、'mix'
navMode: 'mix'
}

@ -6,6 +6,18 @@ import {bindThis} from "@/util"
import {createGetters, createMutations} from "@/util/observable"
const state = {
//路由过渡动画
transition: {
//当未启用多页签时的路由动画
default: 'el-fade-in-linear',
//要访问的tab顺序高于上一个访问的tab时的路由动画
next: 'left-out',
//要访问的tab顺序不高于上一个访问的tab时的路由动画
prev: 'right-out',
//当前使用的路由动画
curr: 'el-fade-in-linear'
},
/*iframe的控制*/
showIframe: false,
currentIframe: '',
@ -28,6 +40,11 @@ export const getters = createGetters(store)
export const mutations = bindThis({
...createMutations(store),
//修改transition时使用Object.assign
transition(obj) {
Object.assign(store.transition, obj)
},
addIframe(src) {
!store.iframeList.includes(src) && store.iframeList.push(src)
},

@ -2,8 +2,7 @@
* 多页签的响应式数据
*/
import Vue from 'vue'
import {route as routeConfig} from "@/config"
import {mutations as pageMutations} from "@/layout/store/page"
import {getters as pageGetters, mutations as pageMutations} from "@/layout/store/page"
import {getRouterViewCacheKey} from "@/layout/util"
import {bindThis} from "@/util"
import {createGetters, createMutations} from "@/util/observable"
@ -20,10 +19,7 @@ const state = {
visitedViews: [],
//缓存的页签,用于<keep-router-view-alive/>:include
cachedViews: [],
//路由过渡动画名称
transitionName: routeConfig.animate.default
cachedViews: []
}
const store = Vue.observable(state)
@ -42,7 +38,7 @@ export const mutations = bindThis({
store.enabled = v
if (!v) {
this.transitionName(routeConfig.animate.default)
pageMutations.transition({curr: pageGetters.transition.default})
this.delAllTagAndCache()
}
},

@ -24,5 +24,5 @@ new Vue({
render: h => h(App)
})
//页面刷新时socket重连
store.dispatch('socket/init').catch()
//页面刷新时websocket重连
store.dispatch('websocket/init').catch()

@ -6,7 +6,7 @@ import CollapseCard from '@/component/CollapseCard'
import DetailPage from "@/view/_common/DetailPage"
import DocHistory from '@/component/biz/doc/DocHistory'
import DocSteps from '@/component/biz/doc/DocSteps'
import UploadFile from '@/component/UploadFile'
import QiniuUpload from '@/component/Upload/Qiniu'
import {isEmpty, mergeObj} from '@/util'
import {auth} from "@/util/auth"
import {deleteUpload} from "@/api/file"
@ -22,7 +22,7 @@ export default {
DetailPage,
DocHistory,
DocSteps,
UploadFile
QiniuUpload
},
props: {
@ -268,7 +268,7 @@ export default {
url: res.key,
name: file.name,
sort: this.attachmentSortSeed,
size: file.size
size: file.raw.size
})
this.attachmentSortSeed++
},

@ -496,7 +496,7 @@ const WebGLFluid = canvas => {
let captureCanvas = textureToCanvas(texture, target.width, target.height)
let dataUri = captureCanvas.toDataURL()
downloadURI('fluid.png', dataUri)
URL.revokeObjectURL(dataUri)
window.URL.revokeObjectURL(dataUri)
}
//startGUI();

@ -542,6 +542,6 @@ export default class WebGlFluid {
const dataUri = textureToCanvas(texture, target.width, target.height).toDataURL()
downloadURI('fluid.png', dataUri)
URL.revokeObjectURL(dataUri)
window.URL.revokeObjectURL(dataUri)
}
}

@ -1,6 +1,5 @@
import store from "@/store"
import {isEmpty} from "@/util"
import {auth, needAuth} from "@/util/auth"
import {isLogin, auth, needAuth} from "@/util/auth"
const noLoginList = ['/login', '/register', '/403', '/404', '/500']
@ -11,7 +10,7 @@ const beforeEach = async (to, from, next) => {
}
//未登录时返回登录页
if (isEmpty(store.state.user.id, store.state.user.token)) {
if (!isLogin()) {
return next({path: '/login', query: {redirect: to.fullPath}})
}

@ -1,7 +0,0 @@
import component from '@/view/dev/uploadFileDev'
export default {
path: 'uploadFile',
component,
meta: {title: '上传'}
}

@ -1,11 +1,8 @@
/*顶级菜单:开发用的页面*/
import {context2array} from '@/router/util'
const files = require.context('./child', false, /\.js$/)
import component from "@/view/dev"
export default {
path: '/dev',
component: {template: `<div><div>开发专用页面</div><router-view/></div>`},
meta: {title: '开发专用页面', icon: 'svg-develop', noAuth: true, noCache: true, alwaysShow: true},
children: context2array(files)
component,
meta: {title: '开发专用页面', icon: 'svg-develop', noAuth: true, noCache: true, alwaysShow: true}
}

@ -6,12 +6,12 @@ import {getDynamicRoutes} from '@/router/define'
import {str2routeConfig, metaExtend} from "@/router/util"
import {getAll} from "@/api/system/resource"
import {isEmpty, deepClone} from "@/util"
import {needAuth, auth} from "@/util/auth" // 此处存在循环引用?
import {needAuth, auth} from "@/util/auth" //此处存在循环引用?
import {createTree, shakeTree} from "@/util/tree"
import {isExternal} from "@/util/validate"
const state = {
//权限映射表:<权限路径,权限id>,用于util.auth
//权限映射表:<权限路径,权限id>,所有的权限数据,用于util.auth
resourceMap: {},
//权限树(当用户非admin时过滤了admin专属权限),用于菜单管理、角色管理中的权限选择
resourceTree: [],
@ -21,13 +21,13 @@ const state = {
}
const mutations = {
resource(state, {data, admin}) {
resource(state, {data, predicate}) {
data = deepClone(data)
state.resourceMap = generateResourceMap(data)
//如果不是admin用户,那么过滤admin类型的权限
if (!admin) data = data.filter(i => !i.admin)
//如果传入了过滤方法,那么过滤权限树
if (predicate) data = data.filter(predicate)
//解析meta,这里不处理meta.dynamicTitle
data.forEach(i => {
@ -44,20 +44,30 @@ const mutations = {
}
const actions = {
init({state, commit}, {admin}) {
init({state, commit, rootState}) {
const user = rootState.user
return getAll
.request()
.then(({data}) => {
commit('resource', {data: data || [], admin})
//如果不是admin用户,那么过滤admin类型的权限
const predicate = user.admin ? undefined : i => !i.admin
commit('resource', {data: data || [], predicate})
//未开启后端动态路由功能时,使用前端预设的静态路由
const routes =
routeConfig.useBackendDataAsRoute
? transformOriginRouteData(data)
: getDynamicRoutes()
const routes = transformOriginRouteData(data)
metaExtend(routes)
//动态添加路由,这里不需要进行权限过滤
//可能存在多次调用的情况,所以仅在第一次调用时添加进vue-router
!state.init && addDynamicRoutes(routes)
//生成经过权限过滤后的菜单
//生成经过权限过滤后的菜单,并传递给layout的store
appMutations.menus(getAuthorizedMenus(routes))
//设置初始化完成的标志
@ -70,9 +80,6 @@ const actions = {
function transformOriginRouteData(data) {
if (!Array.isArray(data)) return []
//未开启后端动态路由功能时,返回前端预设的静态路由
if (!routeConfig.useBackendDataAsRoute) return getDynamicRoutes()
const treeArray = deepClone(data).filter(i => {
//过滤掉数据接口或者未启用的项
if (i.type === 3 || !i.enable) {
@ -93,54 +100,56 @@ function transformOriginRouteData(data) {
//获取经过权限控制后的菜单
function getAuthorizedMenus(menus) {
menus = deepClone(menus)
clean(menus)
addFullPath(menus)
return shakeTree(menus, i => {
//非叶子节点时,当定义了children属性却没有子节点时移除
if (i.children && i.children.length === 0) {
return false
}
return !needAuth(i) || auth(i.fullPath)
})
}
//删除不显示的菜单(没有children且没有meta.title)
function clean(menus) {
for (let i = menus.length - 1; i >= 0; i--) {
const {children = [], meta: {title, alwaysShow, hidden} = {}} = menus[i]
//删除不显示的菜单(没有children且没有meta.title)
function clean(menus) {
for (let i = menus.length - 1; i >= 0; i--) {
const {children = [], meta: {title, alwaysShow, hidden} = {}} = menus[i]
if (hidden) {
menus.splice(i, 1)
continue
}
if (hidden) {
menus.splice(i, 1)
continue
}
if (children.length === 0) {
if (isEmpty(title)) {
menus.splice(i, 1)
}
continue
}
if (children.length === 0) {
if (isEmpty(title)) {
clean(children)
if (children.length === 0 && !alwaysShow) {
menus.splice(i, 1)
}
continue
//移除类似首页那样的无标题父级节点
if (isEmpty(title)) {
menus.splice(i, 1, ...children)
}
}
}
clean(children)
//菜单添加全路径
function addFullPath(routes, basePath = '/') {
routes.forEach(route => {
//外链保持原样
route.fullPath = isExternal(route.path) ? route.path : path.resolve(basePath, route.path)
route.children && addFullPath(route.children, route.fullPath)
})
}
if (children.length === 0 && !alwaysShow) {
menus.splice(i, 1)
}
clean(menus)
addFullPath(menus)
//移除类似首页那样的无标题父级节点
if (isEmpty(title)) {
menus.splice(i, 1, ...children)
return shakeTree(menus, i => {
//非叶子节点时,当定义了children属性却没有子节点时移除
if (i.children && i.children.length === 0) {
return false
}
}
}
//菜单添加全路径
function addFullPath(routes, basePath = '/') {
routes.forEach(route => {
//外链保持原样
route.fullPath = isExternal(route.path) ? route.path : path.resolve(basePath, route.path)
route.children && addFullPath(route.children, route.fullPath)
return !needAuth(i) || auth(i.fullPath)
})
}

@ -1,6 +1,6 @@
import {createMutations} from "@/store/util"
import {emptyOrDefault} from "@/util"
import {autoCompleteUrl} from "@/util/file"
import {autoCompleteUrl} from "@/util/file" //此处存在循环引用?
import {getUser, setUser} from "@/util/storage"
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
import {login, logout} from '@/api/account'
@ -33,35 +33,32 @@ const actions = {
.then(({data: user}) => {
if (user.admin === true) user.roleName = '超级管理员'
user.avatar = autoCompleteUrl(user.avatar)
commit('$all', user)
setUser(user)
return dispatch('socket/init', null, {root: true})
return dispatch('websocket/init', undefined, {root: true})
})
},
logout({commit, state, dispatch}) {
return new Promise((resolve, reject) => {
if (state.prepareLogout) return Promise.reject()
commit('prepareLogout', true)
logout
.request(state.token)
.then(() => {
commit('resource/init', false, {root: true})
return Promise.all([
dispatch('socket/close', null, {root: true}),
dispatch('removeUser'),
tagsViewMutations.delAllTagAndCache()
])
})
.then(() => {
resolve()
window.location.reload()
})
.catch(error => reject(error))
.finally(() => commit('prepareLogout', false))
})
if (state.prepareLogout) return Promise.reject()
commit('prepareLogout', true)
return logout
.request(state.token)
.then(() => Promise.all([
dispatch('websocket/close', null, {root: true}),
dispatch('removeUser'),
//因为可能开启了页签的持久化功能,所以退出登陆时通过清空store来清空持久化数据
tagsViewMutations.delAllTagAndCache()
]))
.then(() => window.location.reload())
.finally(() => commit('prepareLogout', false))
},
//刷新本地存储中保存的用户数据
refresh({state}) {
setUser(state)
},

@ -4,7 +4,7 @@ import {useMock, socketUrl} from '@/config'
import SocketIO from 'socket.io-client'
import {createMutations} from "@/store/util"
let socket
let websocket
const state = {
online: true
@ -18,14 +18,14 @@ const actions = {
if (useMock || isEmpty(id, token)) return
socket = initSocket({id, token})
websocket = initSocket({id, token})
bindDefaultEvent(socket, context)
bindCustomEvent(socket, context)
bindDefaultEvent(websocket, context)
bindCustomEvent(websocket, context)
},
close() {
socket && socket.close()
websocket && websocket.close()
}
}

@ -53,3 +53,12 @@ export function wic(apiMap) {
return attrs
}
/**
* 判断是否已登录已登录则返回true
*
* @return {boolean}
*/
export function isLogin() {
return !isEmpty(store.state.user.token)
}

@ -1,6 +1,6 @@
import cssVariables from '@/asset/style/variables.scss'
import cssVar from '@/asset/style/var.scss'
const maxMobileWidth = parseFloat(cssVariables.maxMobileWidth)
const maxMobileWidth = parseFloat(cssVar.maxMobileWidth)
/**
* 根据body宽度判断是否为移动端是则返回true

@ -1,3 +1,4 @@
import request from "@/api/request"
import {isEmpty} from "@/util"
import {elError} from "@/util/message"

@ -1,13 +1,15 @@
import {saveAs} from 'file-saver'
import request from "@/api/request"
import {getToken} from "@/api/file"
import {file as fileConfig} from '@/config'
import store from '@/store'
import {isEmpty, timeFormat} from "@/util"
import {isTxt} from "@/util/validate"
import {isExternal, isTxt} from "@/util/validate"
const defaultOptions = {
headers: {"Content-Type": "multipart/form-data"},
generateKey(filename) {
return timeFormat('yyyy/MM/dd/') + Date.now() + '/' + filename
return `${timeFormat('yyyy/MM/dd')}/user${store.state.user.id}/${Date.now()}/${filename}`
},
onUploadProgress(e) {
@ -20,8 +22,9 @@ export function preview(url) {
const connectChar = url.includes('?') ? '&' : '?'
url = url + connectChar + 'fullfilename=' + url.replace(fileConfig.storePrefix, '')
const anchor = document.createElement('a')
anchor.style.opacity = '0'
anchor.style.display = 'none'
anchor.href = `${fileConfig.previewPrefix}/onlinePreview?url=${encodeURIComponent(url)}`
anchor.target = '_blank'
document.body.appendChild(anchor)
@ -31,17 +34,7 @@ export function preview(url) {
//下载文件
export function download(url, name) {
const href = typeof url === 'object' ? window.URL.createObjectURL(url) : url
const anchor = document.createElement('a')
anchor.style.opacity = '0'
anchor.href = href
anchor.download = name
document.body.appendChild(anchor)
anchor.click()
document.body.removeChild(anchor)
if (typeof url === 'object') window.URL.revokeObjectURL(url)
saveAs(url, name)
}
//七牛云直传
@ -63,6 +56,6 @@ export function upload(blob, filename, options = {}) {
//自动补全附件链接前缀
export function autoCompleteUrl(url) {
if (isEmpty(url)) return ''
if (url.startsWith('http')) return url
if (isExternal(url)) return url
return fileConfig.storePrefix + url
}

@ -1,17 +1,8 @@
@import "~@/asset/style/variables.scss";
$bg: #283443;
$light_gray: #ffffff;
$dark_gray: #889aa4;
$cursor: #ffffff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
.login-container .el-input input {
color: $cursor;
}
}
@import "~@/asset/style/var";
.login-page {
$background-color: #2d3a4b;
display: flex;
flex-direction: column;
height: 100%;
@ -22,7 +13,7 @@ $cursor: #ffffff;
height: 100%;
width: 100%;
z-index: -1;
background: #2d3a4b;
background-color: $background-color;
}
.login-container {
@ -38,12 +29,11 @@ $cursor: #ffffff;
margin-bottom: 40px;
cursor: pointer;
font-size: 26px;
color: #eee;
color: #ffffff;
font-weight: bold;
position: relative;
.set-animation {
color: #ffffff;
position: absolute;
top: 115px;
font-size: 18px;
@ -54,7 +44,7 @@ $cursor: #ffffff;
.svg-container {
padding: 6px 5px 6px 0;
color: $dark_gray;
color: $--color-text-secondary;
vertical-align: middle;
display: inline-block;
}
@ -64,7 +54,7 @@ $cursor: #ffffff;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
color: $--color-text-secondary;
cursor: pointer;
user-select: none;
}
@ -75,26 +65,20 @@ $cursor: #ffffff;
width: 85%;
input {
background: transparent;
background-color: transparent;
border: 0;
-webkit-appearance: none;
border-radius: 0;
padding: 12px 5px 12px 15px;
color: $light_gray;
color: #ffffff;
height: 47px;
caret-color: $cursor;
&:-webkit-autofill {
box-shadow: 0 0 0 1000px $bg inset !important;
-webkit-text-fill-color: $cursor !important;
}
}
}
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 5px;
background: rgba(0, 0, 0, 0.1);
background-color: rgba(0, 0, 0, 0.1);
margin-bottom: 22px;
}

@ -1,4 +1,5 @@
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
@import "~@/asset/style/mixin.scss";
.detail-page {
&.el-loading-parent--relative {
@ -8,8 +9,8 @@
&-header {
margin: -#{$page-view-margin} -#{$page-view-margin} 0 -#{$page-view-margin};
padding: 16px 32px 0 32px;
background: #ffffff;
border-bottom: 1px solid $border-color-light;
background-color: $--color-white;
border-bottom: $--border-base;
> .el-page-header {
padding: 0 0 24px 0;
@ -42,15 +43,15 @@
min-width: 242px;
margin-left: 88px;
@media (max-width: 1200px) {
@media (max-width: $--lg) {
margin-left: 44px;
}
@media (max-width: 992px) {
@media (max-width: $--md) {
margin-left: 20px;
}
@media (max-width: 768px) {
@media (max-width: $--sm) {
margin-left: 0;
}
@ -62,13 +63,13 @@
&-title {
margin-bottom: 4px;
color: rgba(0, 0, 0, .45);
color: $--color-text-secondary;
font-size: 14px;
}
&-content {
margin-top: 12px;
color: rgba(0, 0, 0, .85);
color: $--color-text-primary;
font-size: 24px;
}
}
@ -80,7 +81,7 @@
}
&-label {
color: rgba(0, 0, 0, .85);
color: $--color-text-primary;
font-weight: 400;
font-size: 14px;
line-height: 1.5715;
@ -88,7 +89,7 @@
}
&-content {
color: rgba(0, 0, 0, .65);
color: $--color-text-regular;
font-size: 14px;
line-height: 1.5715;
}
@ -110,7 +111,7 @@
height: 56px;
padding: 0 24px;
line-height: 56px;
border-top: 1px solid #f0f0f0;
border-top: $--border-base;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, .12), 0 6px 16px 0 rgba(0, 0, 0, .08), 0 9px 28px 8px rgba(0, 0, 0, .05);
backdrop-filter: blur(3px);

@ -1,4 +1,5 @@
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
@import "~@/asset/style/mixin.scss";
.exception {
width: 100%;
@ -16,7 +17,7 @@
.content {
h1 {
color: #434e59;
color: $--color-text-primary;
font-size: 72px;
font-weight: 600;
line-height: 72px;
@ -24,7 +25,7 @@
}
.desc {
color: rgba(0, 0, 0, .45);
color: $--color-text-secondary;
font-size: 20px;
line-height: 28px;
margin-bottom: 16px;

@ -3,6 +3,7 @@
* 监听el-table动态设置max-height属性
*/
import {debounce} from "@/util"
import {findComponentByTag} from "@/util/vue"
export default {
@ -19,7 +20,7 @@ export default {
},
methods: {
//设置表格距离卡片容器底部的理想距离(分页高度 + el-card的1px边距 + el-card padding + 页面margin
//设置表格距离卡片容器底部的理想距离(分页高度 + el-card的1px边距 + el-card padding)
calcExpectedDistance() {
const paginationHeight = this.$el.querySelector('.table-container > .el-pagination') ? 50 : 0
this.expectedDistance = paginationHeight + 1 + 20
@ -51,24 +52,21 @@ export default {
this.tableMaxHeight = tableRect.height - overHeight
}
},
createResizeObserver() {
if (this.resizeObserver) return
this.resizeObserver = new ResizeObserver(this.resize)
this.resizeObserver.observe(this.$el)
const searchFormInstance = findComponentByTag(this, 'search-form')
if (searchFormInstance) {
this.resizeObserver.observe(searchFormInstance.$el)
}
this.$once('hook:beforeDestroy', () => {
this.resizeObserver && this.resizeObserver.disconnect()
})
},
},
mounted() {
this.createResizeObserver()
//resize必须异步,否则新版chrome(87.0.4280.88)会出问题
this.resize = debounce(this.resize, 0)
this.resizeObserver = new ResizeObserver(this.resize)
this.resizeObserver.observe(this.$el)
const searchFormInstance = findComponentByTag(this, 'search-form')
if (searchFormInstance) {
this.resizeObserver.observe(searchFormInstance.$el)
}
this.$once('hook:beforeDestroy', () => {
this.resizeObserver && this.resizeObserver.disconnect()
})
}
}

@ -1,4 +1,4 @@
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.skeleton-page-wrapper {
margin: $page-view-margin;

@ -31,7 +31,8 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
@import "~@/asset/style/mixin.scss";
.card-panel {
height: 108px;
@ -40,7 +41,7 @@ export default {
position: relative;
overflow: hidden;
color: #666;
background: #ffffff;
background-color: #ffffff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
border-color: rgba(0, 0, 0, .05);

@ -8,7 +8,7 @@
<script>
import PanelGroupItem from './PanelGroupItem'
import cssVariables from '@/asset/style/variables.scss'
import cssVar from '@/asset/style/var.scss'
import {getFourBlock} from '@/api/statistic'
import {isEmpty} from "@/util"
import {auth} from "@/util/auth"
@ -26,7 +26,7 @@ export default {
id: 'online',
path: '/system/user',
icon: 'svg-user',
color: cssVariables.info,
color: cssVar.info,
value: 0,
text: '在线用户'
},
@ -34,7 +34,7 @@ export default {
id: 'purchase',
path: '/purchase/order',
icon: 'svg-shopping',
color: cssVariables.primary,
color: cssVar.primary,
value: 0,
text: '今日采购额'
},
@ -42,14 +42,14 @@ export default {
id: 'sell',
path: '/sell/order',
icon: 'svg-sell',
color: cssVariables.warning,
color: cssVar.warning,
value: 0,
text: '今日销售额'
},
{
id: 'profit',
icon: 'svg-money',
color: cssVariables.success,
color: cssVar.success,
value: 0,
text: '今日毛利润'
},

@ -153,7 +153,7 @@ export default {
cursor: pointer;
position: relative;
color: #666;
background: #FFF9EA;
background-color: #FFF9EA;
& + & {
margin-top: 20px;
@ -161,7 +161,7 @@ export default {
&.viewed {
cursor: auto;
background: transparent;
background-color: transparent;
&::before {
visibility: hidden;

@ -43,7 +43,7 @@ export default {
text-align: center;
&.is-active {
background: #f0faff;
background-color: #f0faff;
}
}

@ -44,7 +44,7 @@
</collapse-card>
<collapse-card ref="attachment" header="附件">
<upload-file
<qiniu-upload
:file-list="form.imageList"
:disabled="!canSave"
@remove="removeUpload"

@ -90,7 +90,7 @@
</collapse-card>
<collapse-card ref="attachment" header="附件">
<upload-file
<qiniu-upload
:file-list="form.imageList"
:disabled="!canSave"
@remove="removeUpload"

@ -73,7 +73,7 @@
</collapse-card>
<collapse-card ref="attachment" header="附件">
<upload-file
<qiniu-upload
:file-list="form.imageList"
:disabled="!canSave"
@remove="removeUpload"

@ -59,7 +59,7 @@
</collapse-card>
<collapse-card ref="attachment" header="附件">
<upload-file
<qiniu-upload
:file-list="form.imageList"
:disabled="!canSave"
@remove="removeUpload"

@ -320,7 +320,7 @@ export default {
init() {
this.curNode = null
this.clearForm()
return this.$store.dispatch('resource/init', this.$store.state.user)
return this.$store.dispatch('resource/init')
},
clearForm() {
resetObj(this.form)

@ -53,7 +53,7 @@ export default {
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.user-card {
.avatar-container {
@ -81,7 +81,7 @@ export default {
list-style: none;
li {
border-bottom: 1px solid #EBEEF5;
border-bottom: 1px solid $--border-color-lighter;
padding: 11px 0;
font-size: 13px;
}

@ -4,6 +4,6 @@
<script>
export default {
name: "uploadFileDev"
name: "dev"
}
</script>

@ -40,14 +40,14 @@ export default {
</script>
<style lang="scss" scoped>
@import "~@/asset/style/variables.scss";
@import "~@/asset/style/var";
.q-skeleton + .q-skeleton {
margin-top: 20px;
}
.bg-primary {
background: $--color-primary;
background-color: $--color-primary;
}
.bg-info {

@ -1,22 +1,25 @@
<template>
<div>
<div class="tip-row">
根据业务需求简单封装了upload集成自带预览组件<br/>
根据业务需求简单封装了upload<br/>
图片使用<a href="https://github.com/sachinchoolur/lightgallery.js" target="_blank">lightgallery</a>预览
非图片类型的文件使用<a href="https://github.com/kekingcn/kkFileView" target="_blank">kkFileView</a>预览
</div>
<upload-file :file-list="fileList"/>
<qiniu-upload :file-list="fileList"/>
<div class="tip-row">原始使用axios的上传</div>
<input @change="change" type="file">
</div>
</template>
<script>
import UploadFile from '@/component/UploadFile'
import QiniuUpload from "@/component/Upload/Qiniu"
import request from "@/api/request"
export default {
name: "uploadExample",
components: {UploadFile},
components: {QiniuUpload},
data() {
return {
fileList: [
@ -35,6 +38,7 @@ export default {
]
}
},
methods: {
change(e) {
let file = e.target.files[0]
@ -51,7 +55,8 @@ export default {
//RequestParam
param.append('extraParam1', "额外参数1")
param.append('extraParam2', "额外参数2")
request.post('/test/upload', param, {headers: {"Content-Type": "multipart/form-data"}})
request
.post('/test/upload', param, {headers: {"Content-Type": "multipart/form-data"}})
.then(({data}) => alert(data))
}
}

@ -174,6 +174,8 @@ export default {
</script>
<style lang="scss" scoped>
@import "~@/asset/style/var";
.container {
max-width: 1200px;
text-align: center;
@ -197,7 +199,7 @@ export default {
text-align: center;
p {
color: #909399;
color: $--color-text-regular;
margin-top: 0;
margin-bottom: 1rem;
}

@ -3,8 +3,6 @@ const path = require('path')
const settings = require('./src/config')
const CompressionWebpackPlugin = require('compression-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const ThemeColorReplacer = require('webpack-theme-color-replacer')
const forElementUI = require('webpack-theme-color-replacer/forElementUI')
const EMPTY_PLUGIN = {apply: () => ({})}
@ -56,12 +54,6 @@ module.exports = {
}
},
plugins: [
new ThemeColorReplacer({
fileName: 'css/theme-colors.[contenthash:8].css',
matchColors: forElementUI.getElementUISeries('#1890ff'),
changeSelector: forElementUI.changeSelector,
injectCss: true
}),
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',

Loading…
Cancel
Save