mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2025-12-18 08:55:52 +08:00
优化管理端代码结构
This commit is contained in:
197
manager/src/components/affix-time.vue
Normal file
197
manager/src/components/affix-time.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="breadcrumb">
|
||||
<span @click="clickBreadcrumb(item,index)" :class="{'active':item.selected}" v-for="(item,index) in dateList"
|
||||
:key="index"> {{item.title}}</span>
|
||||
<div class="date-picker">
|
||||
<Select @on-change="changeSelect(selectedWay)" v-model="month" placeholder="年月查询" clearable
|
||||
style="width:200px;margin-left:10px;">
|
||||
<Option v-for="(item,index) in dates" :value="item.year+'-'+item.month" :key="index" clearable>
|
||||
{{ item.year+'年'+item.month+'月' }}</Option>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="shop-list" v-if="!closeShop">
|
||||
<Select clearable @on-change="changeshop(selectedWay)" v-model="storeId" placeholder="店铺查询"
|
||||
style="width:200px;margin-left:10px;">
|
||||
<Scroll :on-reach-bottom="handleReachBottom">
|
||||
<Option v-for="(item,index) in shopsData" :value="item.id" :key="index">{{ item.storeName }}</Option>
|
||||
</Scroll>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getShopListData } from "@/api/shops.js";
|
||||
export default {
|
||||
props: ["closeShop"],
|
||||
data() {
|
||||
return {
|
||||
month: "", // 月份
|
||||
|
||||
selectedWay: {
|
||||
// 可选时间项
|
||||
title: "最近7天",
|
||||
selected: true,
|
||||
searchType: "LAST_SEVEN",
|
||||
},
|
||||
storeId: "", // 店铺id
|
||||
dates: [], // 日期列表
|
||||
params: {
|
||||
// 请求参数
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
storeName: "",
|
||||
},
|
||||
dateList: [
|
||||
// 筛选条件
|
||||
{
|
||||
title: "今天",
|
||||
selected: false,
|
||||
searchType: "TODAY",
|
||||
},
|
||||
{
|
||||
title: "昨天",
|
||||
selected: false,
|
||||
searchType: "YESTERDAY",
|
||||
},
|
||||
{
|
||||
title: "最近7天",
|
||||
selected: true,
|
||||
searchType: "LAST_SEVEN",
|
||||
},
|
||||
{
|
||||
title: "最近30天",
|
||||
selected: false,
|
||||
searchType: "LAST_THIRTY",
|
||||
},
|
||||
],
|
||||
|
||||
shopTotal: "", // 店铺总数
|
||||
shopsData: [], // 店铺数据
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getFiveYears();
|
||||
this.getShopList();
|
||||
},
|
||||
methods: {
|
||||
// 页面触底
|
||||
handleReachBottom() {
|
||||
setTimeout(() => {
|
||||
if (this.params.pageNumber * this.params.pageSize <= this.shopTotal) {
|
||||
this.params.pageNumber++;
|
||||
this.getShopList();
|
||||
}
|
||||
}, 1500);
|
||||
},
|
||||
// 查询店铺列表
|
||||
getShopList() {
|
||||
getShopListData(this.params).then((res) => {
|
||||
if (res.success) {
|
||||
/**
|
||||
* 解决数据请求中,滚动栏会一直上下跳动
|
||||
*/
|
||||
this.shopTotal = res.result.total;
|
||||
|
||||
this.shopsData.push(...res.result.records);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 变更店铺
|
||||
changeshop(val) {
|
||||
this.selectedWay.storeId = this.storeId;
|
||||
this.$emit("selected", this.selectedWay);
|
||||
},
|
||||
|
||||
// 获取近5年 年月
|
||||
getFiveYears() {
|
||||
let getYear = new Date().getFullYear();
|
||||
|
||||
let lastFiveYear = getYear - 5;
|
||||
let maxMonth = new Date().getMonth() + 1;
|
||||
let dates = [];
|
||||
// 循环出过去5年
|
||||
for (let year = lastFiveYear; year <= getYear; year++) {
|
||||
for (let month = 1; month <= 12; month++) {
|
||||
if (year == getYear && month > maxMonth) {
|
||||
} else {
|
||||
dates.push({
|
||||
year: year,
|
||||
month: month,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dates = dates.reverse();
|
||||
},
|
||||
// 改变已选店铺
|
||||
changeSelect() {
|
||||
console.log(this.month);
|
||||
if (this.month) {
|
||||
this.dateList.forEach((res) => {
|
||||
res.selected = false;
|
||||
});
|
||||
this.selectedWay.year = this.month.split("-")[0];
|
||||
this.selectedWay.month = this.month.split("-")[1];
|
||||
this.selectedWay.searchType = "";
|
||||
|
||||
this.$emit("selected", this.selectedWay);
|
||||
} else {
|
||||
}
|
||||
},
|
||||
// 变更时间
|
||||
clickBreadcrumb(item) {
|
||||
this.dateList.forEach((res) => {
|
||||
res.selected = false;
|
||||
});
|
||||
item.selected = true;
|
||||
item.storeId = this.storeId;
|
||||
this.month = "";
|
||||
|
||||
if (item.searchType == "") {
|
||||
if (
|
||||
dateList.some((date) => {
|
||||
return date.title == item.title;
|
||||
})
|
||||
) {
|
||||
item.searchType = date.searchType;
|
||||
} else {
|
||||
item.searchType = "LAST_SEVEN";
|
||||
}
|
||||
}
|
||||
|
||||
this.selectedWay = item;
|
||||
this.selectedWay.year = new Date().getFullYear();
|
||||
this.selectedWay.month = "";
|
||||
|
||||
this.$emit("selected", this.selectedWay);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> span {
|
||||
margin-right: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.active {
|
||||
color: $theme_color;
|
||||
position: relative;
|
||||
}
|
||||
.date-picker {
|
||||
}
|
||||
.active:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: $theme_color;
|
||||
}
|
||||
</style>
|
||||
61
manager/src/components/editor/config.js
Normal file
61
manager/src/components/editor/config.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import plugins from "./plugins";
|
||||
import toobar from "./toolbar";
|
||||
import { upLoadFile } from "@/api/common";
|
||||
|
||||
export const initEditor = {
|
||||
height: "400px",
|
||||
language: "zh_CN",
|
||||
menubar: "file edit insert view format table", // 菜单:指定应该出现哪些菜单
|
||||
toolbar: toobar, // 分组工具栏控件
|
||||
plugins: plugins, // 插件(比如: advlist | link | image | preview等)
|
||||
object_resizing: false, // 是否禁用表格图片大小调整
|
||||
end_container_on_empty_block: true, // enter键 分块
|
||||
powerpaste_word_import: "merge", // 是否保留word粘贴样式 clean | merge
|
||||
code_dialog_height: 450, // 代码框高度 、宽度
|
||||
code_dialog_width: 1000,
|
||||
advlist_bullet_styles: "square", // 无序列表 有序列表
|
||||
maxSize: "2097152", // 设置图片大小
|
||||
accept: "image/jpeg, image/png", // 设置图片上传规则
|
||||
images_upload_handler: async function (blobInfo, success, failure) {
|
||||
console.log("请求")
|
||||
const formData = new FormData();
|
||||
formData.append("file", blobInfo.blob());
|
||||
try {
|
||||
const res = await upLoadFile(formData);
|
||||
if (res.result) {
|
||||
|
||||
success(res.result)
|
||||
} else {
|
||||
failure("上传文件有误请稍后重试");
|
||||
}
|
||||
} catch (e) {
|
||||
failure('上传出错')
|
||||
}
|
||||
},
|
||||
// init_instance_callback: function (editor) {
|
||||
// var freeTiny = document.querySelector(".tox .tox-notification--in");
|
||||
// freeTiny.style.display = "none";
|
||||
// },
|
||||
content_style: `
|
||||
* { padding:0; margin:0; }
|
||||
|
||||
html, body height:100%; }
|
||||
|
||||
img { max-width:100%; display:block;height:auto; }
|
||||
|
||||
a { text-decoration: none; }
|
||||
|
||||
iframe{ width: 100%; }
|
||||
|
||||
p { line-height:1.6; margin: 0px; }
|
||||
|
||||
table{ word-wrap:break-word; word-break:break-all; max-width:100%; border:none; border-color:#999; }
|
||||
|
||||
.mce-object-iframe{ width:100%; box-sizing:border-box; margin:0; padding:0; }
|
||||
|
||||
ul,ol{ list-style-position:inside; }
|
||||
`, // 设置样式
|
||||
statusbar: false, // 隐藏编辑器底部的状态栏
|
||||
elementpath: false, // 禁用编辑器底部的状态栏
|
||||
paste_data_images: true, // 允许粘贴图像
|
||||
};
|
||||
4
manager/src/components/editor/plugins.js
Normal file
4
manager/src/components/editor/plugins.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const plugins = [
|
||||
'advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools importcss insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount'
|
||||
]
|
||||
export default plugins
|
||||
2
manager/src/components/editor/toolbar.js
Normal file
2
manager/src/components/editor/toolbar.js
Normal file
@@ -0,0 +1,2 @@
|
||||
const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor ']
|
||||
export default toolbar
|
||||
BIN
manager/src/components/hotzone/assets/styles/icons8-edit-64.png
Normal file
BIN
manager/src/components/hotzone/assets/styles/icons8-edit-64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
657
manager/src/components/hotzone/assets/styles/main.css
Normal file
657
manager/src/components/hotzone/assets/styles/main.css
Normal file
@@ -0,0 +1,657 @@
|
||||
.hz-m-wrap {
|
||||
position: relative;
|
||||
/*overflow: hidden;*/
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-u-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-area {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-item {
|
||||
position: absolute;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 6px #000;
|
||||
background-color: #e31414;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.hz-m-box{
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box>li {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box.hz-z-hidden>li {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box.hz-m-hoverbox:hover {
|
||||
box-shadow: 0 0 0 2px #373950;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box.hz-m-hoverbox .hz-icon:hover {
|
||||
background-color: #373950;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-icon:hover {
|
||||
background-color: #e31414;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-index {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
background-color: #000;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-close {
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-m-copy {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-small-icon {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-square {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-square:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-square-tl {
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
cursor: nw-resize;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-square-tc {
|
||||
top: -4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-square-tr {
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
cursor: ne-resize;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-square-cl {
|
||||
top: 50%;
|
||||
left: -4px;
|
||||
transform: translateY(-50%);
|
||||
cursor: w-resize;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-square-cr {
|
||||
top: 50%;
|
||||
right: -4px;
|
||||
transform: translateY(-50%);
|
||||
cursor: w-resize;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-square-bl {
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
cursor: sw-resize;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-square-bc {
|
||||
bottom: -4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
cursor: s-resize;
|
||||
}
|
||||
|
||||
.hz-m-wrap .hz-m-box .hz-u-square-br {
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
cursor: se-resize;
|
||||
}
|
||||
|
||||
/* reset */
|
||||
.hz-m-modal,
|
||||
.hz-m-wrap {
|
||||
font-size: 12px;
|
||||
/* 清除内外边距 */
|
||||
/* 重置列表元素 */
|
||||
/* 重置文本格式元素 */
|
||||
/* 初始化 input */
|
||||
}
|
||||
|
||||
.hz-m-modal ul,
|
||||
.hz-m-wrap ul,
|
||||
.hz-m-modal ol,
|
||||
.hz-m-wrap ol,
|
||||
.hz-m-modal li,
|
||||
.hz-m-wrap li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hz-m-modal ul,
|
||||
.hz-m-wrap ul,
|
||||
.hz-m-modal ol,
|
||||
.hz-m-wrap ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.hz-m-modal a,
|
||||
.hz-m-wrap a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.hz-m-modal a:hover,
|
||||
.hz-m-wrap a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.hz-m-modal p,
|
||||
.hz-m-wrap p {
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
}
|
||||
|
||||
.hz-m-modal input[type="checkbox"],
|
||||
.hz-m-wrap input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* basic */
|
||||
/* modal 样式 */
|
||||
.hz-m-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: cross-slide-y pinch-zoom double-tap-zoom;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hz-m-modal:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_dialog {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_close {
|
||||
float: right;
|
||||
margin: -6px -4px 0 0;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.hz-m-modal .hz-modal_dialog {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
html.z-modal,
|
||||
html.z-modal body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hz-m-modal {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_dialog {
|
||||
width: 450px;
|
||||
background: #fff;
|
||||
-webkit-box-shadow: 0 2px 3px rgba(0, 0, 0, 0.125);
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_hd {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #f4f4f4;
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_close {
|
||||
margin: -15px -15px 0 0;
|
||||
padding: 6px;
|
||||
color: #bbb;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_close:hover {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_close .hz-u-icon-close {
|
||||
font-size: 18px;
|
||||
transition: transform 500ms ease-in-out;
|
||||
transform: rotate(0deg);
|
||||
width: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_close:hover .hz-u-icon-close {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_bd {
|
||||
padding: 15px 15px 0 15px;
|
||||
min-height: 10px;
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_ft {
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #f4f4f4;
|
||||
}
|
||||
|
||||
.hz-m-modal .hz-modal_ft .hz-u-btn {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.hz-m-modal .hz-modal_dialog {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 基本按钮样式 btn */
|
||||
.hz-u-btn {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
overflow: visible;
|
||||
font: inherit;
|
||||
text-transform: none;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background: none;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.hz-u-btn:hover,
|
||||
.hz-u-btn:focus {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.hz-u-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.hz-u-btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hz-u-btn {
|
||||
padding: 0 16px;
|
||||
height: 28px;
|
||||
line-height: 26px;
|
||||
background: #f4f4f4;
|
||||
color: #444;
|
||||
border: 1px solid #ddd;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.hz-u-btn:hover,
|
||||
.hz-u-btn:focus {
|
||||
background: #e5e5e5;
|
||||
border: 1px solid #adadad;
|
||||
}
|
||||
|
||||
.hz-u-btn:active {
|
||||
background: #e5e5e5;
|
||||
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.hz-u-btn:disabled {
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
filter: alpha(opacity=65);
|
||||
opacity: 0.65;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 按钮类型 */
|
||||
.hz-u-btn-primary {
|
||||
background: #67739b;
|
||||
color: #fff;
|
||||
border: 1px solid #67739b;
|
||||
}
|
||||
|
||||
.hz-u-btn-primary:hover,
|
||||
.hz-u-btn-primary:focus {
|
||||
background: #31384b;
|
||||
color: #fff;
|
||||
border: 1px solid #31384b;
|
||||
}
|
||||
|
||||
.hz-u-btn-primary:active {
|
||||
background: #367fa9;
|
||||
color: #fff;
|
||||
border: 1px solid #367fa9;
|
||||
}
|
||||
|
||||
.hz-u-btn-primary:disabled {
|
||||
background: #444;
|
||||
color: #fff;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
/* input */
|
||||
.hz-u-input {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.hz-u-input {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
padding: 5px 6px;
|
||||
border: 1px solid #d2d6de;
|
||||
color: #555;
|
||||
background: #fff;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.hz-u-input::-webkit-input-placeholder {
|
||||
color: #bbb;
|
||||
filter: alpha(opacity=100);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hz-u-input::-moz-placeholder {
|
||||
color: #bbb;
|
||||
filter: alpha(opacity=100);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hz-u-input:-moz-placeholder {
|
||||
color: #bbb;
|
||||
filter: alpha(opacity=100);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hz-u-input:-ms-placeholder {
|
||||
color: #bbb;
|
||||
filter: alpha(opacity=100);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hz-u-input:focus {
|
||||
outline: 0;
|
||||
background: #fff;
|
||||
color: #555;
|
||||
border: 1px solid #3c8dbc;
|
||||
}
|
||||
|
||||
.hz-u-input:disabled {
|
||||
cursor: not-allowed;
|
||||
background: #eee;
|
||||
color: #999;
|
||||
border: 1px solid #d2d6de;
|
||||
}
|
||||
|
||||
.hz-u-input {
|
||||
width: 280px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.hz-u-input.hz-u-input-success {
|
||||
color: #00a65a;
|
||||
border-color: #00a65a;
|
||||
}
|
||||
|
||||
.hz-u-input.hz-u-input-warning {
|
||||
color: #f39c12;
|
||||
border-color: #f39c12;
|
||||
}
|
||||
|
||||
.hz-u-input.hz-u-input-error {
|
||||
color: #dd4b39;
|
||||
border-color: #dd4b39;
|
||||
}
|
||||
|
||||
.hz-u-input.hz-u-input-blank {
|
||||
border-color: transparent;
|
||||
border-style: dashed;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.hz-u-input.hz-u-input-blank:focus {
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
/* formItem */
|
||||
.hz-u-formitem {
|
||||
display: inline-block;
|
||||
*zoom: 1;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.hz-u-formitem:before,
|
||||
.hz-u-formitem:after {
|
||||
display: table;
|
||||
content: "";
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.hz-u-formitem:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.hz-u-formitem .hz-formitem_tt {
|
||||
display: block;
|
||||
float: left;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.hz-u-formitem .hz-formitem_ct {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hz-u-formitem .hz-formitem_rqr {
|
||||
line-height: 28px;
|
||||
color: #dd4b39;
|
||||
}
|
||||
|
||||
.hz-u-formitem .hz-formitem_tt {
|
||||
line-height: 34px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.hz-u-formitem .hz-formitem_ct {
|
||||
line-height: 34px;
|
||||
margin-left: 108px;
|
||||
}
|
||||
|
||||
/* icon */
|
||||
.hz-u-icon {
|
||||
display: inline-block;
|
||||
font: normal normal normal 14px/1 FontAwesome;
|
||||
font-size: inherit;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* label */
|
||||
.hz-u-label {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* margin */
|
||||
.hz-f-ml0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* replicator */
|
||||
.hz-u-copy input[data-for-copy] {
|
||||
transform: translateZ(0);
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
z-index: -999;
|
||||
color: transparent;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'iconfont';
|
||||
/* project id 525460 */
|
||||
src: url('//at.alicdn.com/t/font_525460_d0ysfwzacahsemi.eot');
|
||||
src: url('//at.alicdn.com/t/font_525460_d0ysfwzacahsemi.eot?#iefix') format('embedded-opentype'), url('//at.alicdn.com/t/font_525460_d0ysfwzacahsemi.woff') format('woff'), url('//at.alicdn.com/t/font_525460_d0ysfwzacahsemi.ttf') format('truetype'), url('//at.alicdn.com/t/font_525460_d0ysfwzacahsemi.svg#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.hz-icon {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.hz-icon-edit {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.hz-flex-img {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hz-flex-img img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hz-icon-trash:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.hz-edit-img {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
.hz-edit-img img {
|
||||
max-width: 300px;
|
||||
max-height: 200px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hz-edit-del {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
101
manager/src/components/hotzone/components/Hotzone.vue
Normal file
101
manager/src/components/hotzone/components/Hotzone.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div ref="content" class="hz-m-wrap">
|
||||
<img class="hz-u-img" :src="image" />
|
||||
<ul class="hz-m-area" v-add-item>
|
||||
<zone
|
||||
class="hz-m-item"
|
||||
v-for="(zone, index) in zones"
|
||||
:key="index"
|
||||
:index="index"
|
||||
:setting="zone"
|
||||
@delItem="removeItem($event)"
|
||||
@changeInfo="changeInfo($event)"
|
||||
></zone>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Zone from "./Zone";
|
||||
import addItem from "../directives/addItem";
|
||||
|
||||
export default {
|
||||
name: "HotZone",
|
||||
data() {
|
||||
return {
|
||||
zones: [],
|
||||
};
|
||||
},
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
zonesInit: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.zones = this.zonesInit.concat();
|
||||
},
|
||||
methods: {
|
||||
changeInfo(res) {
|
||||
let { info, index, zoneInfo } = res;
|
||||
info = { ...zoneInfo, ...info };
|
||||
// 改变热区并发送change通知
|
||||
Object.assign(this.zones[index], info);
|
||||
this.hasChange("changeInfo");
|
||||
},
|
||||
addItem(setting) {
|
||||
this.zones.push(setting);
|
||||
this.$emit("choose");
|
||||
// this.hasChange() 不应该发送通知,mouseup判定成功才应该发
|
||||
// this.$emit('add', setting)
|
||||
},
|
||||
eraseItem(index = this.zones.length - 1) {
|
||||
this.zones.splice(index, 1);
|
||||
this.$emit("erase", index);
|
||||
},
|
||||
isOverRange() {
|
||||
let { max, zones } = this;
|
||||
|
||||
return max && zones.length > max;
|
||||
},
|
||||
overRange() {
|
||||
const index = this.zones.length - 1;
|
||||
|
||||
this.zones.splice(index, 1);
|
||||
this.$emit("overRange", index);
|
||||
},
|
||||
removeItem(index = this.zones.length - 1) {
|
||||
this.zones.splice(index, 1);
|
||||
this.hasChange("removeItem");
|
||||
this.$emit("remove", index);
|
||||
},
|
||||
changeItem(info, isAdd) {
|
||||
const index = this.zones.length - 1;
|
||||
// 改变热区并发送change通知
|
||||
Object.assign(this.zones[index], info);
|
||||
this.hasChange("changeItem");
|
||||
isAdd && this.$emit("add", this.zones[index]);
|
||||
},
|
||||
hasChange(from) {
|
||||
this.$emit("change", this.zones);
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
addItem,
|
||||
},
|
||||
components: {
|
||||
Zone,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "../assets/styles/main.css";
|
||||
</style>
|
||||
212
manager/src/components/hotzone/components/Zone.vue
Normal file
212
manager/src/components/hotzone/components/Zone.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<li
|
||||
v-drag-item
|
||||
:style="{
|
||||
top: zoneTop,
|
||||
left: zoneLeft,
|
||||
width: zoneWidth,
|
||||
height: zoneHeight,
|
||||
}"
|
||||
>
|
||||
<ul
|
||||
v-change-size
|
||||
class="hz-m-box"
|
||||
:class="{
|
||||
'hz-z-hidden': tooSmall,
|
||||
'hz-m-hoverbox': !hideZone,
|
||||
}"
|
||||
>
|
||||
<li class="hz-u-index" :title="`热区${index + 1}`">{{ index + 1 }}</li>
|
||||
|
||||
<li
|
||||
title="删除该热区"
|
||||
v-show="!hideZone"
|
||||
class="hz-u-close hz-icon hz-icon-trash"
|
||||
@click.prevent.stop="delItem(index)"
|
||||
@mousedown.stop
|
||||
@mouseup.stop
|
||||
@mousemove.stop
|
||||
></li>
|
||||
<li
|
||||
title="编辑该热区"
|
||||
v-show="!hideZone"
|
||||
class="hz-u-close hz-icon hz-icon-edit"
|
||||
@click.prevent.stop="showModalFn(index)"
|
||||
@mousedown.stop
|
||||
@mouseup.stop
|
||||
@mousemove.stop
|
||||
>
|
||||
<img width="17" height="17" src="../assets/styles/icons8-edit-64.png"></img>
|
||||
</li>
|
||||
<li class="hz-flex-img">
|
||||
<img class="hz-u-img" :src="zoneForm.img" />
|
||||
</li>
|
||||
<li class="hz-u-square hz-u-square-tl" data-pointer="dealTL"></li>
|
||||
<li class="hz-u-square hz-u-square-tc" data-pointer="dealTC"></li>
|
||||
<li class="hz-u-square hz-u-square-tr" data-pointer="dealTR"></li>
|
||||
<li class="hz-u-square hz-u-square-cl" data-pointer="dealCL"></li>
|
||||
<li class="hz-u-square hz-u-square-cr" data-pointer="dealCR"></li>
|
||||
<li class="hz-u-square hz-u-square-bl" data-pointer="dealBL"></li>
|
||||
<li class="hz-u-square hz-u-square-bc" data-pointer="dealBC"></li>
|
||||
<li class="hz-u-square hz-u-square-br" data-pointer="dealBR"></li>
|
||||
</ul>
|
||||
<Modal
|
||||
v-model="showModal"
|
||||
title="编辑热区"
|
||||
draggable
|
||||
scrollable
|
||||
:mask="false"
|
||||
ok-text="保存"
|
||||
@on-ok="saveZone"
|
||||
@on-cancel="
|
||||
() => {
|
||||
showModal = false;
|
||||
}
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div class="hz-edit-img">
|
||||
<img class="show-image" :src="zoneForm.img" alt />
|
||||
</div>
|
||||
|
||||
<Form :model="zoneForm" :label-width="80">
|
||||
<FormItem label="图片链接:">
|
||||
<Input v-model="zoneForm.img"></Input>
|
||||
<Button size="small" type="primary" @click="handleSelectImg"
|
||||
>选择图片</Button
|
||||
>
|
||||
</FormItem>
|
||||
<FormItem label="跳转链接:">
|
||||
<Input v-model="zoneForm.link"></Input>
|
||||
<Button size="small" type="primary" @click="handleSelectLink"
|
||||
>选择链接</Button
|
||||
>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
</Modal>
|
||||
<!-- 选择商品。链接 -->
|
||||
<liliDialog ref="liliDialog" @selectedLink="selectedLink"></liliDialog>
|
||||
<!-- 选择图片 -->
|
||||
<Modal width="1200px" v-model="picModelFlag" footer-hide>
|
||||
<ossManage
|
||||
@callback="callbackSelected"
|
||||
:isComponent="true"
|
||||
ref="ossManage"
|
||||
/>
|
||||
</Modal>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import changeSize from "../directives/changeSize";
|
||||
import dragItem from "../directives/dragItem";
|
||||
import ossManage from "@/views/sys/oss-manage/ossManage";
|
||||
|
||||
export default {
|
||||
name: "Zone",
|
||||
components: {
|
||||
ossManage,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
zoneTop: "",
|
||||
zoneLeft: "",
|
||||
zoneWidth: "",
|
||||
zoneHeight: "",
|
||||
hideZone: false,
|
||||
tooSmall: false,
|
||||
showModal: false,
|
||||
picModelFlag: false,
|
||||
currentIndex: 0,
|
||||
zoneForm: {
|
||||
img: "",
|
||||
link: "",
|
||||
type: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
props: ["index", "setting"],
|
||||
mounted() {
|
||||
console.log(this.setting);
|
||||
this.setZoneInfo(this.setting);
|
||||
},
|
||||
methods: {
|
||||
setZoneInfo(val) {
|
||||
this.zoneTop = this.getZoneStyle(val.topPer);
|
||||
this.zoneLeft = this.getZoneStyle(val.leftPer);
|
||||
this.zoneWidth = this.getZoneStyle(val.widthPer);
|
||||
this.zoneHeight = this.getZoneStyle(val.heightPer);
|
||||
this.tooSmall = val.widthPer < 0.01 && val.heightPer < 0.01;
|
||||
this.zoneForm.img = val.img;
|
||||
this.zoneForm.link = val.link;
|
||||
this.zoneForm.type = val.type;
|
||||
},
|
||||
handlehideZone(isHide = true) {
|
||||
if (this.hideZone === isHide) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hideZone = isHide;
|
||||
},
|
||||
changeInfo(info = {}) {
|
||||
const { index } = this;
|
||||
|
||||
this.$emit("changeInfo", {
|
||||
info,
|
||||
index,
|
||||
zoneInfo: this.zoneForm,
|
||||
});
|
||||
},
|
||||
showModalFn(index) {
|
||||
this.showModal = true;
|
||||
this.currentIndex = index;
|
||||
},
|
||||
// 选择图片
|
||||
handleSelectImg() {
|
||||
this.$refs.ossManage.selectImage = true;
|
||||
this.picModelFlag = true;
|
||||
},
|
||||
// 选择图片回调
|
||||
callbackSelected(item) {
|
||||
this.picModelFlag = false;
|
||||
this.zoneForm.img = item.url;
|
||||
},
|
||||
|
||||
// 调起选择链接弹窗
|
||||
handleSelectLink(item, index) {
|
||||
if (item) this.selectedNav = item;
|
||||
this.$refs.liliDialog.open("link");
|
||||
},
|
||||
// 已选链接
|
||||
selectedLink(val) {
|
||||
this.zoneForm.link = this.$options.filters.formatLinkType(val);
|
||||
this.zoneForm.type = val.___type;
|
||||
},
|
||||
saveZone() {},
|
||||
delZone() {
|
||||
this.delItem(this.currentIndex);
|
||||
},
|
||||
delItem(index) {
|
||||
this.$emit("delItem", index);
|
||||
},
|
||||
getZoneStyle(val) {
|
||||
return `${(val || 0) * 100}%`;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
setting: {
|
||||
handler: function (val) {
|
||||
this.setZoneInfo(val);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
changeSize,
|
||||
dragItem,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
100
manager/src/components/hotzone/directives/addItem.js
Normal file
100
manager/src/components/hotzone/directives/addItem.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import _ from '../utils'
|
||||
|
||||
export default {
|
||||
bind: function (el, binding, vnode) {
|
||||
const MIN_LIMIT = _.MIN_LIMIT
|
||||
|
||||
el.addEventListener('mousedown', handleMouseDown,{ passive: false })
|
||||
|
||||
function handleMouseDown (e) {
|
||||
console.log('additem', e)
|
||||
e && e.preventDefault()
|
||||
|
||||
let itemInfo = {
|
||||
top: _.getDistanceY(e, el),
|
||||
left: _.getDistanceX(e, el),
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
let container = _.getOffset(el)
|
||||
|
||||
// Only used once at the beginning of init
|
||||
let setting = {
|
||||
topPer: _.decimalPoint(itemInfo.top / container.height),
|
||||
leftPer: _.decimalPoint(itemInfo.left / container.width),
|
||||
widthPer: 0,
|
||||
heightPer: 0
|
||||
}
|
||||
let preX = _.getPageX(e)
|
||||
let preY = _.getPageY(e)
|
||||
|
||||
vnode.context.addItem(setting)// 这里去添加并发送了add通知,不应该发送通知
|
||||
|
||||
window.addEventListener('mousemove', handleChange,{ passive: false })
|
||||
window.addEventListener('mouseup', handleMouseUp,{ passive: false })
|
||||
|
||||
function handleChange (e) {
|
||||
e && e.preventDefault()
|
||||
|
||||
let moveX = _.getPageX(e) - preX
|
||||
let moveY = _.getPageY(e) - preY
|
||||
preX = _.getPageX(e)
|
||||
preY = _.getPageY(e)
|
||||
|
||||
// Not consider the direction of movement first, consider only the lower right drag point
|
||||
let minLimit = 0
|
||||
// 添加热区时,判定鼠标释放时,满足(热区大于48*48时)条件时生效
|
||||
let styleInfo = _.dealBR(itemInfo, moveX, moveY, minLimit)
|
||||
|
||||
// Boundary value processing 改变热区大小时边界条件的处理
|
||||
itemInfo = _.dealEdgeValue(itemInfo, styleInfo, container, vnode.context.zones)
|
||||
|
||||
Object.assign(el.lastElementChild.style, {
|
||||
top: `${itemInfo.top}px`,
|
||||
left: `${itemInfo.left}px`,
|
||||
width: `${itemInfo.width}px`,
|
||||
height: `${itemInfo.height}px`
|
||||
})
|
||||
}
|
||||
|
||||
function handleMouseUp () {
|
||||
let perInfo = {
|
||||
topPer: _.decimalPoint(itemInfo.top / container.height),
|
||||
leftPer: _.decimalPoint(itemInfo.left / container.width),
|
||||
widthPer: _.decimalPoint(itemInfo.width / container.width),
|
||||
heightPer: _.decimalPoint(itemInfo.height / container.height),
|
||||
img: "",
|
||||
link: "",
|
||||
type: "",
|
||||
}
|
||||
|
||||
if (vnode.context.isOverRange()) {
|
||||
vnode.context.overRange() // 判断超出个数限制,给overRange钩子抛回调
|
||||
} else if (container.height < MIN_LIMIT && itemInfo.width > MIN_LIMIT) {
|
||||
vnode.context.changeItem(Object.assign(perInfo, {
|
||||
topPer: 0,
|
||||
heightPer: 1
|
||||
}), true)
|
||||
} else if (container.width < MIN_LIMIT && itemInfo.height > MIN_LIMIT) {
|
||||
vnode.context.changeItem(Object.assign(perInfo, {
|
||||
leftper: 0,
|
||||
widthPer: 1
|
||||
}), true)
|
||||
} else if (itemInfo.width > MIN_LIMIT && itemInfo.height > MIN_LIMIT) {
|
||||
vnode.context.changeItem(perInfo, true)
|
||||
} else {
|
||||
// 当添加区域超出范围或小于最小区域(48*48)时触发,删除当亲绘制的热区并发送erase事件通知
|
||||
vnode.context.eraseItem()
|
||||
}
|
||||
|
||||
window.removeEventListener('mousemove', handleChange)
|
||||
window.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
}
|
||||
|
||||
el.$destroy = () => el.removeEventListener('mousedown', handleMouseDown)
|
||||
},
|
||||
unbind: function (el) {
|
||||
el.$destroy()
|
||||
}
|
||||
}
|
||||
91
manager/src/components/hotzone/directives/changeSize.js
Normal file
91
manager/src/components/hotzone/directives/changeSize.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import _ from '../utils'
|
||||
|
||||
export default {
|
||||
bind: function (el, binding, vnode) {
|
||||
el.addEventListener('mousedown', handleMouseDown,{ passive: false })
|
||||
|
||||
function handleMouseDown (e) {
|
||||
let pointer = e.target.dataset.pointer //元素上绑定的方法名
|
||||
|
||||
if (!pointer) {
|
||||
return
|
||||
}
|
||||
|
||||
e && e.stopPropagation()
|
||||
|
||||
let zone = el.parentNode
|
||||
let setting = vnode.context.setting
|
||||
let currentIndex = vnode.context.index
|
||||
let container = _.getOffset(zone.parentNode)
|
||||
let itemInfo = {
|
||||
width: _.getOffset(zone).width || 0,
|
||||
height: _.getOffset(zone).height || 0,
|
||||
top: setting.topPer * container.height || 0,
|
||||
left: setting.leftPer * container.width || 0
|
||||
}
|
||||
let preX = _.getPageX(e)
|
||||
let preY = _.getPageY(e)
|
||||
let flag
|
||||
|
||||
// Hide the info displayed by hover
|
||||
vnode.context.handlehideZone(true)
|
||||
|
||||
window.addEventListener('mousemove', handleChange,{ passive: false })
|
||||
window.addEventListener('mouseup', handleMouseUp,{ passive: false })
|
||||
|
||||
function handleChange (e) {
|
||||
e && e.preventDefault()
|
||||
flag = true
|
||||
|
||||
let moveX = _.getPageX(e) - preX
|
||||
let moveY = _.getPageY(e) - preY
|
||||
|
||||
preX = _.getPageX(e)
|
||||
preY = _.getPageY(e)
|
||||
|
||||
// Handling the situation when different dragging points are selected
|
||||
let styleInfo = _[pointer](itemInfo, moveX, moveY)//调用对应的方法
|
||||
// Boundary value processing
|
||||
itemInfo = _.dealEdgeValue(itemInfo, styleInfo, container, vnode.context.$parent.zones, currentIndex)
|
||||
|
||||
Object.assign(zone.style, {
|
||||
top: `${itemInfo.top}px`,
|
||||
left: `${itemInfo.left}px`,
|
||||
width: `${itemInfo.width}px`,
|
||||
height: `${itemInfo.height}px`
|
||||
})
|
||||
}
|
||||
|
||||
function handleMouseUp () {
|
||||
if (flag) {
|
||||
flag = false
|
||||
let perInfo = {
|
||||
topPer: _.decimalPoint(itemInfo.top / container.height),
|
||||
leftPer: _.decimalPoint(itemInfo.left / container.width),
|
||||
widthPer: _.decimalPoint(itemInfo.width / container.width),
|
||||
heightPer: _.decimalPoint(itemInfo.height / container.height)
|
||||
}
|
||||
vnode.context.changeInfo(perInfo)
|
||||
|
||||
// 兼容数据无变更情况下导致 computed 不更新,数据仍为 px 时 resize 出现的问题
|
||||
Object.assign(zone.style, {
|
||||
top: `${itemInfo.top}px`,
|
||||
left: `${itemInfo.left}px`,
|
||||
width: `${itemInfo.width}px`,
|
||||
height: `${itemInfo.height}px`
|
||||
})
|
||||
}
|
||||
// Show the info
|
||||
vnode.context.handlehideZone(false)
|
||||
|
||||
window.removeEventListener('mousemove', handleChange)
|
||||
window.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
}
|
||||
|
||||
el.$destroy = () => el.removeEventListener('mousedown', handleMouseDown)
|
||||
},
|
||||
unbind: function (el) {
|
||||
el.$destroy()
|
||||
}
|
||||
}
|
||||
108
manager/src/components/hotzone/directives/dragItem.js
Normal file
108
manager/src/components/hotzone/directives/dragItem.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import _ from '../utils'
|
||||
|
||||
export default {
|
||||
bind: function (el, binding, vnode) {
|
||||
el.addEventListener('mousedown', handleMouseDown)
|
||||
let collision
|
||||
function handleMouseDown (e) {
|
||||
e && e.stopPropagation()
|
||||
let container = _.getOffset(el.parentNode)
|
||||
let preX = _.getPageX(e)
|
||||
let preY = _.getPageY(e)
|
||||
let topPer
|
||||
let leftPer
|
||||
let flag
|
||||
|
||||
window.addEventListener('mousemove', handleChange,{ passive: false })
|
||||
window.addEventListener('mouseup', handleMouseUp,{ passive: false })
|
||||
|
||||
function handleChange (e) {
|
||||
e && e.preventDefault()
|
||||
flag = true
|
||||
collision = false
|
||||
// Hide the info displayed by hover
|
||||
vnode.context.handlehideZone(true)
|
||||
|
||||
let setting = vnode.context.setting
|
||||
let currentIndex = vnode.context.index
|
||||
let moveX = _.getPageX(e) - preX
|
||||
let moveY = _.getPageY(e) - preY
|
||||
|
||||
setting.topPer = setting.topPer || 0
|
||||
setting.leftPer = setting.leftPer || 0
|
||||
topPer = _.decimalPoint(moveY / container.height + setting.topPer)
|
||||
leftPer = _.decimalPoint(moveX / container.width + setting.leftPer)
|
||||
|
||||
// Hotzone moving boundary processing
|
||||
if (topPer < 0) {
|
||||
topPer = 0
|
||||
moveY = -container.height * setting.topPer
|
||||
}
|
||||
|
||||
if (leftPer < 0) {
|
||||
leftPer = 0
|
||||
moveX = -container.width * setting.leftPer
|
||||
}
|
||||
|
||||
if (topPer + setting.heightPer > 1) {
|
||||
topPer = 1 - setting.heightPer
|
||||
moveY = container.height * (topPer - setting.topPer)
|
||||
}
|
||||
|
||||
if (leftPer + setting.widthPer > 1) {
|
||||
leftPer = 1 - setting.widthPer
|
||||
moveX = container.width * (leftPer - setting.leftPer)
|
||||
}
|
||||
// 拖拽碰撞检测
|
||||
if (vnode.context.$parent.zones.length > 1) {
|
||||
let currentzones = JSON.parse(JSON.stringify(vnode.context.$parent.zones)).map((zone) => {
|
||||
return {
|
||||
left: (zone.leftPer || 0) * container.width,
|
||||
top: (zone.topPer || 0) * container.height,
|
||||
width: (zone.widthPer || 0) * container.width,
|
||||
height: (zone.heightPer || 0) * container.height
|
||||
}
|
||||
})
|
||||
// 矫正
|
||||
let changeSetting = {}
|
||||
changeSetting.left = setting.leftPer * container.width + moveX
|
||||
changeSetting.top = setting.topPer * container.height + moveY
|
||||
changeSetting.width = setting.widthPer * container.width
|
||||
changeSetting.height = setting.heightPer * container.height
|
||||
// 碰撞检测
|
||||
for (let i = 0, len = currentzones.length; i < len; i++) {
|
||||
if (currentIndex !== i && _.handleEgdeCollisions(currentzones[i], changeSetting)) {
|
||||
collision = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
el.style.transform = `translate(${moveX}px, ${moveY}px)`
|
||||
}
|
||||
|
||||
function handleMouseUp () {
|
||||
if (flag) {
|
||||
flag = false
|
||||
el.style.transform = 'translate(0, 0)'
|
||||
if (!collision) {
|
||||
vnode.context.changeInfo({
|
||||
topPer,
|
||||
leftPer
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Show the info
|
||||
vnode.context.handlehideZone(false)
|
||||
|
||||
window.removeEventListener('mousemove', handleChange)
|
||||
window.removeEventListener('mouseup', handleMouseUp)
|
||||
}
|
||||
}
|
||||
|
||||
el.$destroy = () => el.removeEventListener('mousedown', handleMouseDown)
|
||||
},
|
||||
unbind: function (el) {
|
||||
el.$destroy()
|
||||
}
|
||||
}
|
||||
7
manager/src/components/hotzone/index.js
Normal file
7
manager/src/components/hotzone/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import hotzone from './components/Hotzone.vue'
|
||||
|
||||
hotzone.install = (Vue) => {
|
||||
Vue.component(hotzone.name, hotzone)
|
||||
}
|
||||
|
||||
export default hotzone
|
||||
274
manager/src/components/hotzone/utils/index.js
Normal file
274
manager/src/components/hotzone/utils/index.js
Normal file
@@ -0,0 +1,274 @@
|
||||
let _ = {
|
||||
MIN_LIMIT: 48, // Min size of zone
|
||||
DECIMAL_PLACES: 4 // Hotzone positioning decimal point limit number of digits
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a power result of 10 for the power of the constant
|
||||
* @return {Number}
|
||||
*/
|
||||
_.getMultiple = (decimalPlaces = _.DECIMAL_PLACES) => {
|
||||
return Math.pow(10, decimalPlaces)
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit decimal places
|
||||
* @param {Number} num
|
||||
* @return {Number}
|
||||
*/
|
||||
_.decimalPoint = (val = 0) => { // 处理js小数点计算不精确问题,先放再缩小
|
||||
return Math.round(val * _.getMultiple()) / _.getMultiple() || 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Get element width and height
|
||||
* @param {Object} elem
|
||||
* @return {Object}
|
||||
*/
|
||||
_.getOffset = (elem = {}) => ({
|
||||
width: elem.clientWidth || 0,
|
||||
height: elem.clientHeight || 0
|
||||
})
|
||||
|
||||
/**
|
||||
* Get pageX
|
||||
* @param {Object} e
|
||||
* @return {Number}
|
||||
*/
|
||||
_.getPageX = (e) => ('pageX' in e) ? e.pageX : e.touches[0].pageX
|
||||
|
||||
/**
|
||||
* Get pageY
|
||||
* @param {Object} e
|
||||
* @return {Number}
|
||||
*/
|
||||
_.getPageY = (e) => ('pageY' in e) ? e.pageY : e.touches[0].pageY
|
||||
|
||||
/**
|
||||
* Gets the abscissa value of the mouse click relative to the target node
|
||||
* @param {Object} e
|
||||
* @param {Object} container
|
||||
* @return {Number}
|
||||
*/
|
||||
_.getDistanceX = (e, container) =>
|
||||
_.getPageX(e) - (container.getBoundingClientRect().left + window.pageXOffset)
|
||||
|
||||
/**
|
||||
* Gets the ordinate value of the mouse click relative to the target node
|
||||
* @param {Object} e
|
||||
* @param {Object} container
|
||||
* @return {Number}
|
||||
*/
|
||||
_.getDistanceY = (e, container) =>
|
||||
_.getPageY(e) - (container.getBoundingClientRect().top + window.pageYOffset)
|
||||
|
||||
// 检测区域是否有碰撞 true 有碰撞交集 ,false 无碰撞
|
||||
_.handleEgdeCollisions = (rect1, rect2) => {
|
||||
const l1 = { left: rect1.left, top: rect1.top }
|
||||
const r1 = { left: rect1.left + rect1.width, top: rect1.top + rect1.height }
|
||||
const l2 = { left: rect2.left, top: rect2.top }
|
||||
const r2 = { left: rect2.left + rect2.width, top: rect2.top + rect2.height }
|
||||
return !(
|
||||
l1.left > r2.left ||
|
||||
l2.left > r1.left ||
|
||||
l1.top > r2.top ||
|
||||
l2.top > r1.top
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Treatment of boundary conditions when changing the size of the hotzone 改变热区大小时边界条件的处理(如果要避免热区重叠,代码要加载这里)
|
||||
* @param {Object} itemInfo
|
||||
* @param {Object} styleInfo
|
||||
* @param {Object} container
|
||||
*/
|
||||
_.dealEdgeValue = (itemInfo, styleInfo, container, zones, currentIndex = zones.length - 1) => {
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(styleInfo, "left") && styleInfo.left < 0) {
|
||||
styleInfo.left = 0
|
||||
styleInfo.width = itemInfo.width + itemInfo.left
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(styleInfo, "top") && styleInfo.top < 0) {
|
||||
styleInfo.top = 0
|
||||
styleInfo.height = itemInfo.height + itemInfo.top
|
||||
}
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(styleInfo, "left") && Object.prototype.hasOwnProperty.call(styleInfo, "width")) {
|
||||
if (itemInfo.left + styleInfo.width > container.width) {
|
||||
styleInfo.width = container.width - itemInfo.left
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(styleInfo, "top") && Object.prototype.hasOwnProperty.call(styleInfo, "height")) {
|
||||
if (itemInfo.top + styleInfo.height > container.height) {
|
||||
styleInfo.height = container.height - itemInfo.top
|
||||
}
|
||||
}
|
||||
// 与其他热区重叠,则修正 检测是否发生碰撞
|
||||
if (zones.length > 1) {
|
||||
let currentzones = JSON.parse(JSON.stringify(zones)).map((zone) => {
|
||||
return {
|
||||
left: (zone.leftPer || 0) * container.width,
|
||||
top: (zone.topPer || 0) * container.height,
|
||||
width: (zone.widthPer || 0) * container.width,
|
||||
height: (zone.heightPer || 0) * container.height
|
||||
}
|
||||
})
|
||||
let current = { ...itemInfo, ...styleInfo }
|
||||
for (let i = 0, len = currentzones.length; i < len; i++) {
|
||||
if (currentIndex !== i && _.handleEgdeCollisions(currentzones[i], current)) {
|
||||
return itemInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.assign(itemInfo, styleInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle different drag points, capital letters mean: T-top,L-left,C-center,R-right,B-bottom
|
||||
* @param {Object} itemInfo
|
||||
* @param {Number} moveX
|
||||
* @param {Number} moveY
|
||||
* @return {Object}
|
||||
*/
|
||||
_.dealTL = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
|
||||
let styleInfo = {}
|
||||
let width = itemInfo.width - moveX
|
||||
let height = itemInfo.height - moveY
|
||||
|
||||
if (width >= Math.min(minLimit, itemInfo.width)) {
|
||||
Object.assign(styleInfo, {
|
||||
width,
|
||||
left: itemInfo.left + moveX
|
||||
})
|
||||
}
|
||||
|
||||
if (height >= Math.min(minLimit, itemInfo.height)) {
|
||||
Object.assign(styleInfo, {
|
||||
height,
|
||||
top: itemInfo.top + moveY
|
||||
})
|
||||
}
|
||||
|
||||
return styleInfo
|
||||
}
|
||||
|
||||
_.dealTC = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
|
||||
let styleInfo = {}
|
||||
let height = itemInfo.height - moveY
|
||||
|
||||
if (height >= Math.min(minLimit, itemInfo.height)) {
|
||||
styleInfo = {
|
||||
height,
|
||||
top: itemInfo.top + moveY
|
||||
}
|
||||
}
|
||||
|
||||
return styleInfo
|
||||
}
|
||||
|
||||
_.dealTR = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
|
||||
let styleInfo = {}
|
||||
let width = itemInfo.width + moveX
|
||||
let height = itemInfo.height - moveY
|
||||
|
||||
if (width >= Math.min(minLimit, itemInfo.width)) {
|
||||
Object.assign(styleInfo, {
|
||||
width
|
||||
})
|
||||
}
|
||||
|
||||
if (height >= Math.min(minLimit, itemInfo.height)) {
|
||||
Object.assign(styleInfo, {
|
||||
height,
|
||||
top: itemInfo.top + moveY
|
||||
})
|
||||
}
|
||||
|
||||
return styleInfo
|
||||
}
|
||||
|
||||
_.dealCL = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
|
||||
let styleInfo = {}
|
||||
let width = itemInfo.width - moveX
|
||||
|
||||
if (width >= Math.min(minLimit, itemInfo.width)) {
|
||||
Object.assign(styleInfo, {
|
||||
width,
|
||||
left: itemInfo.left + moveX
|
||||
})
|
||||
}
|
||||
|
||||
return styleInfo
|
||||
}
|
||||
|
||||
_.dealCR = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
|
||||
let styleInfo = {}
|
||||
let width = itemInfo.width + moveX
|
||||
|
||||
if (width >= Math.min(minLimit, itemInfo.width)) {
|
||||
Object.assign(styleInfo, {
|
||||
width
|
||||
})
|
||||
}
|
||||
|
||||
return styleInfo
|
||||
}
|
||||
|
||||
_.dealBL = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
|
||||
let styleInfo = {}
|
||||
let width = itemInfo.width - moveX
|
||||
let height = itemInfo.height + moveY
|
||||
|
||||
if (width >= Math.min(minLimit, itemInfo.width)) {
|
||||
Object.assign(styleInfo, {
|
||||
width,
|
||||
left: itemInfo.left + moveX
|
||||
})
|
||||
}
|
||||
|
||||
if (height >= Math.min(minLimit, itemInfo.height)) {
|
||||
Object.assign(styleInfo, {
|
||||
height
|
||||
})
|
||||
}
|
||||
|
||||
return styleInfo
|
||||
}
|
||||
|
||||
_.dealBC = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
|
||||
let styleInfo = {}
|
||||
let height = itemInfo.height + moveY
|
||||
|
||||
if (height >= Math.min(minLimit, itemInfo.height)) {
|
||||
Object.assign(styleInfo, {
|
||||
height
|
||||
})
|
||||
}
|
||||
|
||||
return styleInfo
|
||||
}
|
||||
// 添加热区时,判定鼠标释放点满足一下条件时生效
|
||||
_.dealBR = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
|
||||
let styleInfo = {}
|
||||
let width = itemInfo.width + moveX
|
||||
let height = itemInfo.height + moveY
|
||||
if (width >= Math.min(minLimit, itemInfo.width)) {
|
||||
// 改变后的宽度 >= min(之前宽度,内置的最小宽度标准),即生效
|
||||
Object.assign(styleInfo, {
|
||||
width
|
||||
})
|
||||
}
|
||||
|
||||
if (height >= Math.min(minLimit, itemInfo.height)) {
|
||||
// 改变后的高度 大于等于 Min(最小高度,之前高度)时,生效
|
||||
Object.assign(styleInfo, {
|
||||
height
|
||||
})
|
||||
}
|
||||
|
||||
return styleInfo
|
||||
}
|
||||
|
||||
export default _
|
||||
55
manager/src/components/i18n-translate.vue
Normal file
55
manager/src/components/i18n-translate.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div>
|
||||
<Button @click="enable = true">语言设定</Button>
|
||||
<Modal v-model="enable" draggable sticky scrollable :mask="false" :title="title">
|
||||
|
||||
<Tabs closable type="card" @on-tab-remove="handleTabRemove" :value="language[0].title">
|
||||
<TabPane v-for="(item,index) in language" :key="index" :label="item.title" :name="item.title">
|
||||
<Input v-model="item.___i18n" />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {language} from "./languages";
|
||||
export default {
|
||||
/**
|
||||
* tabs 循环的语言内容格式 [{'title':'test','value':'val'}]
|
||||
*/
|
||||
props: {
|
||||
value: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
language: {
|
||||
handler(val) {
|
||||
this.$emit("language", { language: [...val], val: this.value });
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
language,
|
||||
tabVal: "",
|
||||
enable: false, //是否开启modal
|
||||
title: "转换语言",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 删除tab标签将没有用的语音进行删除
|
||||
handleTabRemove(tab) {
|
||||
this.language = this.language.filter((item) => {
|
||||
return item.value != tab;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
1
manager/src/components/languages.js
Normal file
1
manager/src/components/languages.js
Normal file
@@ -0,0 +1 @@
|
||||
export const language = [{ title: "中文",value:1 }, { title: "英文" ,value:2}];
|
||||
249
manager/src/components/lili-dialog/goods-dialog.vue
Normal file
249
manager/src/components/lili-dialog/goods-dialog.vue
Normal file
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div class="wap-content">
|
||||
<div class="query-wrapper">
|
||||
<div class="query-item">
|
||||
<div>搜索范围</div>
|
||||
<Input placeholder="商品名称" @on-clear="goodsData=[]; goodsParams.goodsName=''; goodsParams.pageNumber = 1; getQueryGoodsList()" @on-enter="()=>{goodsData=[];goodsParams.pageNumber =1; getQueryGoodsList();}" icon="ios-search" clearable
|
||||
style="width: 150px" v-model="goodsParams.goodsName" />
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<Cascader v-model="category" placeholder="请选择商品分类" style="width: 250px" :data="skuList"></Cascader>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<Button type="primary" @click="goodsData=[]; getQueryGoodsList();" icon="ios-search">搜索</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="positon:retavle;">
|
||||
<Scroll class="wap-content-list" :on-reach-bottom="handleReachBottom" :distance-to-edge="[3,3]">
|
||||
<div class="wap-content-item" :class="{ active: item.selected }" @click="checkedGoods(item, index)" v-for="(item, index) in goodsData" :key="index">
|
||||
<div>
|
||||
<img :src="item.thumbnail" alt="" />
|
||||
</div>
|
||||
<div class="wap-content-desc">
|
||||
<div class="wap-content-desc-title">{{ item.goodsName }}</div>
|
||||
<div class="wap-sku">{{ item.goodsUnit }}</div>
|
||||
<div class="wap-sku"><Tag :color="item.salesModel === 'RETAIL' ? 'default' : 'geekblue'">{{item.salesModel === "RETAIL" ? "零售型" : "批发型"}}</Tag></div>
|
||||
<div class="wap-content-desc-bottom">
|
||||
<div>¥{{ item.price | unitPrice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Spin size="large" fix v-if="loading"></Spin>
|
||||
|
||||
<div v-if="empty" class="empty">暂无商品信息</div>
|
||||
</Scroll>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import * as API_Goods from "@/api/goods";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
type: "multiple", //单选或者多选 single multiple
|
||||
skuList: [], // 商品sku列表
|
||||
total: 0, // 商品总数
|
||||
goodsParams: { // 商品请求参数
|
||||
pageNumber: 1,
|
||||
pageSize: 18,
|
||||
order: "desc",
|
||||
goodsName: "",
|
||||
sn: "",
|
||||
categoryPath: "",
|
||||
marketEnable: "UPPER",
|
||||
authFlag: "PASS",
|
||||
sort:"createTime"
|
||||
},
|
||||
category: [], // 分类
|
||||
goodsData: [], // 商品数据
|
||||
empty: false, // 空数据
|
||||
loading: false, // 加载状态
|
||||
};
|
||||
},
|
||||
props: {
|
||||
selectedWay: {
|
||||
type: Array,
|
||||
default: function(){
|
||||
return new Array()
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
category(val) {
|
||||
this.goodsParams.categoryPath = val[2];
|
||||
},
|
||||
selectedWay: {
|
||||
handler() {
|
||||
this.$emit("selected", this.selectedWay);
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
"goodsParams.categoryPath": {
|
||||
handler: function () {
|
||||
this.goodsData = [];
|
||||
(this.goodsParams.pageNumber = 0), this.getQueryGoodsList();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
// 触底加载更多方法
|
||||
handleReachBottom() {
|
||||
setTimeout(() => {
|
||||
if (
|
||||
this.goodsParams.pageNumber * this.goodsParams.pageSize <=
|
||||
this.total
|
||||
) {
|
||||
this.goodsParams.pageNumber++;
|
||||
this.getQueryGoodsList();
|
||||
}
|
||||
}, 1500);
|
||||
},
|
||||
// 获取商品列表
|
||||
getQueryGoodsList() {
|
||||
API_Goods.getGoodsSkuData(this.goodsParams).then((res) => {
|
||||
this.initGoods(res);
|
||||
});
|
||||
},
|
||||
// 获取列表方法
|
||||
initGoods(res) {
|
||||
if (res.result.records.length !=0) {
|
||||
res.result.records.forEach((item) => {
|
||||
item.selected = false;
|
||||
item.___type = "goods"; //设置为goods让pc wap知道标识
|
||||
this.selectedWay.forEach(e => {
|
||||
if (e.id && e.id === item.id) {
|
||||
item.selected = true
|
||||
}
|
||||
})
|
||||
});
|
||||
/**
|
||||
* 解决数据请求中,滚动栏会一直上下跳动
|
||||
*/
|
||||
this.total = res.result.total;
|
||||
this.goodsData.push(...res.result.records);
|
||||
|
||||
} else {
|
||||
this.empty = true;
|
||||
}
|
||||
},
|
||||
// 查询商品
|
||||
init() {
|
||||
API_Goods.getGoodsSkuData(this.goodsParams).then((res) => {
|
||||
// 商品
|
||||
this.initGoods(res);
|
||||
});
|
||||
if (localStorage.getItem('category')) {
|
||||
this.deepGroup(JSON.parse(localStorage.getItem('category')))
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.deepGroup(JSON.parse(localStorage.getItem('category')))
|
||||
},3000)
|
||||
}
|
||||
},
|
||||
|
||||
deepGroup(val) {
|
||||
val.forEach((item) => {
|
||||
let childWay = []; //第二级
|
||||
// 第二层
|
||||
if (item.children) {
|
||||
item.children.forEach((child) => {
|
||||
// // 第三层
|
||||
if (child.children) {
|
||||
child.children.forEach((grandson, index, arr) => {
|
||||
arr[index] = {
|
||||
value: grandson.id,
|
||||
label: grandson.name,
|
||||
children: "",
|
||||
};
|
||||
});
|
||||
}
|
||||
let children = {
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
children: child.children,
|
||||
};
|
||||
childWay.push(children);
|
||||
});
|
||||
}
|
||||
// 第一层
|
||||
let way = {
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
children: childWay,
|
||||
};
|
||||
|
||||
this.skuList.push(way);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击商品
|
||||
*/
|
||||
checkedGoods(val, index) {
|
||||
// 如果单选的话
|
||||
if (this.type != "multiple") {
|
||||
this.goodsData.forEach((item) => {
|
||||
item.selected = false;
|
||||
});
|
||||
this.selectedWay = [];
|
||||
val.selected = true;
|
||||
this.selectedWay.push(val);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (val.selected == false) {
|
||||
val.selected = true;
|
||||
this.selectedWay.push(val);
|
||||
} else {
|
||||
val.selected = false;
|
||||
for (let i = 0; i<this.selectedWay.length; i++ ) {
|
||||
if (this.selectedWay[i].id===val.id) {
|
||||
this.selectedWay.splice(i,1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import "./style.scss";
|
||||
.wap-content {
|
||||
width: 100%;
|
||||
}
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
width: 100%;
|
||||
}
|
||||
.wap-content {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
}
|
||||
.wap-content-list {
|
||||
position: relative;
|
||||
}
|
||||
.wap-content-item {
|
||||
width: 210px;
|
||||
margin: 10px 7px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
// .wap-content-item{
|
||||
|
||||
// }
|
||||
.active {
|
||||
background: url("../../assets/selected.png") no-repeat;
|
||||
background-position: right;
|
||||
background-size: 10%;
|
||||
}
|
||||
</style>
|
||||
83
manager/src/components/lili-dialog/index.vue
Normal file
83
manager/src/components/lili-dialog/index.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<Modal :styles="{ top: '120px' }" width="1160" @on-cancel="clickClose" @on-ok="clickOK" v-model="flag" :mask-closable="false" scrollable>
|
||||
<template v-if="flag">
|
||||
<goodsDialog @selected="(val) => {goodsData = val;}"
|
||||
v-if="goodsFlag" ref="goodsDialog" :selectedWay='goodsData'/>
|
||||
<linkDialog @selectedLink="(val) => { linkData = val; }" v-else class="linkDialog" />
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
<script>
|
||||
import goodsDialog from "./goods-dialog";
|
||||
import linkDialog from "./link-dialog";
|
||||
export default {
|
||||
components: {
|
||||
goodsDialog,
|
||||
linkDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
goodsFlag: false, // 是否商品选择器
|
||||
goodsData: [], //选择的商品
|
||||
linkData: "", //选择的链接
|
||||
flag: false, // modal显隐
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clearGoodsSelected(){
|
||||
this.goodsData = []
|
||||
},
|
||||
// 关闭弹窗
|
||||
clickClose() {
|
||||
this.$emit("closeFlag", false);
|
||||
this.goodsFlag = false;
|
||||
},
|
||||
// 单选商品
|
||||
singleGoods() {
|
||||
var timer = setInterval(() => {
|
||||
if (this.$refs.goodsDialog) {
|
||||
this.$refs.goodsDialog.type = "single";
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
// 点击确认
|
||||
clickOK() {
|
||||
if (this.goodsFlag) {
|
||||
this.$emit("selectedGoodsData", this.goodsData);
|
||||
} else {
|
||||
this.$emit("selectedLink", this.linkData);
|
||||
}
|
||||
this.clickClose();
|
||||
},
|
||||
// 打开组件方法
|
||||
open(type, mutiple) {
|
||||
this.flag = true;
|
||||
if (type == "goods") {
|
||||
this.goodsFlag = true;
|
||||
if (mutiple) {
|
||||
this.singleGoods()
|
||||
}
|
||||
} else {
|
||||
this.goodsFlag = false;
|
||||
}
|
||||
|
||||
},
|
||||
// 关闭组件
|
||||
close() {
|
||||
this.flag = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
/deep/ .ivu-modal {
|
||||
overflow: hidden;
|
||||
height: 650px !important;
|
||||
}
|
||||
/deep/ .ivu-modal-body {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
85
manager/src/components/lili-dialog/link-dialog.vue
Normal file
85
manager/src/components/lili-dialog/link-dialog.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<Tabs :value="wap[0].title" class="tabs">
|
||||
<TabPane
|
||||
:label="item.title"
|
||||
:name="item.title"
|
||||
@click="clickTag(item, i)"
|
||||
v-for="(item, i) in wap"
|
||||
:key="i"
|
||||
>
|
||||
<component
|
||||
ref="lili-component"
|
||||
:is="templateWay[item.name]"
|
||||
@selected="
|
||||
(val) => {
|
||||
changed = val;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import wap from "./wap.js";
|
||||
import goodsDialog from "./goods-dialog";
|
||||
import templateWay from "./template/index";
|
||||
export default {
|
||||
components: {
|
||||
goodsDialog,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
templateWay, // 模板数据
|
||||
changed: "", // 变更模板
|
||||
selected: 0, // 已选数据
|
||||
selectedLink: "", //选中的链接
|
||||
wap, // tab标签
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
changed: {
|
||||
handler(val) {
|
||||
this.$emit("selectedLink", val[0]); //因为是单选,所以直接返回第一个
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs["lili-component"][0].type = "single"; //商品页面设置成为单选
|
||||
});
|
||||
|
||||
this.wap.forEach((item) => {
|
||||
if (item) {
|
||||
item.selected = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import "./style.scss";
|
||||
.wap-content-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.wap-flex {
|
||||
margin: 2px;
|
||||
}
|
||||
.tabs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .ivu-modal {
|
||||
overflow: hidden;
|
||||
height: 650px !important;
|
||||
}
|
||||
/deep/ .ivu-modal-body {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
112
manager/src/components/lili-dialog/style.scss
Normal file
112
manager/src/components/lili-dialog/style.scss
Normal file
@@ -0,0 +1,112 @@
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
.wap-list {
|
||||
flex: 2;
|
||||
text-align: center;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
>.wap-list,
|
||||
|
||||
.wap-content {
|
||||
flex: 8;
|
||||
}
|
||||
}
|
||||
|
||||
.wap-sku {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.query-wrapper {
|
||||
display: flex;
|
||||
margin: 8px 0;
|
||||
|
||||
>.query-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
>* {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .ivu-scroll-container {
|
||||
width: 100% !important;
|
||||
height: 400px !important;
|
||||
}
|
||||
|
||||
/deep/ .ivu-scroll-content {
|
||||
/* */
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.wap-content-list {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.wap-item {
|
||||
padding: 10px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wap-item:hover {
|
||||
background: #ededed;
|
||||
}
|
||||
|
||||
|
||||
.wap-content-item {
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
height: 100px;
|
||||
padding: 2px;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
margin: 10px;
|
||||
|
||||
/deep/ img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wap-content-desc {
|
||||
width: 180px;
|
||||
padding: 8px;
|
||||
|
||||
>.wap-content-desc-title {
|
||||
display: -webkit-box;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
>.wap-content-desc-bottom {
|
||||
font-size: 12px;
|
||||
padding: 4px 0;
|
||||
color: #999;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
>div:nth-of-type(1) {
|
||||
color: $theme_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
118
manager/src/components/lili-dialog/template/category.vue
Normal file
118
manager/src/components/lili-dialog/template/category.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<!-- 一级分类 -->
|
||||
<div class="list">
|
||||
<div class="list-item" :class="{active:parentIndex === cateIndex}" @click="handleClickChild(cate,cateIndex)" v-for="(cate,cateIndex) in categoryList" :key="cateIndex">
|
||||
{{cate.name}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 二级分类 -->
|
||||
<div class="list">
|
||||
<div class="list-item" :class="{active:secondIndex === secondI}" @click="handleClickSecondChild(second,secondI)" v-if="secondLevel.length != 0" v-for="(second,secondI) in secondLevel"
|
||||
:key="secondI">
|
||||
{{second.name}}
|
||||
</div>
|
||||
</div>
|
||||
<!--三级分类 -->
|
||||
<div class="list">
|
||||
<div class="list-item" :class="{active:thirdIndex === thirdI}" @click="handleClickthirdChild(third,thirdI)" v-if="thirdLevel.length != 0" v-for="(third,thirdI) in thirdLevel" :key="thirdI">
|
||||
{{third.name}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
parentIndex: '', // 分类父级下标
|
||||
secondIndex: '', // 分类二级下标
|
||||
thirdIndex: '', // 分类三级下标
|
||||
categoryList: [], // 分类列表一级数据
|
||||
secondLevel: [], // 分类列表二级数据
|
||||
thirdLevel: [], // 分类列表三级数据
|
||||
selectedData: "", // 已选分类数据
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
// 点击一级
|
||||
handleClickChild(item, index) {
|
||||
this.parentIndex = index;
|
||||
this.secondLevel = item.children;
|
||||
item.___type = "category";
|
||||
item.allId = item.id;
|
||||
|
||||
this.secondIndex = '';
|
||||
this.thirdIndex = '';
|
||||
this.thirdLevel = []
|
||||
this.$emit("selected", [item]);
|
||||
// 点击第一级的时候默认显示第二级第一个
|
||||
// this.handleClickSecondChild(item.children, 0);
|
||||
},
|
||||
// 点击二级
|
||||
handleClickSecondChild(second, index) {
|
||||
second.___type = "category";
|
||||
second.allId = `${second.parentId},${second.id}`
|
||||
|
||||
this.secondIndex = index;
|
||||
this.thirdLevel = second.children;
|
||||
this.thirdIndex = '';
|
||||
this.$emit("selected", [second]);
|
||||
// this.handleClickthirdChild(second.children[0], 0);
|
||||
},
|
||||
// 点击三级
|
||||
handleClickthirdChild(item, index) {
|
||||
item.___type = "category";
|
||||
item.allId = `${this.categoryList[this.parentIndex].id},${item.parentId},${item.id}`
|
||||
this.$emit("selected", [item]);
|
||||
this.thirdIndex = index;
|
||||
},
|
||||
init() {
|
||||
let category = JSON.parse(localStorage.getItem('category'))
|
||||
if (category) {
|
||||
category.forEach((item) => {
|
||||
item.___type = "category";
|
||||
});
|
||||
this.categoryList = category;
|
||||
// this.handleClickChild(category[0], 0);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
category = JSON.parse(localStorage.getItem('category'))
|
||||
category.forEach((item) => {
|
||||
item.___type = "category";
|
||||
});
|
||||
this.categoryList = category;
|
||||
// this.handleClickChild(category[0], 0);
|
||||
},3000)
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
width: 30%;
|
||||
margin: 0 1.5%;
|
||||
height: 400px;
|
||||
overflow: auto;
|
||||
> .list-item {
|
||||
padding: 10px;
|
||||
transition: 0.35s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.list-item:hover {
|
||||
background: #ededed;
|
||||
}
|
||||
}
|
||||
.active {
|
||||
background: #ededed;
|
||||
}
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
15
manager/src/components/lili-dialog/template/index.js
Normal file
15
manager/src/components/lili-dialog/template/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
import category from './category.vue'
|
||||
import shops from './shops.vue'
|
||||
import marketing from './marketing.vue'
|
||||
import pages from './pages.vue'
|
||||
import goods from '../goods-dialog.vue'
|
||||
import other from './other.vue'
|
||||
export default {
|
||||
pages,
|
||||
marketing,
|
||||
shops,
|
||||
category,
|
||||
goods,
|
||||
other,
|
||||
}
|
||||
460
manager/src/components/lili-dialog/template/marketing.vue
Normal file
460
manager/src/components/lili-dialog/template/marketing.vue
Normal file
@@ -0,0 +1,460 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div class="list">
|
||||
<div
|
||||
class="list-item"
|
||||
v-for="(item, index) in Object.keys(promotionList)"
|
||||
:key="index"
|
||||
@click="clickPromotion(item, index)"
|
||||
:class="{ active: selectedIndex == index }"
|
||||
>
|
||||
{{ typeOption(item).title }}
|
||||
</div>
|
||||
|
||||
<!-- <div class="list-item" >暂无活动</div> -->
|
||||
</div>
|
||||
<div class="content">
|
||||
<div v-if="showPromotionList">
|
||||
<!-- <div class="search-views">
|
||||
<Input v-model="value11" disabled class="search">
|
||||
<span slot="prepend">店铺名称</span>
|
||||
</Input>
|
||||
<Button type="primary">选择</Button>
|
||||
|
||||
</div> -->
|
||||
|
||||
<div class="tables">
|
||||
<Table
|
||||
height="350"
|
||||
border
|
||||
tooltip
|
||||
:loading="loading"
|
||||
:columns="activeColumns"
|
||||
:data="showPromotionList"
|
||||
></Table>
|
||||
|
||||
<Page
|
||||
@on-change="
|
||||
(val) => {
|
||||
params.pageNumber = val;
|
||||
}
|
||||
"
|
||||
:current="params.pageNumber"
|
||||
:page-size="params.pageSize"
|
||||
class="mt_10"
|
||||
:total="Number(totals)"
|
||||
size="small"
|
||||
show-elevator
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {
|
||||
getAllPromotion,
|
||||
getPromotionSeckill,
|
||||
getPromotionGoods,
|
||||
} from "@/api/promotion";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
totals: "", // 总数
|
||||
loading: true, //表格请求数据为true
|
||||
promotionList: "", // 活动列表
|
||||
selectedIndex: 0, //左侧菜单选择
|
||||
promotions: "", //选中的活动key
|
||||
index: 999, // 已选下标
|
||||
params: {
|
||||
// 请求参数
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
pintuanColumns: [
|
||||
// 表头
|
||||
{
|
||||
title: "活动标题",
|
||||
key: "title",
|
||||
tooltip: true,
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
title: "商品名称",
|
||||
key: "goodsName",
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: "店铺名称",
|
||||
key: "storeName",
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: "开始时间",
|
||||
key: "startTime",
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: "结束时间",
|
||||
key: "endTime",
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
fixed: "right",
|
||||
width: 100,
|
||||
render: (h, params) => {
|
||||
return h("div", [
|
||||
h(
|
||||
"Button",
|
||||
{
|
||||
props: {
|
||||
// type: this.index == params.index ? "primary" : "",
|
||||
size: "small",
|
||||
},
|
||||
on: {
|
||||
click: () => {
|
||||
this.selectedPromotion(params);
|
||||
},
|
||||
},
|
||||
},
|
||||
this.index == params.index ? "已选" : "选择"
|
||||
),
|
||||
]);
|
||||
},
|
||||
},
|
||||
],
|
||||
seckillColumns: [
|
||||
{
|
||||
title: "商品名称",
|
||||
key: "goodsName",
|
||||
tooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: "店铺名称",
|
||||
key: "storeName",
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: "活动时间",
|
||||
key: "timeLine",
|
||||
tooltip: true,
|
||||
render: (h, params) => {
|
||||
return h("div", {}, `${params.row.timeLine}点`);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "原价",
|
||||
key: "originalPrice",
|
||||
tooltip: true,
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"div",
|
||||
{},
|
||||
this.$options.filters.unitPrice(params.row.originalPrice)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "现价",
|
||||
key: "price",
|
||||
tooltip: true,
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
style: {},
|
||||
},
|
||||
this.$options.filters.unitPrice(params.row.price, "¥")
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
key: "promotionApplyStatus",
|
||||
tooltip: true,
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
style: {},
|
||||
},
|
||||
params.row.promotionApplyStatus == "APPLY"
|
||||
? "申请"
|
||||
: params.row.promotionApplyStatus == "PASS"
|
||||
? "通过"
|
||||
: "拒绝"
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: 100,
|
||||
fixed: "right",
|
||||
render: (h, params) => {
|
||||
return h("div", [
|
||||
h(
|
||||
"Button",
|
||||
{
|
||||
props: {
|
||||
// type: this.index == params.index ? "primary" : "",
|
||||
size: "small",
|
||||
},
|
||||
on: {
|
||||
click: () => {
|
||||
this.selectedPromotion(params);
|
||||
},
|
||||
},
|
||||
},
|
||||
this.index == params.index ? "已选" : "选择"
|
||||
),
|
||||
]);
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
activeColumns: [], // 活动表头
|
||||
|
||||
columns: [
|
||||
{
|
||||
title: "活动标题",
|
||||
key: "title",
|
||||
tooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: "商品名称",
|
||||
key: "goodsName",
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: "活动开始时间",
|
||||
key: "startTime",
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: "活动结束时间",
|
||||
key: "endTime",
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
fixed: "right",
|
||||
width: 100,
|
||||
render: (h, params) => {
|
||||
return h("div", [
|
||||
h(
|
||||
"Button",
|
||||
{
|
||||
props: {
|
||||
// type: this.index == params.index ? "primary" : "",
|
||||
size: "small",
|
||||
},
|
||||
on: {
|
||||
click: () => {
|
||||
this.selectedPromotion(params);
|
||||
},
|
||||
},
|
||||
},
|
||||
this.index == params.index ? "已选" : "选择"
|
||||
),
|
||||
]);
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
promotionData: "", //商品集合
|
||||
|
||||
showPromotionList: [], //显示当前促销的商品
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
watch: {
|
||||
params: {
|
||||
handler() {
|
||||
this.index = 999;
|
||||
this.typeOption(this.promotions) &&
|
||||
this.typeOption(this.promotions).methodsed();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
sortGoods(type) {
|
||||
this.loading = false;
|
||||
this.params.pageNumber - 1;
|
||||
this.showPromotionList = this.promotionList[type];
|
||||
},
|
||||
typeOption(type) {
|
||||
// 活动选项
|
||||
switch (type) {
|
||||
case "FULL_DISCOUNT":
|
||||
return {
|
||||
title: "满减",
|
||||
methodsed: () => {
|
||||
this.showPromotionList = [];
|
||||
this.activeColumns = this.pintuanColumns;
|
||||
|
||||
this.sortGoods("FULL_DISCOUNT");
|
||||
},
|
||||
};
|
||||
case "PINTUAN":
|
||||
return {
|
||||
title: "拼团",
|
||||
methodsed: (id) => {
|
||||
this.showPromotionList = [];
|
||||
this.activeColumns = this.pintuanColumns;
|
||||
this.sortGoods("PINTUAN");
|
||||
},
|
||||
};
|
||||
|
||||
case "KANJIA":
|
||||
return {
|
||||
title: "砍价",
|
||||
methodsed: (id) => {
|
||||
this.showPromotionList = [];
|
||||
this.activeColumns = this.pintuanColumns;
|
||||
this.sortGoods("KANJIA");
|
||||
},
|
||||
};
|
||||
case "SECKILL":
|
||||
return {
|
||||
title: "秒杀",
|
||||
methodsed: () => {
|
||||
this.showPromotionList = [];
|
||||
this.activeColumns = this.seckillColumns;
|
||||
this.sortGoods("SECKILL");
|
||||
},
|
||||
};
|
||||
case "COUPON":
|
||||
return {
|
||||
title: "优惠券",
|
||||
methodsed: () => {
|
||||
this.showPromotionList = [];
|
||||
this.activeColumns = this.pintuanColumns;
|
||||
this.sortGoods("COUPON");
|
||||
},
|
||||
};
|
||||
case "POINTS_GOODS":
|
||||
return {
|
||||
title: "积分商品",
|
||||
methodsed: () => {
|
||||
this.showPromotionList = [];
|
||||
this.activeColumns = this.pintuanColumns;
|
||||
this.sortGoods("POINTS_GOODS");
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 选择活动
|
||||
selectedPromotion(val) {
|
||||
val.row.___type = "marketing";
|
||||
val.row.___promotion = this.promotions;
|
||||
this.$emit("selected", [val.row]);
|
||||
|
||||
this.index = val.index;
|
||||
},
|
||||
// 获取所有营销的活动
|
||||
async init() {
|
||||
let res = await getAllPromotion();
|
||||
if (res.success) {
|
||||
this.loading = false;
|
||||
this.getPromotion(res);
|
||||
// this.clickPromotion(this.typeOption[Object.keys(res.result)[0]], 0);
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
getPromotion(res) {
|
||||
if (res.result) {
|
||||
this.promotionList = res.result;
|
||||
this.typeOption(Object.keys(res.result)[0]).methodsed();
|
||||
}
|
||||
|
||||
// if (Object.keys(res.result).length) {
|
||||
// this.typeOption[Object.keys(res.result)[0]].methodsed(
|
||||
// this.promotionList[Object.keys(res.result)[0]].id
|
||||
// );
|
||||
// }
|
||||
},
|
||||
|
||||
// 点击某个活动查询活动列表
|
||||
clickPromotion(val, i) {
|
||||
this.promotions = val;
|
||||
this.selectedIndex = i;
|
||||
this.params.pageNumber = 1;
|
||||
this.typeOption(val) &&
|
||||
this.typeOption(val).methodsed(this.promotionList[val].id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.search {
|
||||
width: 300px;
|
||||
}
|
||||
.page {
|
||||
margin-top: 2vh;
|
||||
text-align: right;
|
||||
}
|
||||
.time {
|
||||
font-size: 12px;
|
||||
}
|
||||
.tables {
|
||||
height: 400px;
|
||||
margin-top: 20px;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
}
|
||||
/deep/ .ivu-table-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
.list {
|
||||
margin: 0 1.5%;
|
||||
height: 400px;
|
||||
overflow: auto;
|
||||
> .list-item {
|
||||
padding: 10px;
|
||||
transition: 0.35s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.list-item:hover {
|
||||
background: #ededed;
|
||||
}
|
||||
}
|
||||
.list {
|
||||
flex: 1;
|
||||
width: auto;
|
||||
}
|
||||
.content {
|
||||
overflow: hidden;
|
||||
flex: 4;
|
||||
}
|
||||
.active {
|
||||
background: #ededed;
|
||||
}
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
.search-views {
|
||||
display: flex;
|
||||
> * {
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
145
manager/src/components/lili-dialog/template/other.vue
Normal file
145
manager/src/components/lili-dialog/template/other.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div>
|
||||
<Row :gutter="30">
|
||||
<Col
|
||||
span="4"
|
||||
v-for="(item, index) in linkList"
|
||||
:key="index"
|
||||
v-if="
|
||||
(item.title !== '拼团频道' && item.title !== '签到') ||
|
||||
$route.name !== 'renovation'
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="card"
|
||||
:class="{ active: selectedIndex == index }"
|
||||
@click="handleLink(item, index)"
|
||||
>
|
||||
<Icon size="24" :type="item.icon" />
|
||||
<p>{{ item.title }}</p>
|
||||
</div>
|
||||
</Col>
|
||||
<!-- 外部链接,只有pc端跳转 -->
|
||||
<Col span="4">
|
||||
<div
|
||||
class="card"
|
||||
:class="{ active: selectedIndex == linkList.length }"
|
||||
@click="handleLink(linkItem, linkList.length)"
|
||||
>
|
||||
<Icon size="24" :type="linkItem.icon" />
|
||||
<p>{{ linkItem.title }}</p>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
linkList: [
|
||||
// 链接列表
|
||||
{
|
||||
title: "首页",
|
||||
icon: "md-home",
|
||||
___type: "home",
|
||||
},
|
||||
{
|
||||
title: "购物车",
|
||||
icon: "md-cart",
|
||||
___type: "cart",
|
||||
},
|
||||
{
|
||||
title: "收藏商品",
|
||||
icon: "md-heart",
|
||||
___type: "collection",
|
||||
},
|
||||
{
|
||||
title: "我的订单",
|
||||
icon: "md-document",
|
||||
___type: "order",
|
||||
},
|
||||
{
|
||||
title: "个人中心",
|
||||
icon: "md-person",
|
||||
___type: "user",
|
||||
},
|
||||
{
|
||||
title: "拼团频道",
|
||||
icon: "md-flame",
|
||||
___type: "group",
|
||||
},
|
||||
{
|
||||
title: "秒杀频道",
|
||||
icon: "md-flame",
|
||||
___type: "seckill",
|
||||
},
|
||||
{
|
||||
title: "领券中心",
|
||||
icon: "md-pricetag",
|
||||
___type: "coupon",
|
||||
},
|
||||
{
|
||||
title: "签到",
|
||||
icon: "md-happy",
|
||||
___type: "sign",
|
||||
},
|
||||
{
|
||||
title: "小程序直播",
|
||||
icon: "ios-videocam",
|
||||
___type: "live",
|
||||
},
|
||||
{
|
||||
title: "砍价",
|
||||
icon: "md-share-alt",
|
||||
___type: "kanjia",
|
||||
},
|
||||
{
|
||||
title: "积分商城",
|
||||
icon: "ios-basket",
|
||||
___type: "point",
|
||||
},
|
||||
],
|
||||
linkItem: {
|
||||
title: "外部链接",
|
||||
icon: "ios-link",
|
||||
___type: "link",
|
||||
url: "",
|
||||
},
|
||||
linkVisible: false, // 是否显示外部链接
|
||||
selectedIndex: 9999999, // 已选index
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleLink(val, index) {
|
||||
val = { ...val, ___type: "other" };
|
||||
this.selectedIndex = index;
|
||||
this.$emit("selected", [val]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../style.scss";
|
||||
.card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
transition: 0.35s;
|
||||
cursor: pointer;
|
||||
/deep/ p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
border: 1px solid #ededed;
|
||||
}
|
||||
.card:hover {
|
||||
background: #ededed;
|
||||
}
|
||||
.active {
|
||||
background: #ededed;
|
||||
}
|
||||
</style>
|
||||
80
manager/src/components/lili-dialog/template/pages.vue
Normal file
80
manager/src/components/lili-dialog/template/pages.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<!-- TODO 目前数据少暂且不用 -->
|
||||
<!-- <div class="list">
|
||||
<div class="list-item active">
|
||||
文章页
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="content">
|
||||
<Article @callbacked="callbackArticle" :selected="true" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Article from "@/views/page/article-manage/articleList.vue";
|
||||
export default {
|
||||
components: {
|
||||
Article,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
callbackArticle(val) {
|
||||
val.___type = "pages";
|
||||
|
||||
val.___path = "/pages/passport/article";
|
||||
|
||||
this.$emit("selected", [val]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
/deep/ .ivu-card-body {
|
||||
height: 414px;
|
||||
overflow: auto;
|
||||
}
|
||||
.ivu-table-wrapper ivu-table-wrapper-with-border {
|
||||
height: 300px !important;
|
||||
}
|
||||
.list {
|
||||
margin: 0 1.5%;
|
||||
height: 400px;
|
||||
overflow: auto;
|
||||
> .list-item {
|
||||
padding: 10px;
|
||||
transition: 0.35s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.list-item:hover {
|
||||
background: #ededed;
|
||||
}
|
||||
}
|
||||
.list {
|
||||
flex: 2;
|
||||
width: auto;
|
||||
}
|
||||
.content {
|
||||
overflow: hidden;
|
||||
flex: 8;
|
||||
height: 431px;
|
||||
}
|
||||
.active {
|
||||
background: #ededed;
|
||||
}
|
||||
.wrapper {
|
||||
height: 416px;
|
||||
overflow: hidden;
|
||||
}
|
||||
/deep/ .ivu-table {
|
||||
height: 300px !important;
|
||||
overflow: auto;
|
||||
}
|
||||
/deep/ .ivu-card-body {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
110
manager/src/components/lili-dialog/template/shops.vue
Normal file
110
manager/src/components/lili-dialog/template/shops.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="shop">
|
||||
<div class="wap-content">
|
||||
<div class="query-wrapper">
|
||||
<div class="query-item">
|
||||
<div>店铺名称</div>
|
||||
<Input placeholder="请输入店铺名称" @on-clear="shopsData=[]; params.storeName=''; params.pageNumber =1; init()" @on-enter="()=>{shopsData=[]; params.pageNumber =1; init();}" icon="ios-search" clearable style="width: 150px"
|
||||
v-model="params.storeName" />
|
||||
</div>
|
||||
|
||||
<div class="query-item">
|
||||
<Button type="primary" @click="shopsData=[];params.pageNumber =1; init();" icon="ios-search">搜索</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Scroll class="wap-content-list" :on-reach-bottom="handleReachBottom" :distance-to-edge="23">
|
||||
<div class="wap-content-item" @click="clickShop(item,index)" :class="{ active:selected == index }" v-for="(item, index) in shopsData" :key="index">
|
||||
<div>
|
||||
<img class="shop-logo" :src="item.storeLogo" alt="" />
|
||||
</div>
|
||||
<div class="wap-content-desc">
|
||||
<div class="wap-content-desc-title">{{ item.storeName }}</div>
|
||||
|
||||
<div class="self-operated" :class="{'theme_color':item.selfOperated }">{{ item.selfOperated ? '自营' : '非自营' }}</div>
|
||||
<div class="wap-sku" :class="{'theme_color':(item.storeDisable === 'OPEN' ? true : false) }">{{ item.storeDisable === 'OPEN' ? '开启中' : '未开启' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Spin size="large" fix v-if="loading"></Spin>
|
||||
</Scroll>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getShopListData } from "@/api/shops.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false, // 加载状态
|
||||
total: "", // 总数
|
||||
params: { // 请求参数
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
storeDisable: "OPEN",
|
||||
storeName: "",
|
||||
},
|
||||
shopsData: [], // 店铺数据
|
||||
selected: 9999999999, //设置一个不可能选中的index
|
||||
};
|
||||
},
|
||||
watch: {},
|
||||
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
handleReachBottom() {
|
||||
setTimeout(() => {
|
||||
if (this.params.pageNumber * this.params.pageSize <= this.total) {
|
||||
this.params.pageNumber++;
|
||||
this.init();
|
||||
}
|
||||
}, 1500);
|
||||
},
|
||||
init() {
|
||||
this.loading = true;
|
||||
getShopListData(this.params).then((res) => {
|
||||
if (res.success) {
|
||||
/**
|
||||
* 解决数据请求中,滚动栏会一直上下跳动
|
||||
*/
|
||||
this.total = res.result.total;
|
||||
|
||||
this.shopsData.push(...res.result.records);
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
clickShop(val, i) {
|
||||
this.selected = i;
|
||||
val = { ...val, ___type: "shops" };
|
||||
this.$emit("selected", [val]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../style.scss";
|
||||
.shop {
|
||||
display: flex;
|
||||
}
|
||||
.self-operated {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.wap-content-list {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.shop-logo {
|
||||
object-fit: cover;
|
||||
}
|
||||
.wap-content-item {
|
||||
}
|
||||
.active {
|
||||
background: url("../../../assets/selected.png") no-repeat;
|
||||
background-position: right;
|
||||
background-size: 10%;
|
||||
}
|
||||
</style>
|
||||
33
manager/src/components/lili-dialog/wap.js
Normal file
33
manager/src/components/lili-dialog/wap.js
Normal file
@@ -0,0 +1,33 @@
|
||||
export default [
|
||||
{
|
||||
title: "商品",
|
||||
url: "0",
|
||||
openGoods: true,
|
||||
name: "goods"
|
||||
},
|
||||
{
|
||||
title: "分类",
|
||||
url: "1",
|
||||
name: "category"
|
||||
},
|
||||
{
|
||||
title: "店铺",
|
||||
url: "2",
|
||||
name: "shops"
|
||||
},
|
||||
{
|
||||
title: "活动",
|
||||
url: "3",
|
||||
name: "marketing"
|
||||
},
|
||||
{
|
||||
title: "页面",
|
||||
url: "3",
|
||||
name: "pages"
|
||||
},
|
||||
{
|
||||
title: "其他",
|
||||
url: "3",
|
||||
name: "other"
|
||||
}
|
||||
];
|
||||
18
manager/src/components/lili/circle-loading.vue
Normal file
18
manager/src/components/lili/circle-loading.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div style="display: inline-block;">
|
||||
<Icon type="ios-loading" size="18" color="#2d8cf0" class="spin-icon-load"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "circleLoading"
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.spin-icon-load {
|
||||
animation: ani-demo-spin 1s linear infinite;
|
||||
}
|
||||
</style>
|
||||
|
||||
72
manager/src/components/lili/department-choose.vue
Normal file
72
manager/src/components/lili/department-choose.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<div>
|
||||
<Cascader
|
||||
v-model="selectDep"
|
||||
:data="department"
|
||||
@on-change="handleChangeDep"
|
||||
change-on-select
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择"
|
||||
></Cascader>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { initDepartment } from "@/api/index";
|
||||
export default {
|
||||
name: "departmentChoose",
|
||||
props: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectDep: [], // 已选数据
|
||||
department: [] // 列表
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 获取部门数据
|
||||
initDepartmentData() {
|
||||
initDepartment().then(res => {
|
||||
if (res.success) {
|
||||
const arr = res.result;
|
||||
this.filterData(arr)
|
||||
this.department = arr
|
||||
}
|
||||
});
|
||||
},
|
||||
handleChangeDep(value, selectedData) {
|
||||
let departmentId = "";
|
||||
// 获取最后一个值
|
||||
if (value && value.length > 0) {
|
||||
departmentId = value[value.length - 1];
|
||||
}
|
||||
this.$emit("on-change", departmentId);
|
||||
},
|
||||
// 清空已选列表
|
||||
clearSelect() {
|
||||
this.selectDep = [];
|
||||
},
|
||||
// 处理部门数据
|
||||
filterData (data) {
|
||||
data.forEach(e => {
|
||||
e.value = e.id;
|
||||
e.label = e.title;
|
||||
if (e.children) {
|
||||
this.filterData(e.children)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initDepartmentData();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
159
manager/src/components/lili/department-tree-choose.vue
Normal file
159
manager/src/components/lili/department-tree-choose.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="display:flex;">
|
||||
<Input
|
||||
v-model="departmentTitle"
|
||||
readonly
|
||||
style="margin-right:10px;"
|
||||
:placeholder="placeholder"
|
||||
:clearable="clearable"
|
||||
@on-clear="clearSelect"
|
||||
/>
|
||||
<Poptip transfer trigger="click" placement="right" title="选择部门" width="250">
|
||||
<Button icon="md-list">选择部门</Button>
|
||||
<div slot="content">
|
||||
<Input
|
||||
v-model="searchKey"
|
||||
suffix="ios-search"
|
||||
@on-change="searchDep"
|
||||
placeholder="输入部门名搜索"
|
||||
clearable
|
||||
/>
|
||||
<div class="dep-tree-bar">
|
||||
<Tree
|
||||
:data="dataDep"
|
||||
@on-select-change="selectTree"
|
||||
></Tree>
|
||||
<Spin size="large" fix v-if="depLoading"></Spin>
|
||||
</div>
|
||||
</div>
|
||||
</Poptip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {initDepartment, searchDepartment} from "@/api/index";
|
||||
|
||||
export default {
|
||||
name: "departmentTreeChoose",
|
||||
props: {
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "点击选择部门"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
depLoading: false, // 加载状态
|
||||
departmentTitle: "", // modal标题
|
||||
searchKey: "", // 搜索关键词
|
||||
dataDep: [], // 部门列表
|
||||
selectDep: [], // 已选部门
|
||||
departmentId: [] // 部门id
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 获取部门数据
|
||||
initDepartmentData() {
|
||||
initDepartment().then(res => {
|
||||
if (res.success) {
|
||||
this.dataDep = res.result;
|
||||
}
|
||||
});
|
||||
},
|
||||
searchDep() {
|
||||
// 搜索部门
|
||||
if (this.searchKey) {
|
||||
this.depLoading = true;
|
||||
searchDepartment({title: this.searchKey}).then(res => {
|
||||
this.depLoading = false;
|
||||
if (res.success) {
|
||||
res.result.forEach(function (e) {
|
||||
if (e.status == -1) {
|
||||
e.title = "[已禁用] " + e.title;
|
||||
e.disabled = true;
|
||||
}
|
||||
});
|
||||
this.dataDep = res.result;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.initDepartmentData();
|
||||
}
|
||||
},
|
||||
// 选择回调
|
||||
selectTree(v) {
|
||||
if (v.length === 0) {
|
||||
this.$emit("on-change", null);
|
||||
this.departmentId = "";
|
||||
this.departmentTitle = "";
|
||||
return
|
||||
}
|
||||
this.departmentId = v[0].id;
|
||||
this.departmentTitle = v[0].title;
|
||||
let department = {
|
||||
departmentId: this.departmentId,
|
||||
departmentTitle: this.departmentTitle
|
||||
}
|
||||
this.$emit("on-change", department);
|
||||
},
|
||||
// 清除选中方法
|
||||
clearSelect() {
|
||||
this.departmentId = [];
|
||||
this.departmentTitle = "";
|
||||
this.initDepartmentData();
|
||||
if (this.multiple) {
|
||||
this.$emit("on-change", []);
|
||||
} else {
|
||||
this.$emit("on-change", "");
|
||||
}
|
||||
this.$emit("on-clear");
|
||||
},
|
||||
// 设置数据 回显用
|
||||
setData(ids, title) {
|
||||
this.departmentTitle = title;
|
||||
if (this.multiple) {
|
||||
this.departmentId = ids;
|
||||
} else {
|
||||
this.departmentId = [];
|
||||
this.departmentId.push(ids);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initDepartmentData();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dep-tree-bar {
|
||||
position: relative;
|
||||
min-height: 80px;
|
||||
max-height: 500px;
|
||||
overflow: auto;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.dep-tree-bar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.dep-tree-bar::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 0 2px #d1d1d1;
|
||||
background: #e4e4e4;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
168
manager/src/components/lili/set-password.vue
Normal file
168
manager/src/components/lili/set-password.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div class="set-password">
|
||||
<Poptip transfer trigger="focus" placement="right" width="250">
|
||||
<Input
|
||||
type="password"
|
||||
password
|
||||
style="width:350px;"
|
||||
:maxlength="maxlength"
|
||||
v-model="currentValue"
|
||||
@on-change="handleChange"
|
||||
:size="size"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<div :class="tipStyle" slot="content">
|
||||
<div class="words">强度 : {{strength}}</div>
|
||||
<Progress
|
||||
:percent="strengthValue"
|
||||
:status="progressStatus"
|
||||
hide-info
|
||||
style="margin: 13px 0;"
|
||||
/>
|
||||
<br />请至少输入 6 个字符。请不要使
|
||||
<br />用容易被猜到的密码。
|
||||
</div>
|
||||
</Poptip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "setPassword",
|
||||
props: {
|
||||
value: String,
|
||||
size: String,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入密码,长度为6-20个字符"
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 20
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentValue: this.value, // 当前值
|
||||
tipStyle: "password-tip-none", // 强度样式
|
||||
strengthValue: 0, // 强度等级
|
||||
progressStatus: "normal", // 进度条状态
|
||||
strength: "无", // 密码强度描述
|
||||
grade: 0 // 强度等级
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
checkStrengthValue(v) {
|
||||
// 评级制判断密码强度 最高5
|
||||
let grade = 0;
|
||||
if (/\d/.test(v)) {
|
||||
grade++; //数字
|
||||
}
|
||||
if (/[a-z]/.test(v)) {
|
||||
grade++; //小写
|
||||
}
|
||||
if (/[A-Z]/.test(v)) {
|
||||
grade++; //大写
|
||||
}
|
||||
if (/\W/.test(v)) {
|
||||
grade++; //特殊字符
|
||||
}
|
||||
if (v.length >= 10) {
|
||||
grade++;
|
||||
}
|
||||
this.grade = grade;
|
||||
return grade;
|
||||
},
|
||||
strengthChange() {
|
||||
if (!this.currentValue) {
|
||||
this.tipStyle = "password-tip-none";
|
||||
this.strength = "无";
|
||||
this.strengthValue = 0;
|
||||
return;
|
||||
}
|
||||
let grade = this.checkStrengthValue(this.currentValue);
|
||||
if (grade <= 1) {
|
||||
this.progressStatus = "wrong";
|
||||
this.tipStyle = "password-tip-weak";
|
||||
this.strength = "弱";
|
||||
this.strengthValue = 33;
|
||||
} else if (grade >= 2 && grade <= 4) {
|
||||
this.progressStatus = "normal";
|
||||
this.tipStyle = "password-tip-middle";
|
||||
this.strength = "中";
|
||||
this.strengthValue = 66;
|
||||
} else {
|
||||
this.progressStatus = "success";
|
||||
this.tipStyle = "password-tip-strong";
|
||||
this.strength = "强";
|
||||
this.strengthValue = 100;
|
||||
}
|
||||
},
|
||||
// 密码变动后回调
|
||||
handleChange(v) {
|
||||
this.strengthChange();
|
||||
this.$emit("input", this.currentValue);
|
||||
this.$emit("on-change", this.currentValue, this.grade, this.strength);
|
||||
},
|
||||
// 监听密码变动,实时回显密码强度
|
||||
setCurrentValue(value) {
|
||||
if (value === this.currentValue) {
|
||||
return;
|
||||
}
|
||||
this.currentValue = value;
|
||||
this.strengthChange();
|
||||
this.$emit("on-change", this.currentValue, this.grade, this.strength);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.setCurrentValue(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.set-password .ivu-poptip,
|
||||
.set-password .ivu-poptip-rel {
|
||||
display: block;
|
||||
}
|
||||
.password-tip-none {
|
||||
padding: 1vh 0;
|
||||
}
|
||||
|
||||
.password-tip-weak {
|
||||
padding: 1vh 0;
|
||||
|
||||
.words {
|
||||
color: #ed3f14;
|
||||
}
|
||||
}
|
||||
|
||||
.password-tip-middle {
|
||||
padding: 1vh 0;
|
||||
|
||||
.words {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
}
|
||||
|
||||
.password-tip-strong {
|
||||
padding: 1vh 0;
|
||||
|
||||
.words {
|
||||
color: #52c41a;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
176
manager/src/components/lili/upload-pic-input.vue
Normal file
176
manager/src/components/lili/upload-pic-input.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="display:flex;">
|
||||
<Input
|
||||
v-model="currentValue"
|
||||
@on-change="handleChange"
|
||||
v-show="showInput"
|
||||
:placeholder="placeholder"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:maxlength="maxlength"
|
||||
>
|
||||
<Poptip slot="append" transfer trigger="hover" title="图片预览" placement="right">
|
||||
<Icon type="md-eye" class="see-icon" />
|
||||
<div slot="content">
|
||||
<img :src="currentValue" alt="该资源不存在" style="width: 100%;margin: 0 auto;display: block;" />
|
||||
<a @click="viewImage=true" style="margin-top:5px;text-align:right;display:block">查看大图</a>
|
||||
</div>
|
||||
</Poptip>
|
||||
</Input>
|
||||
|
||||
<Upload
|
||||
:action="uploadFileUrl"
|
||||
:headers="accessToken"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleError"
|
||||
:format="['jpg','jpeg','png','gif','bmp']"
|
||||
accept=".jpg, .jpeg, .png, .gif, .bmp"
|
||||
:max-size="1024"
|
||||
:on-format-error="handleFormatError"
|
||||
:on-exceeded-size="handleMaxSize"
|
||||
:before-upload="beforeUpload"
|
||||
:show-upload-list="false"
|
||||
ref="up"
|
||||
class="upload"
|
||||
>
|
||||
<Button :loading="loading" :size="size" :disabled="disabled">上传图片</Button>
|
||||
</Upload>
|
||||
</div>
|
||||
|
||||
<Modal title="图片预览" v-model="viewImage" :styles="{top: '30px'}" draggable>
|
||||
<img :src="currentValue" alt="该资源不存在" style="width: 100%;margin: 0 auto;display: block;" />
|
||||
<div slot="footer">
|
||||
<Button @click="viewImage=false">关闭</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uploadFile } from "@/api/index";
|
||||
export default {
|
||||
name: "uploadPicInput",
|
||||
props: {
|
||||
value: String,
|
||||
size: {
|
||||
default: 'default',
|
||||
type: String
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "图片链接"
|
||||
},
|
||||
showInput: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxlength: Number,
|
||||
icon: {
|
||||
type: String,
|
||||
default: "ios-cloud-upload-outline"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
accessToken: {}, // 验证token
|
||||
currentValue: this.value, // 当前值
|
||||
loading: false, // 加载状态
|
||||
viewImage: false, // 预览图片modal
|
||||
uploadFileUrl: uploadFile // 上传地址
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
init() {
|
||||
this.accessToken = {
|
||||
accessToken: this.getStore("accessToken")
|
||||
};
|
||||
},
|
||||
// 格式校验
|
||||
handleFormatError(file) {
|
||||
this.loading = false;
|
||||
this.$Notice.warning({
|
||||
title: "不支持的文件格式",
|
||||
desc:
|
||||
"所选文件‘ " +
|
||||
file.name +
|
||||
" ’格式不正确, 请选择 .jpg .jpeg .png .gif .bmp格式文件"
|
||||
});
|
||||
},
|
||||
// 大小校验
|
||||
handleMaxSize(file) {
|
||||
this.loading = false;
|
||||
this.$Notice.warning({
|
||||
title: "文件大小过大",
|
||||
desc: "所选文件大小过大, 不得超过1M."
|
||||
});
|
||||
},
|
||||
// 上传前
|
||||
beforeUpload() {
|
||||
this.loading = true;
|
||||
return true;
|
||||
},
|
||||
// 上传成功
|
||||
handleSuccess(res, file) {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.currentValue = res.result;
|
||||
this.$emit("input", this.currentValue);
|
||||
this.$emit("on-change", this.currentValue);
|
||||
} else {
|
||||
// this.$Message.error(res.message);
|
||||
}
|
||||
},
|
||||
// 上传失败
|
||||
handleError(error, file, fileList) {
|
||||
this.loading = false;
|
||||
this.$Message.error(error.toString());
|
||||
},
|
||||
// 上传成功回显
|
||||
handleChange(v) {
|
||||
this.$emit("input", this.currentValue);
|
||||
this.$emit("on-change", this.currentValue);
|
||||
this.$attrs.rollback && this.$attrs.rollback()
|
||||
},
|
||||
// 初始值
|
||||
setCurrentValue(value) {
|
||||
if (value === this.currentValue) {
|
||||
return;
|
||||
}
|
||||
this.currentValue = value;
|
||||
this.$emit("on-change", this.currentValue);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.setCurrentValue(val);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.see-icon {
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
294
manager/src/components/lili/upload-pic-thumb.vue
Normal file
294
manager/src/components/lili/upload-pic-thumb.vue
Normal file
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="upload-pic-thumb">
|
||||
<vuedraggable
|
||||
:list="uploadList"
|
||||
:disabled="!draggable||!multiple"
|
||||
:animation="200"
|
||||
class="list-group"
|
||||
ghost-class="thumb-ghost"
|
||||
@end="onEnd"
|
||||
>
|
||||
<div class="upload-list" v-for="(item, index) in uploadList" :key="index">
|
||||
<div v-if="item.status == 'finished'" style="height:60px;">
|
||||
<img :src="item.url" />
|
||||
<div class="upload-list-cover">
|
||||
<Icon type="ios-eye-outline" @click="handleView(item.url)"></Icon>
|
||||
<Icon type="ios-trash-outline" @click="handleRemove(item)"></Icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<Progress v-if="item.showProgress" :percent="item.percentage" hide-info></Progress>
|
||||
</div>
|
||||
</div>
|
||||
</vuedraggable>
|
||||
<Upload
|
||||
ref="upload"
|
||||
:multiple="multiple"
|
||||
:show-upload-list="false"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleError"
|
||||
:format="['jpg','jpeg','png','gif']"
|
||||
:max-size="1024"
|
||||
:on-format-error="handleFormatError"
|
||||
:on-exceeded-size="handleMaxSize"
|
||||
:before-upload="handleBeforeUpload"
|
||||
type="drag"
|
||||
:action="uploadFileUrl"
|
||||
:headers="accessToken"
|
||||
style="display: inline-block;width:58px;"
|
||||
>
|
||||
<div style="width: 58px;height:58px;line-height: 58px;">
|
||||
<Icon type="md-camera" size="20"></Icon>
|
||||
</div>
|
||||
</Upload>
|
||||
</div>
|
||||
<Modal title="图片预览" v-model="viewImage" :styles="{top: '30px'}" draggable>
|
||||
<img :src="imgUrl" alt="无效的图片链接" style="width: 100%;margin: 0 auto;display: block;" />
|
||||
<div slot="footer">
|
||||
<Button @click="viewImage=false">关闭</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uploadFile } from "@/api/index";
|
||||
import vuedraggable from "vuedraggable";
|
||||
export default {
|
||||
name: "uploadPicThumb",
|
||||
components: {
|
||||
vuedraggable
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: null
|
||||
},
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
accessToken: {}, // 验证token
|
||||
uploadFileUrl: uploadFile, // 上传地址
|
||||
uploadList: [], // 上传列表
|
||||
viewImage: false, // 预览modal
|
||||
imgUrl: "" // 图片地址
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 拖拽结束事件
|
||||
onEnd() {
|
||||
this.returnValue();
|
||||
},
|
||||
// 初始化方法
|
||||
init() {
|
||||
this.setData(this.value, true);
|
||||
this.accessToken = {
|
||||
accessToken: this.getStore("accessToken")
|
||||
};
|
||||
},
|
||||
// 预览图片
|
||||
handleView(imgUrl) {
|
||||
this.imgUrl = imgUrl;
|
||||
this.viewImage = true;
|
||||
},
|
||||
// 移除图片
|
||||
handleRemove(file) {
|
||||
this.uploadList = this.uploadList.filter(i => i.url !== file.url);
|
||||
this.returnValue();
|
||||
},
|
||||
// 上传成功
|
||||
handleSuccess(res, file) {
|
||||
if (res.success) {
|
||||
file.url = res.result;
|
||||
// 单张图片处理
|
||||
if (!this.multiple && this.uploadList.length > 0) {
|
||||
// 删除第一张
|
||||
this.uploadList.splice(0, 1);
|
||||
}
|
||||
this.uploadList.push(file);
|
||||
// 返回组件值
|
||||
this.returnValue();
|
||||
} else {
|
||||
this.$Message.error(res.message);
|
||||
}
|
||||
},
|
||||
// 上传失败
|
||||
handleError(error, file, fileList) {
|
||||
this.$Message.error(error.toString());
|
||||
},
|
||||
// 格式校验
|
||||
handleFormatError(file) {
|
||||
this.$Notice.warning({
|
||||
title: "不支持的文件格式",
|
||||
desc:
|
||||
"所选文件‘ " +
|
||||
file.name +
|
||||
" ’格式不正确, 请选择 .jpg .jpeg .png .gif图片格式文件"
|
||||
});
|
||||
},
|
||||
// 上传文件大小校验
|
||||
handleMaxSize(file) {
|
||||
this.$Notice.warning({
|
||||
title: "文件大小过大",
|
||||
desc:
|
||||
"所选文件大小过大,不能超过1M."
|
||||
});
|
||||
},
|
||||
// 上传之前钩子
|
||||
handleBeforeUpload() {
|
||||
if (this.multiple && this.uploadList.length >= this.limit) {
|
||||
this.$Message.warning("最多只能上传" + this.limit + "张图片");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 返回组件值
|
||||
returnValue() {
|
||||
if (!this.uploadList || this.uploadList.length < 1) {
|
||||
if (!this.multiple) {
|
||||
this.$emit("input", "");
|
||||
this.$emit("on-change", "");
|
||||
} else {
|
||||
this.$emit("input", []);
|
||||
this.$emit("on-change", []);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!this.multiple) {
|
||||
// 单张
|
||||
let v = this.uploadList[0].url;
|
||||
this.$emit("input", v);
|
||||
this.$emit("on-change", v);
|
||||
} else {
|
||||
let v = [];
|
||||
this.uploadList.forEach(e => {
|
||||
v.push(e.url);
|
||||
});
|
||||
this.$emit("input", v);
|
||||
this.$emit("on-change", v);
|
||||
}
|
||||
},
|
||||
// 传入值变化时改变值
|
||||
setData(v, init) {
|
||||
if (typeof v == "string") {
|
||||
// 单张
|
||||
if (this.multiple) {
|
||||
this.$Message.warning("多张上传仅支持数组数据类型");
|
||||
return;
|
||||
}
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this.uploadList = [];
|
||||
let item = {
|
||||
url: v,
|
||||
status: "finished"
|
||||
};
|
||||
this.uploadList.push(item);
|
||||
this.$emit("uploadchange", v);
|
||||
this.$emit("on-change", v);
|
||||
} else if (typeof v == "object") {
|
||||
// 多张
|
||||
if (!this.multiple) {
|
||||
this.$Message.warning("单张上传仅支持字符串数据类型");
|
||||
return;
|
||||
}
|
||||
this.uploadList = [];
|
||||
if (v.length > this.limit) {
|
||||
for (let i = 0; i < this.limit; i++) {
|
||||
let item = {
|
||||
url: v[i],
|
||||
status: "finished"
|
||||
};
|
||||
this.uploadList.push(item);
|
||||
}
|
||||
this.$emit("on-change", v.slice(0, this.limit));
|
||||
if (init) {
|
||||
this.$emit("input", v.slice(0, this.limit));
|
||||
}
|
||||
this.$Message.warning("最多只能上传" + this.limit + "张图片");
|
||||
} else {
|
||||
v.forEach(e => {
|
||||
let item = {
|
||||
status: "finished",
|
||||
...e
|
||||
};
|
||||
this.uploadList.push(item);
|
||||
});
|
||||
this.$emit("on-change", v);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.setData(val);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.upload-pic-thumb{
|
||||
display: flex;
|
||||
}
|
||||
.upload-list {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.upload-list img {
|
||||
width: 100%;
|
||||
height: -webkit-fill-available;
|
||||
}
|
||||
.upload-list-cover {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.upload-list:hover .upload-list-cover {
|
||||
display: block;
|
||||
}
|
||||
.upload-list-cover i {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
margin: 0 2px;
|
||||
}
|
||||
.list-group {
|
||||
display: inline-block;
|
||||
}
|
||||
.thumb-ghost {
|
||||
opacity: 0.5;
|
||||
background: #c8ebfb;
|
||||
}
|
||||
</style>
|
||||
186
manager/src/components/map/index.vue
Normal file
186
manager/src/components/map/index.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="map">
|
||||
<Modal v-model="showMap" title="选择地址" width="800">
|
||||
<div class="address">{{ addrContent.address }}</div>
|
||||
<div id="map-container"></div>
|
||||
|
||||
<div class="search-con">
|
||||
<Input
|
||||
placeholder="输入关键字搜索"
|
||||
id="input-map"
|
||||
v-model="mapSearch"
|
||||
/>
|
||||
<ul>
|
||||
<li
|
||||
v-for="(tip, index) in tips"
|
||||
:key="index"
|
||||
@click="selectAddr(tip.location)"
|
||||
>
|
||||
<p>{{ tip.name }}</p>
|
||||
<p>{{ tip.district + tip.address }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<Button type="default" @click="showMap = false">取消</Button>
|
||||
<Button type="primary" :loading="loading" @click="ok">确定</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AMapLoader from "@amap/amap-jsapi-loader";
|
||||
import { getRegion } from "@/api/common.js";
|
||||
|
||||
const config = require('@/config/index')
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config,
|
||||
showMap: false, // modal显隐
|
||||
mapSearch: "", // 地图搜索
|
||||
map: null, // 初始化地图
|
||||
autoComplete: null, // 初始化搜索方法
|
||||
geocoder: null, // 初始化地理、坐标转化
|
||||
positionPicker: null, // 地图拖拽选点
|
||||
tips: [], //搜索关键字列表
|
||||
addrContent: {}, // 回显地址信息
|
||||
loading: false, // 加载状态
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
mapSearch: function (val) {
|
||||
this.searchOfMap(val);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
ok() {
|
||||
// 确定选择
|
||||
this.loading = true;
|
||||
const params = {
|
||||
cityCode: this.addrContent.regeocode.addressComponent.citycode,
|
||||
townName: this.addrContent.regeocode.addressComponent.township,
|
||||
};
|
||||
getRegion(params).then((res) => {
|
||||
if (res.success) {
|
||||
this.addrContent.addr = res.result.name.replace(/,/g, " ");
|
||||
this.addrContent.addrId = res.result.id;
|
||||
this.loading = false;
|
||||
this.showMap = false;
|
||||
this.$emit("getAddress", this.addrContent);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 初始化地图组件
|
||||
init() {
|
||||
AMapLoader.load({
|
||||
key: this.config.aMapKey, // 申请好的Web端开发者Key,首次调用 load 时必填
|
||||
version: "", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
|
||||
plugins: [
|
||||
"AMap.ToolBar",
|
||||
"AMap.Autocomplete",
|
||||
"AMap.PlaceSearch",
|
||||
"AMap.Geolocation",
|
||||
"AMap.Geocoder",
|
||||
], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
|
||||
AMapUI: {
|
||||
// 是否加载 AMapUI,缺省不加载
|
||||
version: "1.1", // AMapUI 缺省 1.1
|
||||
plugins: ["misc/PositionPicker"], // 需要加载的 AMapUI ui插件
|
||||
},
|
||||
}).then((AMap) => {
|
||||
let that = this;
|
||||
this.map = new AMap.Map("map-container", {
|
||||
zoom: 12,
|
||||
});
|
||||
that.map.addControl(new AMap.ToolBar());
|
||||
that.map.addControl(new AMap.Autocomplete());
|
||||
that.map.addControl(new AMap.PlaceSearch());
|
||||
that.map.addControl(new AMap.Geocoder());
|
||||
|
||||
// 实例化Autocomplete
|
||||
let autoOptions = {
|
||||
city: "全国",
|
||||
};
|
||||
that.autoComplete = new AMap.Autocomplete(autoOptions); // 搜索
|
||||
that.geocoder = new AMap.Geocoder(autoOptions);
|
||||
|
||||
that.positionPicker = new AMapUI.PositionPicker({
|
||||
// 拖拽选点
|
||||
mode: "dragMap",
|
||||
map: that.map,
|
||||
});
|
||||
that.positionPicker.start();
|
||||
/**
|
||||
*
|
||||
* 所有回显数据,都在positionResult里面
|
||||
* 需要字段可以查找
|
||||
*
|
||||
*/
|
||||
that.positionPicker.on("success", function (positionResult) {
|
||||
that.addrContent = positionResult;
|
||||
});
|
||||
}).catch((e) => {});
|
||||
},
|
||||
searchOfMap(val) {
|
||||
// 地图搜索
|
||||
let that = this;
|
||||
this.autoComplete.search(val, function (status, result) {
|
||||
// 搜索成功时,result即是对应的匹配数据
|
||||
if (status == "complete" && result.info == "OK") {
|
||||
that.tips = result.tips;
|
||||
} else {
|
||||
that.tips = [];
|
||||
}
|
||||
});
|
||||
},
|
||||
selectAddr(location) {
|
||||
// 选择坐标
|
||||
if (!location) {
|
||||
this.$Message.warning("请选择正确点位");
|
||||
return false;
|
||||
}
|
||||
const lnglat = [location.lng, location.lat];
|
||||
this.positionPicker.start(lnglat);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
#map-container {
|
||||
width: 500px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.search-con {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 64px;
|
||||
width: 260px;
|
||||
ul {
|
||||
width: 260px;
|
||||
height: 400px;
|
||||
overflow: scroll;
|
||||
li {
|
||||
padding: 5px;
|
||||
p:nth-child(2) {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.address {
|
||||
margin-bottom: 10px;
|
||||
// color: $theme_color;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
182
manager/src/components/region.vue
Normal file
182
manager/src/components/region.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div>
|
||||
<Cascader
|
||||
:data="data"
|
||||
:load-data="loadData"
|
||||
v-model="addr"
|
||||
placeholder="请选择地址"
|
||||
@on-change="change"
|
||||
style="width: 350px"
|
||||
></Cascader>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {getChildRegion} from '@/api/common.js';
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
data: [], // 地区数据
|
||||
addr: [] // 已选数据
|
||||
};
|
||||
},
|
||||
props: ['addressId'],
|
||||
mounted () {},
|
||||
methods: {
|
||||
change (val, selectedData) { // 选择地区
|
||||
/**
|
||||
* @returns [regionId,region]
|
||||
*/
|
||||
this.$emit('selected', [
|
||||
val,
|
||||
selectedData[selectedData.length - 1].__label.split('/')
|
||||
]);
|
||||
},
|
||||
loadData (item, callback) { // 加载数据
|
||||
item.loading = true;
|
||||
getChildRegion(item.value).then((res) => {
|
||||
if (res.result.length <= 0) {
|
||||
item.loading = false;
|
||||
} else {
|
||||
res.result.forEach((child) => {
|
||||
item.loading = false;
|
||||
|
||||
let data = {
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
loading: false,
|
||||
children: []
|
||||
};
|
||||
|
||||
if (child.level === 'street' || item.label === '香港特别行政区') {
|
||||
item.children.push({
|
||||
value: child.id,
|
||||
label: child.name
|
||||
});
|
||||
} else {
|
||||
item.children.push(data);
|
||||
}
|
||||
});
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
async init () { // 初始化地图数据
|
||||
let data = await getChildRegion(0);
|
||||
let arr = [];
|
||||
data.result.forEach((item) => {
|
||||
let obj;
|
||||
// 台湾省做处理
|
||||
if (item.name === '台湾省') {
|
||||
obj = {
|
||||
value: item.id,
|
||||
label: item.name
|
||||
};
|
||||
} else {
|
||||
obj = {
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
loading: false,
|
||||
children: []
|
||||
};
|
||||
}
|
||||
arr.push(obj);
|
||||
});
|
||||
this.data = arr;
|
||||
},
|
||||
async reviewData () {
|
||||
// 数据回显
|
||||
let addr = JSON.parse(JSON.stringify(this.addressId.split(',')));
|
||||
let length = addr.length;
|
||||
let data = await getChildRegion(0);
|
||||
let arr0 = [];
|
||||
let arr1 = [];
|
||||
let arr2 = [];
|
||||
// 第一级数据
|
||||
data.result.forEach((item) => {
|
||||
let obj;
|
||||
// 台湾省做处理
|
||||
if (item.name === '台湾省') {
|
||||
obj = {
|
||||
value: item.id,
|
||||
label: item.name
|
||||
};
|
||||
} else {
|
||||
obj = {
|
||||
value: item.id,
|
||||
label: item.name,
|
||||
loading: false,
|
||||
children: []
|
||||
};
|
||||
}
|
||||
arr0.push(obj);
|
||||
});
|
||||
// 根据选择的数据来加载数据列表
|
||||
if (length > 0) {
|
||||
let children = await getChildRegion(addr[0]);
|
||||
children = this.handleData(children.result);
|
||||
arr0.forEach((e) => {
|
||||
if (e.value === addr[0]) {
|
||||
e.children = arr1 = children;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (length > 1) {
|
||||
let children = await getChildRegion(addr[1]);
|
||||
children = this.handleData(children.result);
|
||||
arr1.forEach((e) => {
|
||||
if (e.value === addr[1]) {
|
||||
e.children = arr2 = children;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (length > 2) {
|
||||
let children = await getChildRegion(addr[2]);
|
||||
children = this.handleData(children.result);
|
||||
arr2.forEach((e) => {
|
||||
if (e.value === addr[2]) {
|
||||
e.children = children;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.data = arr0;
|
||||
this.addr = addr;
|
||||
},
|
||||
handleData (data) {
|
||||
// 处理接口数据
|
||||
let item = [];
|
||||
data.forEach((child) => {
|
||||
let obj = {
|
||||
value: child.id,
|
||||
label: child.name,
|
||||
loading: false,
|
||||
children: []
|
||||
};
|
||||
|
||||
if (child.level === 'street' || item.label === '香港特别行政区') {
|
||||
item.push({
|
||||
value: child.id,
|
||||
label: child.name
|
||||
});
|
||||
} else {
|
||||
item.push(obj);
|
||||
}
|
||||
});
|
||||
return item;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
addressId: {
|
||||
handler: function (v) {
|
||||
if (v) {
|
||||
this.reviewData();
|
||||
} else {
|
||||
this.init();
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
117
manager/src/components/tree-table/Checkbox/Checkbox.less
Normal file
117
manager/src/components/tree-table/Checkbox/Checkbox.less
Normal file
@@ -0,0 +1,117 @@
|
||||
// text
|
||||
@prefixCls: zk-checkbox;
|
||||
// color
|
||||
@border: #dddee1;
|
||||
@hoverBorder: #bcbcbc;
|
||||
@blue: #2d8cf0;
|
||||
|
||||
.@{prefixCls}-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.@{prefixCls} {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
&:hover {
|
||||
.@{prefixCls}__inner {
|
||||
border-color: @hoverBorder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}__inner {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: 1px solid @border;
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
transition: border-color .2s ease-in-out,background-color .2s ease-in-out;
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 4px;
|
||||
border: 2px solid #fff;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
transform: rotate(45deg) scale(0);
|
||||
transition: all .2s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}--indeterminate {
|
||||
.@{prefixCls}__inner {
|
||||
background-color: @blue;
|
||||
border-color: @blue;
|
||||
&::after {
|
||||
content: "";
|
||||
width: 8px;
|
||||
height: 1px;
|
||||
transform: scale(1);
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 5px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.@{prefixCls}__inner {
|
||||
border-color: @blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}--checked {
|
||||
.@{prefixCls}__inner {
|
||||
border-color: @blue;
|
||||
background-color: @blue;
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
width: 4px;
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 4px;
|
||||
border: 2px solid #ffffff;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
transform: rotate(45deg) scale(1);
|
||||
transition: all .2s ease-in-out;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.@{prefixCls}__inner {
|
||||
border-color: @blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}--disabled {
|
||||
cursor: not-allowed;
|
||||
.@{prefixCls}__inner {
|
||||
background-color: #f3f3f3;
|
||||
border-color: @border;
|
||||
&::after {
|
||||
animation-name: none;
|
||||
border-color: #ccc;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.@{prefixCls}__inner {
|
||||
border-color: @border;
|
||||
}
|
||||
}
|
||||
}
|
||||
58
manager/src/components/tree-table/Checkbox/Checkbox.vue
Normal file
58
manager/src/components/tree-table/Checkbox/Checkbox.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template lang="html">
|
||||
<label :class="`${prefixCls}-wrapper`">
|
||||
<span :class="checkboxClass">
|
||||
<span
|
||||
:class="`${prefixCls}__inner`"
|
||||
@click="toggle"></span>
|
||||
</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'zk-checkbox',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
indeterminate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
prefixCls: 'zk-checkbox',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
checkboxClass() {
|
||||
return [
|
||||
`${this.prefixCls}`,
|
||||
{
|
||||
[`${this.prefixCls}--disabled`]: this.disabled,
|
||||
[`${this.prefixCls}--checked`]: this.value,
|
||||
[`${this.prefixCls}--indeterminate`]: this.indeterminate,
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
if (this.disabled) {
|
||||
return false;
|
||||
}
|
||||
const value = !this.value;
|
||||
this.$emit('input', value);
|
||||
return this.$emit('on-change', value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped src="./Checkbox.less"></style>
|
||||
170
manager/src/components/tree-table/Table/Table.less
Normal file
170
manager/src/components/tree-table/Table/Table.less
Normal file
@@ -0,0 +1,170 @@
|
||||
@import "./font/iconfont";
|
||||
|
||||
// text
|
||||
@prefixCls: zk-table;
|
||||
// color
|
||||
@black: #515a6e;
|
||||
@white: #ffffff;
|
||||
@border: #e9eaec;
|
||||
@hoverRow: #ebf7ff;
|
||||
@backgroundRow: #f8f8f9;
|
||||
|
||||
.@{prefixCls} {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: @white;
|
||||
border: 1px solid @border;
|
||||
font-size: 14px;
|
||||
line-height: 26px;
|
||||
color: @black;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.@{prefixCls}__header-cell {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.@{prefixCls}__cell-inner {
|
||||
padding: 0px 18px;
|
||||
}
|
||||
|
||||
.@{prefixCls}-default {
|
||||
font-size: 14px;
|
||||
|
||||
.@{prefixCls}__header-row {
|
||||
height: 40px !important;
|
||||
}
|
||||
|
||||
.@{prefixCls}__body-row {
|
||||
height: 48px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}-small {
|
||||
font-size: 12px;
|
||||
|
||||
.@{prefixCls}__header-row {
|
||||
height: 36px !important;
|
||||
}
|
||||
|
||||
.@{prefixCls}__body-row {
|
||||
height: 40px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}-large {
|
||||
font-size: 16px;
|
||||
|
||||
.@{prefixCls}__header-row {
|
||||
height: 43px !important;
|
||||
}
|
||||
|
||||
.@{prefixCls}__body-row {
|
||||
height: 60px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}__header-wrapper,
|
||||
.@{prefixCls}__footer-wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.@{prefixCls}__body-wrapper {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.@{prefixCls}__header,
|
||||
.@{prefixCls}__body,
|
||||
.@{prefixCls}__footer {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.@{prefixCls}__header-row {
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
background-color: @backgroundRow;
|
||||
border-bottom: 1px solid @border;
|
||||
}
|
||||
|
||||
.@{prefixCls}__footer-row {
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
background-color: @white;
|
||||
border-top: 1px solid @border;
|
||||
}
|
||||
|
||||
.@{prefixCls}__body-row {
|
||||
height: 48px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
border-top: 1px solid @border;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}__header-cell,
|
||||
.@{prefixCls}__body-cell,
|
||||
.@{prefixCls}__footer-cell {
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.@{prefixCls}--firstProp-header-inner {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
.@{prefixCls}--empty-row {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.@{prefixCls}--empty-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.@{prefixCls}--center-cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.@{prefixCls}--right-cell {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.@{prefixCls}--stripe-row {
|
||||
background-color: @backgroundRow;
|
||||
}
|
||||
|
||||
.@{prefixCls}--row-hover {
|
||||
background-color: @hoverRow;
|
||||
}
|
||||
|
||||
.@{prefixCls}--border-cell {
|
||||
&:not(:last-of-type) {
|
||||
border-right: 1px solid @border;
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefixCls}--tree-icon {
|
||||
margin-right: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.@{prefixCls}--expand-inner {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: transform .2s ease-in-out;
|
||||
}
|
||||
|
||||
.@{prefixCls}--expanded-inner {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.@{prefixCls}--expand-content {
|
||||
padding: 20px;
|
||||
}
|
||||
396
manager/src/components/tree-table/Table/Table.vue
Normal file
396
manager/src/components/tree-table/Table/Table.vue
Normal file
@@ -0,0 +1,396 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
v-if="columns.length > 0"
|
||||
ref="table"
|
||||
:class="[prefixCls, `${prefixCls}-${size}`, tableClass]">
|
||||
<Spin fix v-if="loading"></Spin>
|
||||
<div
|
||||
v-show="showHeader"
|
||||
ref="header-wrapper"
|
||||
:class="`${prefixCls}__header-wrapper`"
|
||||
@mousewheel="handleEvent('header', $event)">
|
||||
<table-header
|
||||
ref="table-header">
|
||||
</table-header>
|
||||
</div>
|
||||
<div
|
||||
ref="body-wrapper"
|
||||
:style="bodyWrapperStyle"
|
||||
:class="`${prefixCls}__body-wrapper`"
|
||||
@scroll="handleEvent('body', $event)">
|
||||
<table-body
|
||||
ref="table-body"
|
||||
:class="bodyClass">
|
||||
</table-body>
|
||||
</div>
|
||||
<div
|
||||
v-show="showSummary && data.length > 0"
|
||||
ref="footer-wrapper"
|
||||
:class="`${prefixCls}__footer-wrapper`"
|
||||
@mousewheel="handleEvent('footer', $event)">
|
||||
<table-footer
|
||||
ref="table-footer">
|
||||
</table-footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TableHeader from "./TableHeader";
|
||||
import TableBody from "./TableBody";
|
||||
import TableFooter from "./TableFooter";
|
||||
import { mixins, scrollBarWidth as getSbw } from "./utils";
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
// function getBodyData(data, isTreeType, childrenProp, isFold, level = 1) {
|
||||
function getBodyData(
|
||||
primaryKey,
|
||||
oldBodyData,
|
||||
data,
|
||||
isTreeType,
|
||||
childrenProp,
|
||||
isFold,
|
||||
parentFold,
|
||||
level = 1
|
||||
) {
|
||||
let bodyData = [];
|
||||
data.forEach((row, index) => {
|
||||
const children = row[childrenProp];
|
||||
const childrenLen =
|
||||
Object.prototype.toString.call(children).slice(8, -1) === "Array"
|
||||
? children.length
|
||||
: 0;
|
||||
let curIsFold = isFold;
|
||||
if (
|
||||
isFold &&
|
||||
typeof primaryKey === "string" &&
|
||||
Array.isArray(oldBodyData)
|
||||
) {
|
||||
for (let i = 0; i < oldBodyData.length; i++) {
|
||||
const oldRow = oldBodyData[i];
|
||||
if (oldRow[primaryKey] === row[primaryKey]) {
|
||||
if ("_isFold" in oldRow) {
|
||||
curIsFold = oldRow._isFold;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
bodyData.push({
|
||||
_isHover: false,
|
||||
_isExpanded: false,
|
||||
_isChecked: false,
|
||||
_level: level,
|
||||
// _isHide: isFold ? level !== 1 : false,
|
||||
// _isFold: isFold,
|
||||
_isHide: level !== 1 ? isFold && parentFold : false,
|
||||
_isFold: isFold && curIsFold,
|
||||
_childrenLen: childrenLen,
|
||||
_normalIndex: index + 1,
|
||||
...row
|
||||
});
|
||||
if (isTreeType) {
|
||||
if (childrenLen > 0) {
|
||||
// bodyData = bodyData.concat(getBodyData(children, true, childrenProp, isFold, level + 1));
|
||||
bodyData = bodyData.concat(
|
||||
getBodyData(
|
||||
primaryKey,
|
||||
oldBodyData,
|
||||
children,
|
||||
true,
|
||||
childrenProp,
|
||||
isFold,
|
||||
curIsFold,
|
||||
level + 1
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
return bodyData;
|
||||
}
|
||||
|
||||
function initialState(table, expandKey) {
|
||||
return {
|
||||
bodyHeight: "auto",
|
||||
firstProp: expandKey || (table.columns[0] && table.columns[0].key),
|
||||
// bodyData: getBodyData(table.data, table.treeType, table.childrenProp, table.isFold),
|
||||
bodyData: getBodyData(
|
||||
table.primaryKey,
|
||||
table.bodyData,
|
||||
table.data,
|
||||
table.treeType,
|
||||
table.childrenProp,
|
||||
table.isFold,
|
||||
false
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
function initialColumns(table, clientWidth) {
|
||||
let columnsWidth = 0;
|
||||
const minWidthColumns = [];
|
||||
const otherColumns = [];
|
||||
const columns = table.columns.concat();
|
||||
if (table.expandType) {
|
||||
columns.unshift({
|
||||
width: "50"
|
||||
});
|
||||
}
|
||||
if (table.selectable) {
|
||||
columns.unshift({
|
||||
width: "50"
|
||||
});
|
||||
}
|
||||
if (table.showIndex) {
|
||||
columns.unshift({
|
||||
width: "50px",
|
||||
key: "_normalIndex",
|
||||
title: table.indexText
|
||||
});
|
||||
}
|
||||
columns.forEach((column, index) => {
|
||||
let width = "";
|
||||
let minWidth = "";
|
||||
if (!column.width) {
|
||||
if (column.minWidth) {
|
||||
minWidth =
|
||||
typeof column.minWidth === "number"
|
||||
? column.minWidth
|
||||
: parseInt(column.minWidth, 10);
|
||||
} else {
|
||||
minWidth = 80;
|
||||
}
|
||||
minWidthColumns.push({
|
||||
...column,
|
||||
minWidth,
|
||||
_index: index
|
||||
});
|
||||
} else {
|
||||
width =
|
||||
typeof column.width === "number"
|
||||
? column.width
|
||||
: parseInt(column.width, 10);
|
||||
otherColumns.push({
|
||||
...column,
|
||||
width,
|
||||
_index: index
|
||||
});
|
||||
}
|
||||
columnsWidth += minWidth || width;
|
||||
});
|
||||
const scrollBarWidth = getSbw();
|
||||
const totalWidth = columnsWidth + scrollBarWidth;
|
||||
const isScrollX = totalWidth > clientWidth;
|
||||
if (!isScrollX) {
|
||||
const extraWidth = clientWidth - totalWidth;
|
||||
const averageExtraWidth = Math.floor(extraWidth / minWidthColumns.length);
|
||||
minWidthColumns.forEach(column => {
|
||||
column.computedWidth = column.minWidth + averageExtraWidth;
|
||||
});
|
||||
}
|
||||
const tableColumns = otherColumns.concat(minWidthColumns);
|
||||
tableColumns.sort((a, b) => a._index - b._index);
|
||||
return tableColumns;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "TreeTable",
|
||||
mixins: [mixins],
|
||||
components: {
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
size: {
|
||||
default() {
|
||||
return !this.$IVIEW || this.$IVIEW.size === ""
|
||||
? "default"
|
||||
: this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxHeight: {
|
||||
type: [String, Number],
|
||||
default: "auto"
|
||||
},
|
||||
stripe: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
treeType: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
childrenProp: {
|
||||
type: String,
|
||||
default: "children"
|
||||
},
|
||||
isFold: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
expandType: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selectable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selectType: {
|
||||
type: String,
|
||||
default: "checkbox"
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: "暂无数据"
|
||||
},
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
indexText: {
|
||||
type: String,
|
||||
default: "#"
|
||||
},
|
||||
showSummary: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
sumText: {
|
||||
type: String,
|
||||
default: "合计"
|
||||
},
|
||||
primaryKey: String,
|
||||
summaryMethod: Function,
|
||||
showRowHover: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
rowKey: Function,
|
||||
rowClassName: [String, Function],
|
||||
cellClassName: [String, Function],
|
||||
rowStyle: [Object, Function],
|
||||
cellStyle: [Object, Function],
|
||||
expandKey: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
computedWidth: "",
|
||||
computedHeight: "",
|
||||
tableColumns: [],
|
||||
...initialState(this, this.expandKey)
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
bodyWrapperStyle() {
|
||||
return {
|
||||
height: this.bodyHeight
|
||||
};
|
||||
},
|
||||
tableClass() {
|
||||
return {
|
||||
[`${this.prefixCls}--border`]: this.border
|
||||
};
|
||||
},
|
||||
bodyClass() {
|
||||
return {
|
||||
[`${this.prefixCls}--stripe`]: this.stripe
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleEvent(type, $event) {
|
||||
this.validateType(type, ["header", "body", "footer"], "handleEvent");
|
||||
const eventType = $event.type;
|
||||
if (eventType === "scroll") {
|
||||
this.$refs["header-wrapper"].scrollLeft = $event.target.scrollLeft;
|
||||
this.$refs["footer-wrapper"].scrollLeft = $event.target.scrollLeft;
|
||||
}
|
||||
if (eventType === "mousewheel") {
|
||||
const deltaX = $event.deltaX;
|
||||
const $body = this.$refs["body-wrapper"];
|
||||
if (deltaX > 0) {
|
||||
$body.scrollLeft += 10;
|
||||
} else {
|
||||
$body.scrollLeft -= 10;
|
||||
}
|
||||
}
|
||||
return this.$emit(`${type}-${eventType}`, $event);
|
||||
},
|
||||
// computedWidth, computedHeight, tableColumns
|
||||
measure() {
|
||||
this.$nextTick(() => {
|
||||
const { clientWidth, clientHeight } = this.$el;
|
||||
this.computedWidth = clientWidth + 2;
|
||||
this.computedHeight = clientHeight + 2;
|
||||
|
||||
const maxHeight = parseInt(this.maxHeight, 10);
|
||||
if (this.maxHeight !== "auto" && this.computedHeight > maxHeight) {
|
||||
this.bodyHeight = `${maxHeight - 83}px`;
|
||||
}
|
||||
this.tableColumns = initialColumns(this, clientWidth);
|
||||
});
|
||||
},
|
||||
getCheckedProp(key = "index") {
|
||||
if (!this.selectable) {
|
||||
return [];
|
||||
}
|
||||
const checkedIndexs = [];
|
||||
this.bodyData.forEach((item, index) => {
|
||||
if (item._isChecked) {
|
||||
if (key === "index") {
|
||||
checkedIndexs.push(index);
|
||||
} else {
|
||||
checkedIndexs.push(item[key]);
|
||||
}
|
||||
}
|
||||
});
|
||||
return checkedIndexs;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$props: {
|
||||
deep: true,
|
||||
handler() {
|
||||
Object.assign(this.$data, initialState(this, this.expandKey));
|
||||
}
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
this.measure();
|
||||
},
|
||||
mounted() {
|
||||
this.measure();
|
||||
window.addEventListener("resize", this.measure);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("resize", this.measure);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" src="./Table.less"></style>
|
||||
328
manager/src/components/tree-table/Table/TableBody.js
Normal file
328
manager/src/components/tree-table/Table/TableBody.js
Normal file
@@ -0,0 +1,328 @@
|
||||
import Checkbox from '../Checkbox/Checkbox'; // eslint-disable-line
|
||||
// import Radio from '../Radio/Radio'; // eslint-disable-line
|
||||
import { mixins } from './utils';
|
||||
import { Radio } from 'view-design'; // eslint-disable-line
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
export default {
|
||||
name: 'TreeTable__body',
|
||||
mixins: [mixins],
|
||||
components: { Radio },
|
||||
data() {
|
||||
return {
|
||||
radioSelectedIndex: -1,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
table() {
|
||||
return this.$parent;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleStatus(type, row, rowIndex, value) {
|
||||
this.validateType(type, ['Expanded', 'Checked', 'Hide', 'Fold'], 'toggleStatus', false);
|
||||
const target = this.table.bodyData[rowIndex];
|
||||
this.table.bodyData.splice(rowIndex, 1, {
|
||||
...target,
|
||||
[`_is${type}`]: typeof value === 'undefined' ? !row[`_is${type}`] : value,
|
||||
});
|
||||
},
|
||||
getChildrenIndex(parentLevel, parentIndex, careFold = true) {
|
||||
const data = this.table.bodyData;
|
||||
let childrenIndex = [];
|
||||
for (let i = parentIndex + 1; i < data.length; i++) {
|
||||
if (data[i]._level <= parentLevel) break;
|
||||
if (data[i]._level - 1 === parentLevel) {
|
||||
childrenIndex.push(i);
|
||||
}
|
||||
}
|
||||
const len = childrenIndex.length; // important!!!
|
||||
if (len > 0) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
const childData = data[childrenIndex[i]];
|
||||
if (
|
||||
childData._childrenLen &&
|
||||
(!careFold || (careFold && !childData._isFold))
|
||||
) {
|
||||
childrenIndex = childrenIndex.concat(
|
||||
this.getChildrenIndex(childData._level, childrenIndex[i], careFold));
|
||||
}
|
||||
}
|
||||
}
|
||||
return childrenIndex;
|
||||
},
|
||||
handleEvent($event, type, data, others) {
|
||||
const certainType = this.validateType(type, ['cell', 'row', 'checkbox', 'icon', 'radio'], 'handleEvent');
|
||||
const eventType = $event ? $event.type : '';
|
||||
const { row, rowIndex, column, columnIndex } = data;
|
||||
const latestData = this.table.bodyData;
|
||||
// Checkbox
|
||||
if (certainType.checkbox) {
|
||||
const { isChecked } = others;
|
||||
this.toggleStatus('Checked', row, rowIndex, isChecked);
|
||||
if (row._childrenLen > 0) {
|
||||
const childrenIndex = this.getChildrenIndex(row._level, rowIndex, false);
|
||||
for (let i = 0; i < childrenIndex.length; i++) {
|
||||
this.toggleStatus('Checked', latestData[childrenIndex[i]], childrenIndex[i], isChecked);
|
||||
}
|
||||
}
|
||||
return this.table.$emit('checkbox-click', latestData[rowIndex], column, columnIndex, $event);
|
||||
}
|
||||
// Radio
|
||||
if (certainType.radio) {
|
||||
this.radioSelectedIndex = rowIndex;
|
||||
return this.table.$emit('radio-click', { row, rowIndex, column, columnIndex, $event });
|
||||
}
|
||||
// Tree's icon
|
||||
if (certainType.icon) {
|
||||
$event.stopPropagation();
|
||||
this.toggleStatus('Fold', row, rowIndex);
|
||||
const childrenIndex = this.getChildrenIndex(row._level, rowIndex);
|
||||
for (let i = 0; i < childrenIndex.length; i++) {
|
||||
this.toggleStatus('Hide', latestData[childrenIndex[i]], childrenIndex[i]);
|
||||
}
|
||||
return this.table.$emit('tree-icon-click', latestData[rowIndex], column, columnIndex, $event);
|
||||
}
|
||||
if (certainType.cell && eventType === 'click') {
|
||||
// 点击扩展单元格
|
||||
if (this.isExpandCell(this.table, columnIndex)) {
|
||||
this.toggleStatus('Expanded', row, rowIndex);
|
||||
return this.table.$emit('expand-cell-click', latestData[rowIndex], column, columnIndex, $event);
|
||||
}
|
||||
}
|
||||
// 行:Hover
|
||||
if (certainType.row && (eventType === 'mouseenter' || eventType === 'mouseleave')) {
|
||||
const { hover } = others;
|
||||
const target = latestData[rowIndex];
|
||||
latestData.splice(rowIndex, 1, {
|
||||
...target,
|
||||
_isHover: hover,
|
||||
});
|
||||
}
|
||||
if (certainType.row && others && others.clickRow && certainType.radio) {
|
||||
this.radioSelectedIndex = rowIndex;
|
||||
return this.table.$emit('radio-click', { row, rowIndex, column, columnIndex, $event });
|
||||
}
|
||||
if (certainType.cell) {
|
||||
return this.table.$emit(`${type}-${eventType}`, latestData[rowIndex], rowIndex, column, columnIndex, $event);
|
||||
}
|
||||
return this.table.$emit(`${type}-${eventType}`, latestData[rowIndex], rowIndex, $event);
|
||||
},
|
||||
},
|
||||
render() {
|
||||
// key
|
||||
// function getKey(row, rowIndex) {
|
||||
// const rowKey = this.table.rowKey;
|
||||
// if (rowKey) {
|
||||
// return rowKey.call(null, row, rowIndex);
|
||||
// }
|
||||
// return rowIndex;
|
||||
// }
|
||||
|
||||
// style
|
||||
function getStyle(type, row, rowIndex, column, columnIndex) {
|
||||
const certainType = this.validateType(type, ['cell', 'row'], 'getStyle');
|
||||
const style = this.table[`${type}Style`];
|
||||
if (typeof style === 'function') {
|
||||
if (certainType.row) {
|
||||
return style.call(null, row, rowIndex);
|
||||
}
|
||||
if (certainType.cell) {
|
||||
return style.call(null, row, rowIndex, column, columnIndex);
|
||||
}
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
// className
|
||||
function getClassName(type, row, rowIndex, column, columnIndex) {
|
||||
const certainType = this.validateType(type, ['cell', 'row', 'inner'], 'getClassName');
|
||||
const classList = [];
|
||||
if (column && column.key == "_normalIndex") {
|
||||
classList.push(`${this.prefixCls}--center-cell`);
|
||||
}
|
||||
// console.log(certainType.inner)
|
||||
if (certainType.row || certainType.cell) {
|
||||
const className = this.table[`${type}ClassName`];
|
||||
if (typeof className === 'string') {
|
||||
classList.push(className);
|
||||
} else if (typeof className === 'function') {
|
||||
if (certainType.row) {
|
||||
classList.push(className.call(null, row, rowIndex) || '');
|
||||
}
|
||||
if (certainType.cell) {
|
||||
classList.push(className.call(null, row, rowIndex, column, columnIndex) || '');
|
||||
}
|
||||
}
|
||||
if (certainType.row) {
|
||||
classList.push(`${this.prefixCls}__body-row`);
|
||||
if (this.table.stripe && rowIndex % 2 !== 0) {
|
||||
classList.push(`${this.prefixCls}--stripe-row`);
|
||||
}
|
||||
if (this.table.showRowHover && row._isHover) {
|
||||
classList.push(`${this.prefixCls}--row-hover`);
|
||||
}
|
||||
}
|
||||
if (certainType.cell) {
|
||||
classList.push(`${this.prefixCls}__body-cell`);
|
||||
if (this.table.border) {
|
||||
classList.push(`${this.prefixCls}--border-cell`);
|
||||
}
|
||||
const align = column.align;
|
||||
if (['center', 'right'].indexOf(align) > -1) {
|
||||
classList.push(`${this.prefixCls}--${align}-cell`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (certainType.inner) {
|
||||
classList.push(`${this.prefixCls}__cell-inner`);
|
||||
if (this.isExpandCell(this.table, columnIndex)) {
|
||||
classList.push(`${this.prefixCls}--expand-inner`);
|
||||
if (row._isExpanded) {
|
||||
classList.push(`${this.prefixCls}--expanded-inner`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classList.join(' ');
|
||||
}
|
||||
|
||||
// 根据type渲染单元格Cell
|
||||
function renderCell(row, rowIndex, column, columnIndex) {
|
||||
// ExpandType
|
||||
if (this.isExpandCell(this.table, columnIndex)) {
|
||||
return <i class='zk-icon zk-icon-angle-right'></i>;
|
||||
}
|
||||
// SelectionType's Checkbox
|
||||
if (this.isSelectionCell(this.table, columnIndex)) {
|
||||
let res = null;
|
||||
if (this.table.selectType === 'checkbox') {
|
||||
let allCheck;
|
||||
let childrenIndex;
|
||||
const hasChildren = row._childrenLen > 0;
|
||||
if (hasChildren) {
|
||||
childrenIndex = this.getChildrenIndex(row._level, rowIndex, false);
|
||||
allCheck = true;
|
||||
for (let i = 0; i < childrenIndex.length; i++) {
|
||||
if (!this.table.bodyData[childrenIndex[i]]._isChecked) {
|
||||
allCheck = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allCheck = row._isChecked;
|
||||
}
|
||||
let indeterminate = false;
|
||||
if (hasChildren && !allCheck) {
|
||||
for (let i = 0; i < childrenIndex.length; i++) {
|
||||
if (this.table.bodyData[childrenIndex[i]]._isChecked) {
|
||||
indeterminate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// res = <Checkbox
|
||||
// indeterminate={indeterminate}
|
||||
// value={allCheck}
|
||||
// onOn-change={isChecked => this.handleEvent(null, 'checkbox', { row, rowIndex, column, columnIndex }, { isChecked })}>
|
||||
// </Checkbox>;
|
||||
} else {
|
||||
res = <Radio value={this.radioSelectedIndex === rowIndex} on-on-change={() => this.handleEvent(null, 'radio', { row, rowIndex, column, columnIndex })}></Radio>;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Tree's firstProp
|
||||
if (this.table.treeType && this.table.firstProp === column.key) {
|
||||
return <span
|
||||
class={`${this.prefixCls}--level-${row._level}-cell`}
|
||||
style={{
|
||||
marginLeft: `${(row._level - 1) * 24}px`,
|
||||
paddingLeft: row._childrenLen === 0 ? '20px' : '',
|
||||
}}>
|
||||
{row._childrenLen > 0 &&
|
||||
<i
|
||||
class={`${this.prefixCls}--tree-icon zk-icon zk-icon-${row._isFold ? 'plus' : 'minus'}-square-o`}
|
||||
on-click={$event => this.handleEvent($event, 'icon', { row, rowIndex, column, columnIndex }, { isFold: row._isFold })}></i>
|
||||
}
|
||||
{row[column.key] ? row[column.key] : ''}
|
||||
</span>;
|
||||
}
|
||||
// TreeType children's index
|
||||
if (this.table.showIndex && this.table.treeType && column.key === '_normalIndex' && row._level > 1) {
|
||||
return '';
|
||||
}
|
||||
if (column.type === undefined || column.type === 'custom') {
|
||||
return row[column.key];
|
||||
} else if (column.type === 'template') {
|
||||
return this.table.$scopedSlots[column.template]
|
||||
? this.table.$scopedSlots[column.template]({ row, rowIndex, column, columnIndex })
|
||||
: '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Template
|
||||
return (
|
||||
<table cellspacing="0" cellpadding="0" border="0" class={`${this.prefixCls}__body`}>
|
||||
{/* <colgroup>
|
||||
{this.table.tableColumns.map(column =>
|
||||
<col width={column.computedWidth || column.minWidth || column.width}></col>)
|
||||
}
|
||||
</colgroup> */}
|
||||
<tbody>
|
||||
{ this.table.bodyData.length > 0
|
||||
? this.table.bodyData.map((row, rowIndex) =>
|
||||
[
|
||||
<tr
|
||||
v-show={!row._isHide}
|
||||
key={`table_row_${rowIndex}`}
|
||||
style={getStyle.call(this, 'row', row, rowIndex)}
|
||||
class={getClassName.call(this, 'row', row, rowIndex)}
|
||||
on-click={$event => this.handleEvent($event, 'row', { row, rowIndex }, { clickRow: true })}
|
||||
on-dblclick={$event => this.handleEvent($event, 'row', { row, rowIndex })}
|
||||
on-contextmenu={$event => this.handleEvent($event, 'row', { row, rowIndex })}
|
||||
on-mouseenter={$event => this.handleEvent($event, 'row', { row, rowIndex }, { hover: true })}
|
||||
on-mouseleave={$event => this.handleEvent($event, 'row', { row, rowIndex }, { hover: false })}>
|
||||
{ this.table.tableColumns.map((column, columnIndex) =>
|
||||
column.key ? <td
|
||||
style={getStyle.call(this, 'cell', row, rowIndex, column, columnIndex)}
|
||||
class={getClassName.call(this, 'cell', row, rowIndex, column, columnIndex)}
|
||||
on-click={$event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex })}
|
||||
on-dblclick={$event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex })}
|
||||
on-contextmenu={$event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex })}
|
||||
on-mouseenter={$event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex })}
|
||||
on-mouseleave={$event => this.handleEvent($event, 'cell', { row, rowIndex, column, columnIndex })}>
|
||||
<div class={getClassName.call(this, 'inner', row, rowIndex, column, columnIndex)}>
|
||||
{renderCell.call(this, row, rowIndex, column, columnIndex)}
|
||||
</div>
|
||||
</td>:"")
|
||||
}
|
||||
</tr>,
|
||||
this.table.expandType && row._isExpanded &&
|
||||
<tr
|
||||
key={rowIndex}
|
||||
class={`${this.prefixCls}__body-row ${this.prefixCls}--expand-row`}>
|
||||
<td
|
||||
class={`${this.prefixCls}--expand-content`}
|
||||
colspan={this.table.tableColumns.length}>
|
||||
{this.table.$scopedSlots.expand
|
||||
? this.table.$scopedSlots.expand({ row, rowIndex })
|
||||
: ''
|
||||
}
|
||||
</td>
|
||||
</tr>,
|
||||
])
|
||||
: <tr
|
||||
class={`${this.prefixCls}--empty-row`}>
|
||||
<td
|
||||
class={`${this.prefixCls}__body-cell ${this.prefixCls}--empty-content`}
|
||||
colspan={this.table.tableColumns.length}>
|
||||
{this.table.emptyText}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
},
|
||||
};
|
||||
84
manager/src/components/tree-table/Table/TableFooter.js
Normal file
84
manager/src/components/tree-table/Table/TableFooter.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import { mixins } from './utils';
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
export default {
|
||||
name: 'TreeTable__footer',
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
table() {
|
||||
return this.$parent;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
},
|
||||
render() {
|
||||
// 计算各列总和
|
||||
function renderCell({ key }, columnIndex) {
|
||||
if (columnIndex === 0) {
|
||||
return this.table.sumText;
|
||||
}
|
||||
const rows = this.table.bodyData;
|
||||
const values = rows.map(row => Number(row[key]));
|
||||
const precisions = [];
|
||||
let notNumber = true;
|
||||
values.forEach((value) => {
|
||||
if (!isNaN(value)) {
|
||||
notNumber = false;
|
||||
const decimal = value.toString().split('.')[1];
|
||||
precisions.push(decimal ? decimal.length : 0);
|
||||
}
|
||||
});
|
||||
const precision = Math.max.apply(null, precisions);
|
||||
if (!notNumber) {
|
||||
return values.reduce((prev, curr) => {
|
||||
const value = Number(curr);
|
||||
if (!isNaN(value)) {
|
||||
return parseFloat((prev + curr).toFixed(precision));
|
||||
}
|
||||
return prev;
|
||||
}, 0);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// className
|
||||
function getClassName() {
|
||||
const classList = [];
|
||||
classList.push(`${this.prefixCls}__footer-cell`);
|
||||
if (this.table.border) {
|
||||
classList.push(`${this.prefixCls}--border-cell`);
|
||||
}
|
||||
return classList.join(' ');
|
||||
}
|
||||
|
||||
// Template
|
||||
return (
|
||||
<table cellspacing="0" cellpadding="0" border="0" class={ `${this.prefixCls}__footer` }>
|
||||
<colgroup>
|
||||
{ this.table.tableColumns.map(column =>
|
||||
<col width={ column.computedWidth || column.minWidth || column.width }></col>)
|
||||
}
|
||||
</colgroup>
|
||||
<tfoot>
|
||||
<tr class={ `${this.prefixCls}__footer-row` }>
|
||||
{ this.table.tableColumns.map((column, columnIndex) =>
|
||||
<td class={ getClassName.call(this) }>
|
||||
<div class={ `${this.prefixCls}__cell-inner` }>
|
||||
{ this.table.summaryMethod
|
||||
? this.table.summaryMethod(this.table.bodyData, column, columnIndex)
|
||||
: renderCell.call(this, column, columnIndex) }
|
||||
</div>
|
||||
</td>)
|
||||
}
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
);
|
||||
},
|
||||
};
|
||||
108
manager/src/components/tree-table/Table/TableHeader.js
Normal file
108
manager/src/components/tree-table/Table/TableHeader.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import Checkbox from "../Checkbox/Checkbox"; // eslint-disable-line
|
||||
import { mixins } from "./utils";
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
export default {
|
||||
name: "TreeTable__header",
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
table() {
|
||||
return this.$parent;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleAllChecked(checked) {
|
||||
this.table.bodyData = this.table.bodyData.map(row => ({
|
||||
...row,
|
||||
_isChecked: checked
|
||||
}));
|
||||
}
|
||||
},
|
||||
render() {
|
||||
// className
|
||||
function getClassName(type, { headerAlign, key }) {
|
||||
const certainType = this.validateType(
|
||||
type,
|
||||
["cell", "inner"],
|
||||
"getClassName"
|
||||
);
|
||||
const classList = [];
|
||||
if (key == "_normalIndex") {
|
||||
classList.push(`${this.prefixCls}--center-cell`);
|
||||
}
|
||||
if (certainType.cell) {
|
||||
classList.push(`${this.prefixCls}__header-cell`);
|
||||
if (this.table.border) {
|
||||
classList.push(`${this.prefixCls}--border-cell`);
|
||||
}
|
||||
if (["center", "right"].indexOf(headerAlign) > -1) {
|
||||
classList.push(`${this.prefixCls}--${headerAlign}-cell`);
|
||||
}
|
||||
}
|
||||
if (certainType.inner) {
|
||||
classList.push(`${this.prefixCls}__cell-inner`);
|
||||
if (this.table.treeType && this.table.firstProp === key) {
|
||||
classList.push(`${this.prefixCls}--firstProp-header-inner`);
|
||||
}
|
||||
}
|
||||
return classList.join(" ");
|
||||
}
|
||||
|
||||
// 根据type渲染单元格Label
|
||||
function renderLabel(column, columnIndex) {
|
||||
if (
|
||||
this.isSelectionCell(this.table, columnIndex) &&
|
||||
this.selectType === "checkbox"
|
||||
) {
|
||||
const allCheck = this.table.bodyData.every(row => row._isChecked);
|
||||
const indeterminate =
|
||||
!allCheck && this.table.bodyData.some(row => row._isChecked);
|
||||
return (
|
||||
<Checkbox
|
||||
indeterminate={indeterminate}
|
||||
value={allCheck}
|
||||
onOn-change={checked => this.toggleAllChecked(checked)}
|
||||
></Checkbox>
|
||||
);
|
||||
}
|
||||
return column.title ? column.title : "";
|
||||
}
|
||||
|
||||
// Template
|
||||
return (
|
||||
<table
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
class={`${this.prefixCls}__header`}
|
||||
>
|
||||
{/* <colgroup>
|
||||
{this.table.tableColumns.map(column => (
|
||||
<col
|
||||
width={column.computedWidth || column.minWidth || column.width}
|
||||
></col>
|
||||
))}
|
||||
</colgroup> */}
|
||||
<thead>
|
||||
<tr class={`${this.prefixCls}__header-row`}>
|
||||
{
|
||||
this.table.tableColumns.map((column, columnIndex) =>
|
||||
column.key ? (
|
||||
<th class={getClassName.call(this, "cell", column)}>
|
||||
<div class={getClassName.call(this, "inner", column)}>
|
||||
{renderLabel.call(this, column, columnIndex)}
|
||||
</div>
|
||||
</th>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
};
|
||||
BIN
manager/src/components/tree-table/Table/font/iconfont.eot
Normal file
BIN
manager/src/components/tree-table/Table/font/iconfont.eot
Normal file
Binary file not shown.
22
manager/src/components/tree-table/Table/font/iconfont.less
Normal file
22
manager/src/components/tree-table/Table/font/iconfont.less
Normal file
@@ -0,0 +1,22 @@
|
||||
// icon
|
||||
@font-face {font-family: "iconfont";
|
||||
src: url('iconfont.eot?t=1505310522875'); /* IE9*/
|
||||
src: url('iconfont.eot?t=1505310522875#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAW0AAsAAAAACOQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kggY21hcAAAAYAAAABuAAABojLtBtFnbHlmAAAB8AAAAa8AAAKA73SzT2hlYWQAAAOgAAAALwAAADYO3fRqaGhlYQAAA9AAAAAcAAAAJAfeA4ZobXR4AAAD7AAAABMAAAAUE+kAAGxvY2EAAAQAAAAADAAAAAwBbAHYbWF4cAAABAwAAAAeAAAAIAEUAF1uYW1lAAAELAAAAUUAAAJtPlT+fXBvc3QAAAV0AAAAQAAAAFryy5h0eJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/s04gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDwzZm7438AQw9zA0AAUZgTJAQAoHgyieJzFkcENwyAUQ98HyqHKKDmEZoEOklOnYOK/RmI+uXSCWDLG/pZAALyALK5iAfthDBxKLfLMO/LCJl+lRqL7fp7y3VuoKprV0KxO0qbyGOy5o/+xxPq9nV6YflNX9DY5fsA/k6Pj+yTpAn3jEO8AAHiclVDNitNQFD7n3slNE9vE5N7kpOn0J0mbKB3DGDMZRGw3bhQXA2LB5TyAbmfjohvBhQvfYEAEoc8wr+EDiK4KPkITU0EcXDmHw3fOgfN9fHygATTf+BUPQMIduA9P4AwAxRxjiw0xysqczdGLNI+UxbMki/QkzvljpFgov6jKlIQubLRwhA+iospyluFJuWCPsPCHiP1B+MKdHbr8I5pBNnpXP2Of0Bsnh/biXv30aKmKiexcdF2377ofOkLTOowd2Ba+Jt/QDFPUnzU79K7Gd9kYu/0sfP6qNxm45+/LN8MZGYjrNcrBxPqydEKn7behL92+frvXCcJeMlV48eNWILvD9Du0hXtgl+wSHIBZnJZLzNKyKgh9paPAdVca260hAxPBMBowz9t1uzUDuT8CswEDDtq8Gr7mADaM4QgetrKRhbozQooWeOrkKJVIojg9ccqqjcT3uBJ6lGNZnUYjnBU+ORbGaeYskM93m+kx4vGUrX5PQXK3kUSSrSS9JFWvFJHCjaIGJCFSugcOLeM6c7f6wyFZf/37+PO6AgD/x/vNnN/A7H8bhF86tmcbAHicY2BkYGAAYl/p/jnx/DZfGbhZGEDg6v0IKwT9/yELA7MEkMvBwAQSBQAZYgnZAHicY2BkYGBu+N/AEMPCAAJAkpEBFbACAEcLAm54nGNhYGBgfsnAwMKAwAAOmwD9AAAAAAAAdgCYAPYBQHicY2BkYGBgZQgEYhBgAmIuIGRg+A/mMwAAES0BcgAAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicY2BigAAuBuyAlZGJkZmRhZGVkY2BsYI7MS89J1W3KDM9o4S3IKe0WLe4sDSxKFU3ny83Mw+Jy8AAAHSWD8A=') format('woff'),
|
||||
url('iconfont.ttf?t=1505310522875') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
|
||||
url('iconfont.svg?t=1505310522875#iconfont') format('svg'); /* iOS 4.1- */
|
||||
}
|
||||
|
||||
.zk-icon {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.zk-icon-plus-square-o:before { content: "\e631"; }
|
||||
|
||||
.zk-icon-minus-square-o:before { content: "\e632"; }
|
||||
|
||||
.zk-icon-angle-right:before { content: "\e633"; }
|
||||
42
manager/src/components/tree-table/Table/font/iconfont.svg
Normal file
42
manager/src/components/tree-table/Table/font/iconfont.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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" >
|
||||
<!--
|
||||
2013-9-30: Created.
|
||||
-->
|
||||
<svg>
|
||||
<metadata>
|
||||
Created by iconfont
|
||||
</metadata>
|
||||
<defs>
|
||||
|
||||
<font id="iconfont" horiz-adv-x="1024" >
|
||||
<font-face
|
||||
font-family="iconfont"
|
||||
font-weight="500"
|
||||
font-stretch="normal"
|
||||
units-per-em="1024"
|
||||
ascent="896"
|
||||
descent="-128"
|
||||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="x" unicode="x" horiz-adv-x="1001"
|
||||
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
|
||||
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
|
||||
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
|
||||
|
||||
|
||||
|
||||
<glyph glyph-name="angle-right" unicode="" d="M384.087 97.474c-7.857 0-15.713 2.997-21.707 8.992-11.989 11.989-11.989 31.426 0 43.415l234.118 234.12-234.118 234.118c-11.988 11.989-11.988 31.427 0 43.416 11.989 11.988 31.426 11.988 43.416 0l255.826-255.827c11.989-11.989 11.989-31.427 0-43.416l-255.826-255.827c-5.995-5.995-13.851-8.992-21.708-8.992z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="plus-square-o" unicode="" d="M810.666667 768H213.333333c-46.933333 0-85.333333-38.4-85.333333-85.333333v-597.333334c0-46.933333 38.4-85.333333 85.333333-85.333333h597.333334c46.933333 0 85.333333 38.4 85.333333 85.333333V682.666667c0 46.933333-38.4 85.333333-85.333333 85.333333z m42.666666-682.666667c0-25.6-17.066667-42.666667-42.666666-42.666666H213.333333c-25.6 0-42.666667 17.066667-42.666666 42.666666V682.666667c0 25.6 17.066667 42.666667 42.666666 42.666666h597.333334c25.6 0 42.666667-17.066667 42.666666-42.666666v-597.333334zM768 384c0-25.6-17.066667-42.666667-42.666667-42.666667H298.666667c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666667h426.666666c25.6 0 42.666667-17.066667 42.666667-42.666667zM512 640c25.6 0 42.666667-17.066667 42.666667-42.666667v-426.666666c0-25.6-17.066667-42.666667-42.666667-42.666667s-42.666667 17.066667-42.666667 42.666667V597.333333c0 25.6 17.066667 42.666667 42.666667 42.666667z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="minus-square-o" unicode="" d="M810.666667 768H213.333333c-46.933333 0-85.333333-38.4-85.333333-85.333333v-597.333334c0-46.933333 38.4-85.333333 85.333333-85.333333h597.333334c46.933333 0 85.333333 38.4 85.333333 85.333333V682.666667c0 46.933333-38.4 85.333333-85.333333 85.333333z m42.666666-682.666667c0-25.6-17.066667-42.666667-42.666666-42.666666H213.333333c-25.6 0-42.666667 17.066667-42.666666 42.666666V682.666667c0 25.6 17.066667 42.666667 42.666666 42.666666h597.333334c25.6 0 42.666667-17.066667 42.666666-42.666666v-597.333334zM768 384c0-25.6-17.066667-42.666667-42.666667-42.666667H298.666667c-25.6 0-42.666667 17.066667-42.666667 42.666667s17.066667 42.666667 42.666667 42.666667h426.666666c25.6 0 42.666667-17.066667 42.666667-42.666667z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
|
||||
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
BIN
manager/src/components/tree-table/Table/font/iconfont.ttf
Normal file
BIN
manager/src/components/tree-table/Table/font/iconfont.ttf
Normal file
Binary file not shown.
BIN
manager/src/components/tree-table/Table/font/iconfont.woff
Normal file
BIN
manager/src/components/tree-table/Table/font/iconfont.woff
Normal file
Binary file not shown.
6
manager/src/components/tree-table/Table/utils/index.js
Normal file
6
manager/src/components/tree-table/Table/utils/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import mixins from './mixins';
|
||||
import scrollBarWidth from './scrollBarWidth';
|
||||
export {
|
||||
mixins,
|
||||
scrollBarWidth
|
||||
}
|
||||
34
manager/src/components/tree-table/Table/utils/mixins.js
Normal file
34
manager/src/components/tree-table/Table/utils/mixins.js
Normal file
@@ -0,0 +1,34 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
prefixCls: 'zk-table',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
validateType(type, validTypes, funcName, isReturn = true) {
|
||||
if (validTypes.indexOf(type) < 0) throw new Error(`${funcName}'s type must is ${validTypes.join(' or ')}.`);
|
||||
if (isReturn) {
|
||||
const certainType = {};
|
||||
validTypes.forEach((item) => {
|
||||
certainType[item] = item === type;
|
||||
});
|
||||
return certainType;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
isExpandCell(table, columnIndex) {
|
||||
return table.expandType && (
|
||||
(table.showIndex && columnIndex === 1) ||
|
||||
(!table.showIndex && columnIndex === 0)
|
||||
);
|
||||
},
|
||||
isSelectionCell(table, columnIndex) {
|
||||
return table.selectable && (
|
||||
(table.showIndex && table.expandType && columnIndex === 2) ||
|
||||
(!table.showIndex && table.expandType && columnIndex === 1) ||
|
||||
(table.showIndex && !table.expandType && columnIndex === 1) ||
|
||||
(!table.showIndex && !table.expandType && columnIndex === 0)
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
let scrollBarWidth;
|
||||
|
||||
export default function () {
|
||||
if (Vue.prototype.$isServer) return 0;
|
||||
if (scrollBarWidth !== undefined) return scrollBarWidth;
|
||||
|
||||
const outer = document.createElement('div');
|
||||
outer.style.visibility = 'hidden';
|
||||
outer.style.width = '100px';
|
||||
outer.style.position = 'absolute';
|
||||
outer.style.top = '-9999px';
|
||||
document.body.appendChild(outer);
|
||||
|
||||
const widthNoScroll = outer.offsetWidth;
|
||||
outer.style.overflow = 'scroll';
|
||||
|
||||
const inner = document.createElement('div');
|
||||
inner.style.width = '100%';
|
||||
outer.appendChild(inner);
|
||||
|
||||
const widthWithScroll = inner.offsetWidth;
|
||||
outer.parentNode.removeChild(outer);
|
||||
scrollBarWidth = widthNoScroll - widthWithScroll;
|
||||
|
||||
return scrollBarWidth;
|
||||
}
|
||||
14
manager/src/components/verify/README.md
Normal file
14
manager/src/components/verify/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
### 滑动拼图验证
|
||||
|
||||
### 在页面中引入 .vue文件
|
||||
|
||||
#### 参数
|
||||
|
||||
#### 在组件上添加v-if来判断组件显隐
|
||||
|
||||
#### verifyType 验证格式[ 'LOGIN' ,'REGISTER' ]
|
||||
|
||||
#### @change方法 获取回调,参数为对象 {status:false,distance:100} status 为回调状态,distance为移动距离
|
||||
|
||||
|
||||
#### <Verify class="verify-content" verifyType='LOGIN' @change="verifyChange"></Verify>
|
||||
191
manager/src/components/verify/index.vue
Normal file
191
manager/src/components/verify/index.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="verify-content" v-if="show" @mousemove="mouseMove" @mouseup="mouseUp" @click.stop>
|
||||
<div class="imgBox" :style="{width:data.originalWidth+'px',height:data.originalHeight + 'px'}">
|
||||
<img :src="data.backImage" style="width:100%;height:100%" alt="">
|
||||
<img class="slider" :src="data.slidingImage" :style="{left:distance+'px',top:data.randomY+'px'}" :width="data.sliderWidth" :height="data.sliderHeight" alt="">
|
||||
<Icon type="md-refresh" class="refresh" @click="init" />
|
||||
</div>
|
||||
<div class="handle" :style="{width:data.originalWidth+'px'}">
|
||||
<span class="bgcolor" :style="{width:distance + 'px',background:bgColor}"></span>
|
||||
<span class="swiper" :style="{left:distance + 'px'}" @mousedown="mouseDown">
|
||||
<Icon type="md-arrow-round-forward" />
|
||||
</span>
|
||||
<span class="text">{{verifyText}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getVerifyImg, postVerifyImg } from './verify.js';
|
||||
export default {
|
||||
props: {
|
||||
// 传入数据,判断是登录、注册、修改密码
|
||||
verifyType: {
|
||||
defalut: 'LOGIN',
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
show: false, // 验证码显隐
|
||||
type: 'LOGIN', // 请求类型
|
||||
data: { // 验证码数据
|
||||
backImage: '',
|
||||
slidingImage: '',
|
||||
originalHeight: 150,
|
||||
originalWidth: 300,
|
||||
sliderWidth: 60,
|
||||
sliderHeight: 60
|
||||
},
|
||||
distance: 0, // 拼图移动距离
|
||||
flag: false, // 判断滑块是否按下
|
||||
downX: 0, // 鼠标按下位置
|
||||
bgColor: '#04ad11', // 滑动背景颜色
|
||||
verifyText: '拖动滑块解锁' // 文字提示
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 鼠标按下事件,开始拖动滑块
|
||||
mouseDown (e) {
|
||||
this.downX = e.clientX;
|
||||
this.flag = true;
|
||||
},
|
||||
// 鼠标移动事件,计算距离
|
||||
mouseMove (e) {
|
||||
if (this.flag) {
|
||||
let offset = e.clientX - this.downX;
|
||||
|
||||
if (offset > this.data.originalWidth - 43) {
|
||||
this.distance = this.data.originalWidth - 43;
|
||||
} else if (offset < 0) {
|
||||
this.distance = 0;
|
||||
} else {
|
||||
this.distance = offset;
|
||||
}
|
||||
}
|
||||
},
|
||||
// 鼠标抬起事件,验证是否正确
|
||||
mouseUp () {
|
||||
if (!this.flag) return false;
|
||||
this.flag = false;
|
||||
let params = {
|
||||
verificationEnums: this.type,
|
||||
xPos: this.distance
|
||||
};
|
||||
postVerifyImg(params).then(res => {
|
||||
if (res.success) {
|
||||
if (res.result) {
|
||||
this.bgColor = 'green';
|
||||
this.verifyText = '解锁成功';
|
||||
this.$emit('change', { status: true, distance: this.distance });
|
||||
} else {
|
||||
this.bgColor = 'red';
|
||||
this.verifyText = '解锁失败';
|
||||
let that = this;
|
||||
setTimeout(() => {
|
||||
that.init();
|
||||
}, 1000);
|
||||
this.$emit('change', { status: false, distance: this.distance });
|
||||
}
|
||||
} else {
|
||||
this.init()
|
||||
}
|
||||
|
||||
}).catch(()=>{
|
||||
this.init()
|
||||
});
|
||||
},
|
||||
init () { // 初始化数据
|
||||
this.flag = false;
|
||||
this.downX = 0;
|
||||
this.distance = 0;
|
||||
this.bgColor = '#04ad11';
|
||||
this.verifyText = '拖动滑块解锁';
|
||||
getVerifyImg(this.type).then(res => {
|
||||
if (res.result) {
|
||||
this.data = res.result;
|
||||
this.show = true;
|
||||
} else {
|
||||
this.$Message.warning('请求失败请重试!')
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
verifyType: {
|
||||
immediate: true,
|
||||
handler: function (v) {
|
||||
this.type = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.verify-content{
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 1px 3px #999;
|
||||
}
|
||||
.imgBox {
|
||||
width: 300px;
|
||||
height: 150px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.refresh {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.handle {
|
||||
border: 1px solid #e4dede;
|
||||
margin-top: 5px;
|
||||
height: 42px;
|
||||
background: #ddd;
|
||||
position: relative;
|
||||
|
||||
.bgcolor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
opacity: 0.5;
|
||||
background: #04ad11;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.ivu-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
display: inline-block;
|
||||
width: inherit;
|
||||
text-align: center;
|
||||
line-height: 42px;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
13
manager/src/components/verify/verify.js
Normal file
13
manager/src/components/verify/verify.js
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
import {commonUrl, getRequestWithNoToken, postRequestWithNoToken} from '@/libs/axios';
|
||||
|
||||
|
||||
// 获取拼图验证
|
||||
export const getVerifyImg = (verificationEnums) => {
|
||||
return getRequestWithNoToken(`${commonUrl}/common/common/slider/${verificationEnums}`);
|
||||
};
|
||||
|
||||
// 拼图验证
|
||||
export const postVerifyImg = (params) => {
|
||||
return postRequestWithNoToken(`${commonUrl}/common/common/slider/${params.verificationEnums}`, params);
|
||||
};
|
||||
Reference in New Issue
Block a user