'admin-21.03.07:新增图片裁剪、富文本、城市多级联动,修复错误界面图片404问题等'

This commit is contained in:
lyt-Top 2021-03-07 19:24:03 +08:00
parent 970ce01765
commit 611bb33831
21 changed files with 460 additions and 8 deletions

View File

@ -113,6 +113,8 @@ cnpm run build
- <a href="https://github.com/sass/sass" target="_blank">sass</a> - <a href="https://github.com/sass/sass" target="_blank">sass</a>
- <a href="https://github.com/microsoft/TypeScript" target="_blank">typescript</a> - <a href="https://github.com/microsoft/TypeScript" target="_blank">typescript</a>
- <a href="https://github.com/vitejs/vite" target="_blank">vite</a> - <a href="https://github.com/vitejs/vite" target="_blank">vite</a>
- <a href="https://github.com/wangeditor-team/wangEditor" target="_blank">wangeditor</a>
- <a href="https://github.com/fengyuanchen/cropperjs" target="_blank">cropperjs</a>
#### 特别感谢 #### 特别感谢

View File

@ -9,6 +9,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"clipboard": "^2.0.6", "clipboard": "^2.0.6",
"countup.js": "^2.0.7", "countup.js": "^2.0.7",
"cropperjs": "^1.5.11",
"echarts": "^5.0.2", "echarts": "^5.0.2",
"echarts-wordcloud": "^2.0.0", "echarts-wordcloud": "^2.0.0",
"element-plus": "^1.0.2-beta.33", "element-plus": "^1.0.2-beta.33",
@ -18,7 +19,8 @@
"sortablejs": "^1.13.0", "sortablejs": "^1.13.0",
"vue": "^3.0.5", "vue": "^3.0.5",
"vue-router": "^4.0.2", "vue-router": "^4.0.2",
"vuex": "^4.0.0-rc.2" "vuex": "^4.0.0-rc.2",
"wangeditor": "^4.6.8"
}, },
"devDependencies": { "devDependencies": {
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",

View File

@ -0,0 +1,148 @@
<template>
<div>
<el-dialog title="更换头像" v-model="isShowDialog" width="769px">
<div class="cropper-warp">
<div class="cropper-warp-left">
<img :src="cropperImg" class="cropper-warp-left-img" />
</div>
<div class="cropper-warp-right">
<div class="cropper-warp-right-title">预览</div>
<div class="cropper-warp-right-item">
<div class="cropper-warp-right-value">
<img :src="cropperImgBase64" class="cropper-warp-right-value-img" />
</div>
<div class="cropper-warp-right-label">100 x 100</div>
</div>
<div class="cropper-warp-right-item">
<div class="cropper-warp-right-value">
<img :src="cropperImgBase64" class="cropper-warp-right-value-img cropper-size" />
</div>
<div class="cropper-warp-right-label">50 x 50</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, nextTick } from "vue";
import Cropper from "cropperjs";
import "cropperjs/dist/cropper.css";
export default {
name: "cropperIndex",
setup() {
const state = reactive({
isShowDialog: false,
cropperImg: "",
cropperImgBase64: "",
});
//
const openDialog = (imgs) => {
state.cropperImg = imgs;
state.isShowDialog = true;
nextTick(() => {
initCropper();
});
};
//
const closeDialog = (row) => {
state.isShowDialog = false;
};
//
const onCancel = () => {
closeDialog();
};
//
const onSubmit = () => {};
// cropperjs
const initCropper = () => {
const cropper = new Cropper(
document.querySelector(".cropper-warp-left-img"),
{
viewMode: 1,
dragMode: "none",
initialAspectRatio: 1,
aspectRatio: 1,
preview: ".before",
background: false,
autoCropArea: 0.6,
zoomOnWheel: false,
crop: (e) => {
state.cropperImgBase64 = cropper
.getCroppedCanvas()
.toDataURL("image/jpeg");
},
}
);
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
initCropper,
...toRefs(state),
};
},
};
</script>
<style scoped lang="scss">
.cropper-warp {
display: flex;
.cropper-warp-left {
position: relative;
display: inline-block;
height: 350px;
flex: 1;
border: 1px solid #ebeef5;
background: #fff;
overflow: hidden;
background-repeat: no-repeat;
cursor: move;
border-radius: 3px;
.cropper-warp-left-img {
width: 100%;
height: 100%;
}
}
.cropper-warp-right {
width: 150px;
height: 350px;
.cropper-warp-right-title {
text-align: center;
height: 20px;
line-height: 20px;
}
.cropper-warp-right-item {
margin: 15px 0;
.cropper-warp-right-value {
.cropper-warp-right-value-img {
width: 100px;
height: 100px;
border-radius: 100%;
margin: auto;
}
.cropper-size {
width: 50px;
height: 50px;
}
}
.cropper-warp-right-label {
text-align: center;
font-size: 12px;
color: #666666;
height: 30px;
line-height: 30px;
}
}
}
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -376,6 +376,36 @@ export const dynamicRoutes = [
auth: ['admin', 'test'], auth: ['admin', 'test'],
icon: 'iconfont icon-xuanzeqi' icon: 'iconfont icon-xuanzeqi'
} }
},
{
path: '/fun/wangEditor',
name: 'wangEditor',
component: () => import('/@/views/fun/wangEditor/index.vue'),
meta: {
title: 'wangEditor 编辑器',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: false,
isIframe: false,
auth: ['admin', 'test'],
icon: 'iconfont icon-fuwenbenkuang'
}
},
{
path: '/fun/cropper',
name: 'cropper',
component: () => import('/@/views/fun/cropper/index.vue'),
meta: {
title: 'cropper 图片裁剪',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: false,
isIframe: false,
auth: ['admin', 'test'],
icon: 'iconfont icon-caijian'
}
} }
] ]
}, },
@ -455,6 +485,21 @@ export const dynamicRoutes = [
icon: 'el-icon-set-up' icon: 'el-icon-set-up'
} }
}, },
{
path: '/pages/cityLinkage',
name: 'cityLinkage',
component: () => import('/@/views/pages/cityLinkage/index.vue'),
meta: {
title: '城市多级联动',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: false,
isIframe: false,
auth: ['admin', 'test'],
icon: 'iconfont icon-jiliandongxuanzeqi'
}
},
] ]
}, },
{ {

View File

@ -22,6 +22,7 @@ body,
background-color: #f8f8f8; background-color: #f8f8f8;
font-size: 14px; font-size: 14px;
overflow: hidden; overflow: hidden;
position: relative;
} }
/* 主布局样式 /* 主布局样式
@ -43,13 +44,14 @@ body,
} }
} }
.layout-header { .layout-header {
box-shadow: 0 1px 4px rgb(0 21 41 / 4%);
padding: 0 !important; padding: 0 !important;
} }
.layout-main { .layout-main {
padding: 0 !important; padding: 0 !important;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
background-color: #f8f8f8;
border-top: 1px solid #f1f2f3;
} }
.el-scrollbar { .el-scrollbar {
width: 100%; width: 100%;

View File

@ -949,6 +949,9 @@
.el-select-dropdown .el-scrollbar__wrap { .el-select-dropdown .el-scrollbar__wrap {
overflow-x: scroll !important; overflow-x: scroll !important;
} }
.el-select-dropdown__wrap {
max-height: 274px !important; /*修复Select 选择器高度问题*/
}
/* Drawer 抽屉 /* Drawer 抽屉
------------------------------- */ ------------------------------- */

View File

@ -1,5 +1,6 @@
@import './app.scss'; @import './app.scss';
@import './base.scss'; @import './base.scss';
@import './other.scss';
@import './element.scss'; @import './element.scss';
@import './iconSelector.scss'; @import './iconSelector.scss';
@import './media/media.scss'; @import './media/media.scss';

View File

@ -0,0 +1,10 @@
@import './index.scss';
/* 页面宽度小于576px
------------------------------- */
@media screen and (max-width: $xs) {
.el-cascader__dropdown.el-popper {
overflow: auto;
max-width: 100%;
}
}

View File

@ -9,3 +9,4 @@
@import './scrollbar.scss'; @import './scrollbar.scss';
@import './pagination.scss'; @import './pagination.scss';
@import './dialog.scss'; @import './dialog.scss';
@import './cityLinkage.scss';

16
src/theme/other.scss Normal file
View File

@ -0,0 +1,16 @@
/* wangeditor富文本编辑器
------------------------------- */
.w-e-toolbar {
border: 1px solid #ebeef5 !important;
border-bottom: 1px solid #ebeef5 !important;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
z-index: 2 !important;
}
.w-e-text-container {
border: 1px solid #ebeef5 !important;
border-top: none !important;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
z-index: 1 !important;
}

View File

@ -1,6 +1,6 @@
// 字体图标 url // 字体图标 url
const cssCdnUrlList: Array<string> = [ const cssCdnUrlList: Array<string> = [
'//at.alicdn.com/t/font_2298093_xviws8fx96h.css', '//at.alicdn.com/t/font_2298093_sctkh48ml1.css',
'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css' '//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'
] ]
// 第三方 js url // 第三方 js url

View File

@ -12,7 +12,7 @@
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/images/error/401.png" /> <img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/error/401.png" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/images/error/404.png" /> <img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/error/404.png" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,54 @@
<template>
<div class="croppers-container">
<el-card shadow="hover" header="cropper 图片裁剪">
<el-alert title="感谢优秀的 `cropperjs`项目地址https://github.com/fengyuanchen/cropperjs" type="success"
:closable="false" class="mb15"></el-alert>
<div class="cropper-img-warp">
<div class="mb15 mt15">
<img class="cropper-img" :src="cropperImg">
</div>
<el-button type="primary" icon="el-icon-crop" size="small" @click="onCropperDialogOpen">更换头像</el-button>
</div>
</el-card>
<CropperDialog ref="cropperDialogRef" />
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive } from "vue";
import CropperDialog from "/@/components/cropper/index.vue";
export default {
name: "cropper",
components: { CropperDialog },
setup() {
const cropperDialogRef = ref();
const state = reactive({
cropperImg:
"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg",
});
//
const onCropperDialogOpen = () => {
cropperDialogRef.value.openDialog(state.cropperImg);
};
return {
cropperDialogRef,
onCropperDialogOpen,
...toRefs(state),
};
},
};
</script>
<style scoped lang="scss">
.croppers-container {
.cropper-img-warp {
text-align: center;
.cropper-img {
margin: auto;
width: 150px;
height: 150px;
border-radius: 100%;
}
}
}
</style>

View File

@ -49,6 +49,12 @@
</el-button> </el-button>
</div> </div>
</div> </div>
<div class="flex-warp-item">
<div class="flex-warp-item-box">
<el-button type="success" size="small" icon="el-icon-full-screen" @click="openCurrenFullscreen">当前页全屏
</el-button>
</div>
</div>
</div> </div>
</el-card> </el-card>
</div> </div>
@ -115,6 +121,13 @@ export default {
path: route.path, path: route.path,
}); });
}; };
// 5
const openCurrenFullscreen = () => {
proxy.mittBus.emit("onCurrentContextmenuClick", {
id: 4,
path: route.path,
});
};
// //
const onImplementClick = () => { const onImplementClick = () => {
proxy.mittBus.emit("onCurrentContextmenuClick", { proxy.mittBus.emit("onCurrentContextmenuClick", {
@ -127,6 +140,7 @@ export default {
closeCurrentTagsView, closeCurrentTagsView,
closeOtherTagsView, closeOtherTagsView,
closeAllTagsView, closeAllTagsView,
openCurrenFullscreen,
onImplementClick, onImplementClick,
...toRefs(state), ...toRefs(state),
}; };

View File

@ -0,0 +1,40 @@
<template>
<div class="editor-container">
<el-card shadow="hover" header="wangeditor富文本编辑器">
<el-alert title="感谢优秀的 `wangeditor`项目地址https://github.com/wangeditor-team/wangEditor" type="success"
:closable="false" class="mb15"></el-alert>
<div id="wangeditor"></div>
</el-card>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted } from "vue";
import wangeditor from "wangeditor";
export default {
name: "wangeditor",
components: {},
setup() {
const state = reactive({});
//
// https://doc.wangeditor.com/
const initWangeditor = () => {
const editor = new wangeditor("#wangeditor");
editor.config.placeholder = "请输入内容";
editor.config.onchange = (html) => {
console.log(html);
// console.log(editor.txt.html());
// console.log(editor.txt.text());
};
editor.create();
};
//
onMounted(() => {
initWangeditor();
});
return {
...toRefs(state),
};
},
};
</script>

View File

@ -130,6 +130,5 @@ export default {
padding-right: 15px; padding-right: 15px;
background: var(--bg-topBar); background: var(--bg-topBar);
overflow: hidden; overflow: hidden;
border-bottom: 1px solid rgb(238, 238, 238);
} }
</style> </style>

View File

@ -41,6 +41,12 @@ export default defineComponent({
{ id: 1, txt: "关闭", affix: false, icon: "el-icon-close" }, { id: 1, txt: "关闭", affix: false, icon: "el-icon-close" },
{ id: 2, txt: "关闭其它", affix: false, icon: "el-icon-circle-close" }, { id: 2, txt: "关闭其它", affix: false, icon: "el-icon-circle-close" },
{ id: 3, txt: "全部关闭", affix: false, icon: "el-icon-folder-delete" }, { id: 3, txt: "全部关闭", affix: false, icon: "el-icon-folder-delete" },
{
id: 4,
txt: "当前全屏",
affix: false,
icon: "iconfont icon-fullscreen",
},
], ],
path: {}, path: {},
}); });
@ -93,6 +99,9 @@ export default defineComponent({
position: fixed; position: fixed;
.el-dropdown-menu__item { .el-dropdown-menu__item {
font-size: 12px !important; font-size: 12px !important;
i {
font-size: 12px !important;
}
} }
} }
</style> </style>

View File

@ -38,6 +38,7 @@ import {
watch, watch,
} from "vue"; } from "vue";
import { useRoute, useRouter, onBeforeRouteUpdate } from "vue-router"; import { useRoute, useRouter, onBeforeRouteUpdate } from "vue-router";
import screenfull from "screenfull";
import { useStore } from "/@/store/index.ts"; import { useStore } from "/@/store/index.ts";
import { setSession, getSession, removeSession } from "/@/utils/storage.ts"; import { setSession, getSession, removeSession } from "/@/utils/storage.ts";
import Sortable from "sortablejs"; import Sortable from "sortablejs";
@ -153,6 +154,14 @@ export default {
}); });
addBrowserSetSession(state.tagsViewList); addBrowserSetSession(state.tagsViewList);
}; };
// 6
const openCurrenFullscreen = (path: string) => {
nextTick(() => {
router.push(path);
const element = document.querySelector(".layout-main");
screenfull.request(element);
});
};
// //
const onCurrentContextmenuClick = (data: object) => { const onCurrentContextmenuClick = (data: object) => {
let { id, path } = data; let { id, path } = data;
@ -171,6 +180,9 @@ export default {
case 3: case 3:
closeAllTagsView(path); closeAllTagsView(path);
break; break;
case 4:
openCurrenFullscreen(path);
break;
} }
}; };
// //
@ -293,6 +305,7 @@ export default {
.layout-navbars-tagsview { .layout-navbars-tagsview {
flex: 1; flex: 1;
background-color: #ffffff; background-color: #ffffff;
border-top: 1px solid #ebeef5;
::v-deep(.el-scrollbar__wrap) { ::v-deep(.el-scrollbar__wrap) {
overflow-x: auto !important; overflow-x: auto !important;
} }

View File

@ -0,0 +1,92 @@
<template>
<div class="city-linkage-container">
<el-alert title="温馨提示:箭头问题为 `element plusCascader 级联选择器` 宽度设为 100%后自带bug" type="warning" :closable="false">
</el-alert>
<el-card shadow="hover" header="1、三级联动省市区" class="mt15">
<el-cascader v-model="threeLevelLinkage" :options="threeLevelLinkageList" :props="{ expandTrigger: 'hover',
value: 'code' , label: 'name' }" size="small" clearable class="w100">
</el-cascader>
</el-card>
<el-card shadow="hover" header="2、分开联动" class="mt15">
<el-form size="small" label-width="40px">
<el-row :gutter="15">
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="mb15">
<el-form-item label="省级">
<el-select v-model="linkage.province" placeholder="请选择" size="small" clearable @change="onProvinceChange"
class="w100">
<el-option v-for="(v,k) in linkage.provinceList" :key="k" :label="v.name" :value="v.name"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8" class="mb15">
<el-form-item label="市级">
<el-select v-model="linkage.city" placeholder="请选择" size="small" clearable @change="onCityChange"
class="w100">
<el-option v-for="(v,k) in linkage.cityList" :key="k" :label="v.name" :value="v.name"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-form-item label="区级">
<el-select v-model="linkage.area" placeholder="请选择" size="small" clearable class="w100">
<el-option v-for="(v,k) in linkage.areaList" :key="k" :label="v.name" :value="v.name"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted } from "vue";
import threeLevelLinkageJson from "/@/mock/threeLevelLinkage.json";
export default {
name: "cityLinkage",
setup() {
const state = reactive({
threeLevelLinkage: "",
threeLevelLinkageList: [],
linkage: {
province: "",
city: "",
area: "",
provinceList: [], //
cityList: [], //
areaList: [], //
},
});
//
const initCityData = () => {
state.threeLevelLinkageList = threeLevelLinkageJson;
state.linkage.provinceList = threeLevelLinkageJson;
};
//
const onProvinceChange = (name) => {
state.linkage.city = "";
state.linkage.cityList = [];
state.linkage.provinceList.map((v) => {
if (v.name === name) state.linkage.cityList = v.children;
});
};
//
const onCityChange = (name) => {
state.linkage.area = "";
state.linkage.areaList = [];
state.linkage.cityList.map((v) => {
if (v.name === name) state.linkage.areaList = v.children;
});
};
//
onMounted(() => {
initCityData();
});
return {
onProvinceChange,
onCityChange,
...toRefs(state),
};
},
};
</script>