commit message

This commit is contained in:
Chopper
2021-05-13 10:56:04 +08:00
commit ec3e958037
728 changed files with 132685 additions and 0 deletions

View File

@@ -0,0 +1,633 @@
<template>
<Collapse v-model="open_panel">
<Panel name="1">
规格名称
<div slot="content" class="sku-item-content">
<Form :model="skuForm" @submit.native.prevent style="width:100%;">
<div v-for="(item, $index) in skuInfo" :key="$index" class="sku_value">
<Card :bordered="true" style="margin: 15px 0;">
<FormItem label="规格名:" class="sku-item-content-name">
<AutoComplete
style="width: 150px;"
v-model="item.name"
:maxlength="30"
:data="skuKey"
placeholder="请输入规格项名称"
:filter-method="filterMethod"
@on-change="skuItemChange(item.name,$index)"
@keyup.enter.native="editSkuItem(item, $index)"
>
<!-- @on-focus="getActiveSkuItem(index, $index ,item, val)"
@on-blur="editSkuItem(item, $index)"
on-change 变更
on-focus 聚焦时触发 event
on-blur 失焦时触发
keyup.enter.native 回车时 -->
</AutoComplete>
<Button
type="error"
style="margin-left: 10px;"
@click="handleCloseSkuItem($index)"
>删除</Button>
<Checkbox
v-if="$index === 0"
style="padding: 0px 0px 0px 30px;"
v-model="checkedImage"
@on-change="handleChangeImage"
>规格图片</Checkbox>
</FormItem>
<FormItem label="规格值:" class="sku-item-content-value">
<!--规格值文本列表-->
<div
v-for="(val, index) in item.spec_values"
:key="index"
style="padding: 0px 20px 10px 0px;float: left"
>
<div style="display:inline-block;">
<AutoComplete
style="width: 150px; float: left"
v-model="val.value"
:maxlength="30"
:data="skuValue"
placeholder="请输入规格值名称"
:filter-method="filterMethod"
@on-change="skuValueChange(val.value,$index)"
@on-focus="editSkuIValue($index)"
@keyup.enter.native="editSkuIValue(item, val, $index, index)"
></AutoComplete>
<Button
type="error"
style="float: left;margin-left: 10px"
@click="handleCloseSkuValue($index, index)"
>删除</Button>
</div>
<div>
<upload-pic-thumb
style="text-align: left;"
v-show="$index === 0 && checkedImage"
v-model="val.spec_image" v-on:change="thumbChange" :multiple="false" v-on:uploadchange="uploadchange"
/>
</div>
</div>
<div style="float: left">
<Button type="primary" @click="addSpec($index, item)">添加规格值</Button>
</div>
</FormItem>
</Card>
</div>
</Form>
<Button type="primary" size="mini" @click="addSkuItem">添加规格项目</Button>
</div>
</Panel>
<Panel name="2">
规格详细
<div slot="content">
<div slot="content">
<Table
:columns="skuTableColumn"
:data="skuTableData"
style="width: 100%;.ivu-table-overflowX{ overflow-x: hidden;}"
:span-method="handleSpan"
>
<template slot-scope="{ row }" slot="sn">
<Input
v-model="row.sn"
placeholder="请输入货号"
@on-change="updateSkuTable(row,'sn')"
@on-focus="updateSkuTable(row,'sn')"
@on-blur="updateSkuTable(row,'sn')"
/>
</template>
<template slot-scope="{ row }" slot="weight">
<Input
v-model="row.weight"
placeholder="请输入重量"
@on-change="updateSkuTable(row,'weight')"
/>
</template>
<template slot-scope="{ row }" slot="quantity">
<Input
v-model="row.quantity"
placeholder="请输入库存"
@on-change="updateSkuTable(row,'quantity')"
/>
</template>
<template slot-scope="{ row }" slot="cost">
<Input
v-model="row.cost"
placeholder="请输入成本价"
@on-change="updateSkuTable(row,'cost')"
/>
</template>
<template slot-scope="{ row }" slot="price">
<Input
v-model="row.price"
placeholder="请输入成本价"
@on-change="updateSkuTable(row,'price')"
/>
</template>
</Table>
</div>
</div>
</Panel>
</Collapse>
</template>
<script>
import { regular } from "@/utils";
import uploadPicThumb from "../lili/upload-pic-thumb.vue";
import cloneObj from "@/utils/index";
export default {
name: "goodsSku",
components: {
uploadPicThumb
},
props: {
open_panel: {
type: [String, Number],
default: false
},
/** 是否自动生成货号 */
productSn: {
type: Boolean,
default: false
},
/** 当前商品编号 */
goodsSn: {
type: [String, Number],
default: ['', 0]
}
},
data() {
return {
/** 请求数据*/
skuData: [],
/** 当前可选择的 规格名称*/
skuKey: [],
/** 当前可选择的 规格值*/
skuValue: [],
/** 表单数据*/
skuForm: {},
/** 表格头 */
skuTableColumn: [],
/** 表格数据 */
skuTableData: [
{
sn: '',
weight: '',
quantity: '',
cost: '',
price: ''
}
],
/** 要提交的规格数据*/
skuInfo: [],
/** 当前规格项下的规格值列表*/
specList: [],
/** 当前规格项索引 */
activeSkuItemIndex: 0,
/** 当前规格项 */
activeSkuItem: {},
/** 规格图片 */
images: [],
/** 当前规格值索引 */
activeSkuValIndex: 0,
/** 当前规格值 */
activeSkuVal: {},
/** 存储未通过校验的单元格位置 */
validateError: [],
//规格照片
checkedImage:false
};
},
mounted(){
},
watch: {
/** 自动生成货号 */
productSn() {
if (this.productSn && this.skuTableData.length > 0) {
let count = 1
this.skuTableData.forEach(key => {
key.sn = this.goodsSn + '-00000' + count
count++
})
/** 异步更新skuTableData数据 */
this.$emit("getTableData", this.skuTableData);
}
}
},
methods: {
uploadchange(url){
/**
* 渲染规格详细表格
*/
this.renderTableData();
},
/** 添加规格项 */
addSkuItem() {
// 写入对象,下标,具体对象
this.$set(this.skuInfo, this.skuInfo.length, {
spec_values: []
});
/**
* 渲染规格详细表格
*/
this.renderTableData();
},
/** 移除当前规格项 进行数据变化*/
handleCloseSkuItem($index) {
this.skuInfo.splice($index, 1);
/**
* 渲染规格详细表格
*/
this.renderTableData();
},
/** 规格变更 */
skuItemChange(value, $index) {
this.activeSkuItemIndex = $index;
//判定是否存在这个规格分组
let item = this.findSpec(value);
/** 更新skuInfo数据 */
this.$set(this.skuInfo[this.activeSkuItemIndex], "name", item.name);
this.$set(this.skuInfo[this.activeSkuItemIndex], "spec_values", []);
/**
* 渲染规格详细表格
*/
this.renderTableData();
},
/**
* 根据规格项名称,搜索对应的规格对象(如果是服务器设置过的话)
*/
findSpec(name) {
let spec = { name: name };
this.skuData.forEach(item => {
if (item.name === name) {
spec = item;
}
});
return spec;
},
/** 选中/不选中 添加规格图片 是否显示上传组件*/
handleChangeImage(val) {
this.$set(this.skuInfo, this.skuInfo.length, {});
this.skuInfo.splice(this.skuInfo.length - 1, 1);
this.checkedImage = val;
/** 如果 图片按钮不显示 则置空图片列表中的所有图片数据 并且spec_type设置为0 */
if (!val) {
this.skuInfo.forEach(key => {
if (key.spec_values) {
key.spec_values.forEach(item => {
item.spec_image = "";
});
}
});
}
/**
* 渲染规格详细表格
*/
this.renderTableData();
},
/** 规格值 */
/** 添加当前规格项的规格值*/
addSpec($index, item) {
this.activeSkuItemIndex = $index;
this.$set(
this.skuInfo[$index].spec_values,
this.skuInfo[$index].spec_values.length,
{
name: item.name
}
);
/**
* 渲染规格详细表格
*/
this.renderTableData();
},
/** 移除当前规格值 */
handleCloseSkuValue($index, index) {
this.skuInfo[$index].spec_values.splice(index, 1);
/**
* 渲染规格详细表格
*/
this.renderTableData();
},
/** 选择规格值时触发 */
skuValueChange(val) {
/** 更新skuInfo数据 */
let _arr = cloneObj(this.skuInfo[this.activeSkuItemIndex]);
this.$set(_arr.spec_values[this.activeSkuValIndex], "name", _arr.name);
this.$set(this.skuInfo, this.activeSkuItemIndex, _arr);
/**
* 渲染规格详细表格
*/
this.renderTableData();
},
/** 编辑规格值时触发 */
editSkuIValue($index) {
if (this.skuInfo.length >= $index) {
let data = this.findSpec(this.skuInfo[$index].name);
this.skuValue = data.value;
}
/**
* 渲染规格详细表格
*/
this.renderTableData();
},
/** 获取编辑时的skuInfo信息 */
getSkuInfo() {
/** 下拉列表数据(skuData)存在时 检测productSkuInfo中对应的规格(spec_id)项 并且赋值于skuInfo中对应的规格项信息描述 + 名称) */
if (this.categoryId) {
API_spec.getSpecValuesListData(this.categoryId, {}).then(response => {
this.skuData = response;
if (
this.skuData.length > 0 &&
Array.isArray(this.productSkuInfo) &&
this.productSkuInfo.length > 0
) {
this.skuInfo = cloneObj(this.productSkuInfo);
if (this.skuInfo.length > 0) {
this.skuInfo.forEach(key => {
this.skuData.forEach(item => {
if (key.spec_id === item.spec_id) {
key.name = item.name;
key.spec_memo = item.spec_memo;
}
});
});
}
/** 如果存在图片则进行显示 */
if (this.skuInfo[0].spec_values[0].spec_type === 1) {
this.checkedImage = true;
}
/** 触发一次数据转换(规格选择数据=> 规格表格数据) */
}
});
}
/**
* 渲染规格详细表格
*/
this.renderTableData();
},
/**
* 渲染table所需要的column 和 data
*/
renderTableData() {
this.skuTableColumn = [];
this.skuTableData = [];
//渲染头部
this.skuInfo.forEach(sku => {
//列名称
let columnName = sku.name;
this.skuTableColumn.push({
title: columnName,
key: columnName
});
});
this.skuTableColumn.push(
{
title: "货号",
slot: "sn"
},
{
title: "重量",
slot: "weight"
},
{
title: "库存",
slot: "quantity"
},
{
title: "成本价",
slot: "cost"
},
{
title: "价格",
slot: "price"
}
);
//克隆所有渲染的数据
let cloneTemp = cloneObj(this.skuInfo);
//数据清空一次
this.skuTableData = [];
//判定 是否存在规格分组
if (cloneTemp[0]) {
//存放最终结果
let result = [];
//记录存放表格的 key
//循环选中的 sku 数据
if(this.checkedImage===false){
cloneTemp[0].spec_values.forEach(specItem => {
result.push({
[specItem.name]: specItem.value
});
});
}else{
cloneTemp[0].spec_values.forEach(specItem => {
result.push({
[specItem.name]: specItem.value,
images: specItem.spec_image
});
});
}
cloneTemp.splice(0, 1);
result = this.specIterator(result, cloneTemp);
result = this.defaultParams(result);
this.skuTableData = result;
}
this.$emit("getTableData", this.skuTableData);
},
/** 自动完成表单所需方法*/
filterMethod(value, option) {
return option.toUpperCase().indexOf(value.toUpperCase()) !== -1;
},
/** 根据分类id获取系统设置规格信息*/
Get_SkuInfoByCategory() {
if (this.baseInfoForm.categoryId) {
getGoodsSpecInfo(this.baseInfoForm.categoryId, {}).then(response => {
this.skuData = response;
if (this.skuData.length > 0) {
this.skuData.forEach(spec => {
this.skuKey.push(spec.name);
});
}
});
}
},
/** 检测是否未通过0-99999999之间的数字校验 */
isValidate(index, scope) {
return this.validateError.some(key => {
return key[0] === index && key[1] === scope.$index
})
},
/** 数据改变之后 抛出数据 */
updateSkuTable(row, item) {
let index = row._index;
this.skuTableData[index][item] = row[item];
/** 进行自定义校验 判断是否是数字(小数也能通过)重量 */
if (
(!/^[+]{0,1}(\d+)$|^[+]{0,1}(\d+\.\d+)$/.test(row[item]) &&
item === "weight") ||
parseInt(row[item]) < 0 ||
parseInt(row[item]) > 99999999
) {
this.$Message.error("请输入0~99999999之间的数字值");
return;
} else if (
(item === "quantity" && !/^[0-9]\d*$/.test(row[item])) ||
parseInt(row[item]) < 0 ||
parseInt(row[item]) > 99999999
) {
// 库存
this.$Message.error("请输入0~99999999之间的整数");
return;
} else if (
((item === "cost" || item === "price") &&
!regular.money.test(row[item])) ||
parseInt(row[item]) < 0 ||
parseInt(row[item]) > 99999999
) {
// 成本价 价格
this.$Message.error("请输入0~99999999之间的价格");
return;
}
this.skuTableData[index][item] = row[item];
this.$emit("getTableData", this.skuTableData);
// console.log(999, this.skuTableData)
},
/**
* 迭代属性,形成表格
* result 渲染的数据
* array spec数据
*/
specIterator(result, cloneTemp) {
//是否还可以循环
if (cloneTemp.length > 0) {
let table = [];
result.forEach(resItem => {
let tableItem = [];
cloneTemp[0].spec_values.forEach(valItem => {
let obj = cloneObj(resItem);
obj[valItem.name] = valItem.value;
table.push(obj);
});
});
result = [];
table.forEach(t => {
result.push(t);
});
//清除当前循环的分组
cloneTemp.splice(0, 1);
} else {
return result;
}
return this.specIterator(result, cloneTemp);
},
showData() {
// console.log(JSON.stringify(this.skuTableData));
// console.warn(this.specList);
},
/**
* 添加固有属性
*/
defaultParams(tableData) {
return tableData;
},
handleSpan({ row, column, rowIndex, columnIndex }) {},
thumbChange(){
this.$emit("getTableData", this.skuTableData);
}
},
};
</script>
<style lang="scss" scoped>
.layout {
margin-bottom: 20px;
.sku-item-content {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 100%;
.sku-item-content-name {
margin-top: 24px;
display: flex;
align-items: flex-start;
width: 100%;
}
}
.demo-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,.2);
margin-right: 4px;
}
.demo-upload-list img{
width: 100%;
height: 100%;
}
.demo-upload-list-cover{
display: none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0,0,0,.6);
}
.demo-upload-list:hover .demo-upload-list-cover{
display: block;
}
.demo-upload-list-cover i{
color: #fff;
font-size: 20px;
cursor: pointer;
margin: 0 2px;
}
}
</style>

View 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>

View File

@@ -0,0 +1,114 @@
<template>
<div>
<Button
:countTime="countTime"
:loading="loading"
:type="type"
:size="size"
:ghost="ghost"
:disabled="disabled||clicked"
:icon="icon"
:shape="shape"
:long="long"
@click="handleClick"
>{{buttonText}}</Button>
</div>
</template>
<script>
export default {
name: "iconChoose",
props: {
text: {
type: String,
default: "提交"
},
autoCountDown: {
type: Boolean,
default: true
},
countTime: {
type: [Number, String],
default: 60
},
suffixText: {
type: String,
default: "后重试"
},
type: String,
size: String,
loading: {
type: Boolean,
default: false
},
ghost: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
icon: String,
shape: String,
long: {
type: Boolean,
default: false
}
},
data() {
return {
buttonText: this.text,
count: Number(this.countTime),
clicked: false
};
},
methods: {
init() {},
handleClick() {
if (this.autoCountDown) {
this.clicked = true;
this.countDown();
}
this.$emit("on-click", true);
},
startCountDown() {
this.clicked = true;
this.countDown();
},
countDown() {
let that = this;
if (this.count == 0) {
this.clicked = false;
this.count = this.countTime;
this.buttonText = this.text;
return;
} else {
this.buttonText = this.count + " 秒" + this.suffixText;
this.count--;
}
setTimeout(function() {
that.countDown();
}, 1000);
},
setText(value) {
if (value === this.buttonText) {
return;
}
this.buttonText = value;
},
},
watch: {
text(val) {
this.setText(val);
}
},
mounted() {
this.init();
}
};
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div>
<Cascader
v-model="selectDep"
:data="department"
:load-data="loadData"
@on-change="handleChangeDep"
change-on-select
filterable
clearable
placeholder="请选择"
></Cascader>
</div>
</template>
<script>
import { initDepartment, loadDepartment } from "@/api/index";
export default {
name: "departmentChoose",
props: {
},
data() {
return {
selectDep: [],
department: []
};
},
methods: {
initDepartmentData() {
initDepartment().then(res => {
if (res.success) {
res.result.forEach(function(e) {
if (e.isParent) {
e.value = e.id;
e.label = e.title;
e.loading = false;
e.children = [];
} else {
e.value = e.id;
e.label = e.title;
}
if (e.status == -1) {
e.label = "[已禁用] " + e.label;
e.disabled = true;
}
});
this.department = res.result;
}
});
},
loadData(item, callback) {
item.loading = true;
loadDepartment(item.value).then(res => {
item.loading = false;
if (res.success) {
res.result.forEach(function(e) {
if (e.isParent) {
e.value = e.id;
e.label = e.title;
e.loading = false;
e.children = [];
} else {
e.value = e.id;
e.label = e.title;
}
if (e.status == -1) {
e.label = "[已禁用] " + e.label;
e.disabled = true;
}
});
item.children = res.result;
callback();
}
});
},
handleChangeDep(value, selectedData) {
let departmentId = "";
// 获取最后一个值
if (value && value.length > 0) {
departmentId = value[value.length - 1];
}
this.$emit("on-change", departmentId);
},
clearSelect() {
this.selectDep = [];
}
},
created() {
this.initDepartmentData();
}
};
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,184 @@
<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"
:load-data="loadData"
@on-select-change="selectTree"
></Tree>
<Spin size="large" fix v-if="depLoading"></Spin>
</div>
</div>
</Poptip>
</div>
</div>
</template>
<script>
import {initDepartment, loadDepartment, 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: "",
searchKey: "",
dataDep: [],
selectDep: [],
departmentId: []
};
},
methods: {
initDepartmentData() {
initDepartment().then(res => {
if (res.success) {
res.result.forEach(function (e) {
if (e.isParent) {
e.loading = false;
e.children = [];
}
if (e.status == -1) {
e.title = "[已禁用] " + e.title;
e.disabled = true;
}
});
this.dataDep = res.result;
}
});
},
loadData(item, callback) {
loadDepartment(item.id).then(res => {
if (res.success) {
res.result.forEach(function (e) {
if (e.isParent) {
e.loading = false;
e.children = [];
}
if (e.status == -1) {
e.title = "[已禁用] " + e.title;
e.disabled = true;
}
});
callback(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);
}
// this.$emit("on-change", this.departmentId);
}
},
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>

View File

@@ -0,0 +1,240 @@
<template>
<div>
<div style="position:relative">
<div :id="id" style="text-align: left;width:850px"></div>
<div v-if="showExpand">
<div class="e-menu e-code" @click="editHTML">
<Icon type="md-code-working" size="22" />
</div>
<div class="e-menu e-preview" @click="fullscreenModal=true">
<Icon type="ios-eye" size="24" />
</div>
<div class="e-menu e-trash" @click="clear">
<Icon type="md-trash" size="18" />
</div>
</div>
</div>
<Modal
title="编辑html代码"
v-model="showHTMLModal"
:mask-closable="false"
:width="900"
:fullscreen="full"
>
<Input
v-if="!full"
v-model="dataEdit"
:rows="15"
type="textarea"
style="max-height:60vh;overflow:auto;"
/>
<Input v-if="full" v-model="dataEdit" :rows="32" type="textarea" />
<div slot="footer">
<Button @click="full=!full" icon="md-expand">全屏开/</Button>
<Button @click="editHTMLOk" type="primary" icon="md-checkmark-circle-outline">确定保存</Button>
</div>
</Modal>
<Modal title="预览" v-model="fullscreenModal" fullscreen>
<div v-html="data">{{data}}</div>
<div slot="footer">
<Button @click="fullscreenModal=false">关闭</Button>
</div>
</Modal>
</div>
</template>
<script>
import { uploadFile } from "@/api/index";
import E from "wangeditor";
import xss from "xss";
// 表情包配置 自定义表情可在该js文件中统一修改
import { sina } from "@/libs/emoji";
var editor = null;
export default {
name: "editor",
props: {
id: {
type: String,
default: "editor"
},
value: String,
base64: {
type: Boolean,
default: false
},
showExpand: {
type: Boolean,
default: true
},
openXss: {
type: Boolean,
default: false
}
},
data() {
return {
editor: null,
data: this.value, // 富文本数据
dataEdit: "", // 编辑数据
showHTMLModal: false, // 显示html
full: false, // html全屏开关
fullscreenModal: false // 显示全屏预览
};
},
methods: {
initEditor() {
let that = this;
// 详见wangeditor3官网文档 https://www.kancloud.cn/wangfupeng/wangeditor3/332599
editor = new E(`#${this.id}`);
// 编辑内容绑定数据
editor.config.onchange = html => {
if (this.openXss) {
this.data = xss(html);
} else {
this.data = html;
}
this.$emit("input", this.data);
this.$emit("on-change", this.data);
};
editor.config.showFullScreen = false;
// z-index
editor.config.zIndex = 100;
if (this.base64) {
// 使用 base64 保存图片
editor.config.uploadImgShowBase64 = true;
} else {
// 配置上传图片服务器端地址
editor.config.uploadImgServer = uploadFile;
// lili如要header中传入token鉴权
editor.config.uploadImgHeaders = {
accessToken: that.getStore("accessToken")
};
editor.config.uploadFileName = "file";
editor.config.uploadImgHooks = {
before: function(xhr, editor, files) {
// 图片上传之前触发
},
success: function(xhr, editor, result) {
// 图片上传并返回结果,图片插入成功之后触发
},
fail: function(xhr, editor, result) {
// 图片上传并返回结果,但图片插入错误时触发
that.$Message.error("上传图片失败");
},
error: function(xhr, editor) {
// 图片上传出错时触发
that.$Message.error("上传图片出错");
},
timeout: function(xhr, editor) {
// 图片上传超时时触发
that.$Message.error("上传图片超时");
},
// 如果服务器端返回的不是 {errno:0, data: [...]} 这种格式,可使用该配置
customInsert: function(insertImg, result, editor) {
if (result.success == true) {
let url = result.result;
insertImg(url);
that.$Message.success("上传图片成功");
} else {
that.$Message.error(result.message);
}
}
};
}
editor.config.customAlert = function(info) {
// info 是需要提示的内容
// that.$Message.info(info);
};
// 字体
editor.config.fontNames = ["微软雅黑", "宋体", "黑体", "Arial"];
// 表情面板可以有多个 tab ,因此要配置成一个数组。数组每个元素代表一个 tab 的配置
editor.config.emotions = [
{
// tab 的标题
title: "新浪",
// type -> 'emoji' / 'image'
type: "image",
// content -> 数组
content: sina
}
];
editor.create();
if (this.value) {
if (this.openXss) {
editor.txt.html(xss(this.value));
} else {
editor.txt.html(this.value);
}
}
},
editHTML() {
this.dataEdit = this.data;
this.showHTMLModal = true;
},
editHTMLOk() {
editor.txt.html(this.dataEdit);
this.$emit("input", this.data);
this.$emit("on-change", this.data);
this.showHTMLModal = false;
},
clear() {
this.$Modal.confirm({
title: "确认清空",
content: "确认要清空编辑器内容?清空后不能撤回",
onOk: () => {
this.data = "";
editor.txt.html(this.data);
this.$emit("input", this.data);
this.$emit("on-change", this.data);
}
});
},
setData(value) {
if (!editor) {
this.initEditor();
}
if (value && value != this.data) {
this.data = value;
editor.txt.html(this.data);
this.$emit("input", this.data);
this.$emit("on-change", this.data);
}
}
},
watch: {
value(val) {
this.setData(val);
}
},
mounted() {
this.initEditor();
}
};
</script>
<style lang="scss" scoped>
.e-menu {
z-index: 101;
position: absolute;
cursor: pointer;
color: #999;
:hover {
color: #333;
}
}
.e-code {
top: 6px;
left: 818px;
}
.e-preview {
top: 46px;
left: 174px;
}
.e-trash {
top: 46px;
left: 215px;
}
</style>

View File

@@ -0,0 +1,221 @@
<template>
<div>
<div style="display:flex">
<Input
v-model="currentValue"
@on-change="handleChange"
:placeholder="placeholder"
:size="size"
:disabled="disabled"
:readonly="readonly"
:maxlength="maxlength"
:icon="currentValue"
/>
<Button
@click="iconModalVisible=true"
:size="size"
:disabled="disabled"
:icon="icon"
style="margin-left:10px"
>选择图标</Button>
</div>
<Modal
title="选择图标"
v-model="iconModalVisible"
:width="950"
:styles="{top: '30px'}"
footer-hide
:z-index="1060"
>
<div class="icon-search">
<input
type="text"
v-model="key"
:placeholder="tip"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
>
</div>
<div class="icon-block icon-bar">
<div class="icon-wrap" v-for="(item, i) in iconData" :key="i" @click="hanleChoose(item)">
<div class="icons-item">
<Icon :type="item" style="font-size: 32px;"/>
<p>{{item}}</p>
</div>
</div>
</div>
</Modal>
</div>
</template>
<script>
import { icons } from "@/libs/icon";
export default {
name: "iconChoose",
props: {
value: {
type: String,
default: ""
},
size: String,
placeholder: {
type: String,
default: "输入图标名或选择图标"
},
disabled: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
},
maxlength: Number,
icon: {
type: String,
default: "md-ionic"
}
},
data() {
return {
iconModalVisible: false,
currentValue: this.value,
iconData: [],
key: "",
tip: "输入英文关键词搜索,比如 success"
};
},
methods: {
init() {
let re = [];
icons.forEach(e => {
e.icons.forEach(item => {
re.push(item);
});
});
this.iconData = re;
},
handleInput() {
if (this.key) {
// 搜索
let re = [];
icons.forEach(e => {
e.tags.forEach(item => {
if (item.indexOf(this.key) >= 0) {
e.icons.forEach(r => {
re.push(r);
});
}
});
});
this.iconData = re;
} else {
this.init();
}
},
handleFocus() {
if (!this.key) {
this.tip = "";
}
},
handleBlur() {
if (!this.key) {
this.tip = "输入英文关键词搜索,比如 success";
}
},
handleChange(v) {
this.$emit("input", this.currentValue);
this.$emit("on-change", this.currentValue);
},
setCurrentValue(value) {
if (value === this.currentValue) {
return;
}
this.currentValue = value;
},
hanleChoose(v) {
this.currentValue = v;
this.$emit("input", this.currentValue);
this.$emit("on-change", this.currentValue);
this.iconModalVisible = false;
}
},
watch: {
value(val) {
this.setCurrentValue(val);
}
},
created() {
this.init();
}
};
</script>
<style lang="scss" scoped>
.icon-search {
position: relative;
margin: 20px auto 30px;
text-align: center;
input {
width: 500px;
box-sizing: border-box;
border: 0;
border-radius: 4px;
background: #f5f5f5;
text-align: center;
font-size: 14px;
outline: none;
margin: 0 auto;
padding: 8px 0;
}
}
.icon-block {
display: flex;
flex-wrap: wrap;
max-height: 500px;
overflow: auto;
}
.icon-bar {
overflow: auto;
overflow-x: hidden;
}
.icon-bar::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.icon-bar::-webkit-scrollbar-thumb {
border-radius: 3px;
background: #c3c3c3;
}
.icon-bar::-webkit-scrollbar-track {
background: #fff;
}
.icon-wrap {
:hover {
color: #1890ff;
transition: color 0.3s;
}
}
.icons-item {
margin: 6px 6px 6px 0;
width: 145px;
text-align: center;
list-style: none;
cursor: pointer;
height: 100px;
color: #5c6b77;
transition: all 0.2s ease;
position: relative;
padding-top: 10px;
p {
padding-top: 15px;
margin: 5px;
font-size: 14px;
}
}
</style>

View File

@@ -0,0 +1,346 @@
<template>
<div class="member-choose">
<Button @click="userModalVisible=true" :icon="icon">{{text}}</Button>
<span @click="clearSelectData" class="clear">清空已选</span>
<Collapse simple class="collapse">
<Panel name="1">
已选择
<span class="select-count">{{selectUsers.length}}</span>
<p slot="content">
<Tag
v-for="(item, i) in selectUsers"
:key="i"
:name="item.id"
color="default"
closable
@on-close="handleCancelUser"
>{{item.username}}
</Tag>
</p>
</Panel>
</Collapse>
<Drawer title="选择用户" closable v-model="userModalVisible" width="800" draggable>
<Form ref="searchUserForm" :model="searchUserForm" inline :label-width="55">
<Form-item label="用户名" prop="username">
<Input
type="text"
v-model="searchUserForm.username"
clearable
placeholder="请输入用户名"
style="width: 200px"
/>
</Form-item>
<Form-item style="margin-left:-35px;" class="br">
<Button @click="handleSearchUser" type="primary" icon="ios-search">搜索</Button>
<Button @click="handleResetUser">重置</Button>
</Form-item>
</Form>
<Table
:loading="userLoading"
border
:columns="userColumns"
:data="userData"
:height="height"
ref="userTable"
></Table>
<Row type="flex" justify="end" style="margin: 10px 0;">
<Page
:current="searchUserForm.pageNumber"
:total="totalUser"
:page-size="searchUserForm.pageSize"
@on-change="changeUserPage"
@on-page-size-change="changeUserPageSize"
:page-size-opts="[10,20,50]"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
<div class="my-drawer-footer">
已选择
<span class="select-count">{{selectUsers.length}}</span>
<Button @click="clearSelectData" style="margin-left:10px">清空已选</Button>
<Button @click="userModalVisible=false" type="primary" style="margin-left:10px">关闭</Button>
</div>
</Drawer>
</div>
</template>
<script>
import {getMember} from "@/api/member";
export default {
name: "userChoose",
props: {
text: {
type: String,
default: "选择会员"
},
icon: {
type: String,
default: "md-person-add"
}
},
data() {
return {
height: 500,
userLoading: true,
userModalVisible: false,
selectUsers: [],
searchUserForm: {
username: "",
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc" // 默认排序方式
},
userColumns: [
{
type: "index",
width: 60,
align: "center"
},
{
title: "用户名",
key: "username",
minWidth: 140,
sortable: true
},
{
title: "头像",
key: "face",
width: 80,
align: "center",
render: (h, params) => {
return h("Avatar", {
props: {
src: params.row.face
}
});
}
},
{
title: "手机",
key: "mobile",
width: 125,
sortable: true
},
{
title: "状态",
key: "status",
align: "center",
width: 120,
render: (h, params) => {
if (params.row.delFlag == 0) {
return h("div", [
h("Badge", {
props: {
status: "success",
text: "正常启用"
}
})
]);
} else if (params.row.delFlag == -1) {
return h("div", [
h("Badge", {
props: {
status: "error",
text: "禁用"
}
})
]);
}
}
},
{
title: "创建时间",
key: "createTime",
sortable: true,
sortType: "desc",
width: 170
},
{
title: "操作",
key: "action",
width: 130,
align: "center",
fixed: "right",
render: (h, params) => {
let select;
this.selectUsers.forEach(item => {
if (item.id === params.row.id) {
select = params.row
}
});
if (select) {
return h("div", [
h(
"Button",
{
props: {
type: "info",
size: "small"
},
on: {
click: () => {
this.chooseCancel(params.row);
}
}
},
"取消选择"
)
]);
} else {
return h("div", [
h(
"Button",
{
props: {
type: "info",
size: "small"
},
on: {
click: () => {
this.chooseUser(params.row);
}
}
},
"选择"
)
]);
}
}
}
],
userData: [],
totalUser: 0
};
},
methods: {
changeUserPage(v) {
this.searchUserForm.pageNumber = v;
this.getUserDataList();
},
changeUserPageSize(v) {
this.searchUserForm.pageSize = v;
this.getUserDataList();
},
getUserDataList() {
this.userLoading = true;
getMember(this.searchUserForm).then(res => {
this.userLoading = false;
if (res.success) {
this.userData = res.result.records;
this.totalUser = res.result.total;
}
});
},
handleSearchUser() {
this.searchUserForm.pageNumber = 1;
this.searchUserForm.pageSize = 10;
this.getUserDataList();
},
handleResetUser() {
this.$refs.searchUserForm.resetFields();
this.searchUserForm.pageNumber = 1;
this.searchUserForm.pageSize = 9;
this.$refs.dep.clearSelect();
this.searchUserForm.departmentId = "";
// 重新加载数据
this.getUserDataList();
},
setData(v) {
this.selectUsers = v;
this.$emit("on-change", this.selectUsers);
},
chooseCancel(v){
let _index;
this.selectUsers.forEach((e,index) => {
if (v.id == e.id) {
_index = index;
}
});
if(_index||_index==0){
this.selectUsers.splice(_index,1);
this.$emit("on-change", this.selectUsers);
}
},
chooseUser(v) {
// 去重
let that = this;
let flag = true;
this.selectUsers.forEach(e => {
if (v.id == e.id) {
flag = false;
}
});
if (flag) {
let u = {
id: v.id,
username: v.username
};
this.selectUsers.push(u);
this.$emit("on-change", this.selectUsers);
}
},
clearSelectData() {
this.selectUsers = [];
this.$emit("on-change", this.selectUsers);
},
handleCancelUser(e, id) {
// 删除所选用户
let newArray = [];
this.selectUsers.forEach(e => {
if (id != e.id) {
newArray.push(e);
}
});
this.selectUsers = newArray;
this.$emit("on-change", this.selectUsers);
this.$Message.success("删除所选用户成功");
}
},
created() {
// 计算高度
this.height = Number(document.documentElement.clientHeight - 230);
this.getUserDataList();
}
};
</script>
<style lang="scss" scoped>
.member-choose {
.clear {
font-size: 12px;
margin-left: 15px;
color: #40a9ff;
cursor: pointer;
}
.collapse {
font-size: 12px;
margin-top: 15px;
width: 500px;
}
.select-count {
font-weight: 600;
color: #40a9ff;
}
}
.my-drawer-footer {
z-index: 10;
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>

View File

@@ -0,0 +1,284 @@
<template>
<div>
<div id="toolbar">
<button class="ql-bold" title="粗体"></button>
<button class="ql-italic" title="斜体"></button>
<button class="ql-underline" title="下划线"></button>
<button class="ql-strike" title="删除线"></button>
<select class="ql-size" title="字体大小">
<option value="small"></option>
<option selected></option>
<option value="large"></option>
<option value="huge"></option>
</select>
<select class="ql-header" title="标题大小">
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
<option value="6"></option>
<option selected></option>
</select>
<select class="ql-font" title="字体"></select>
<select class="ql-align" title="对齐方式"></select>
<select class="ql-color" title="字体颜色"></select>
<select class="ql-background" title="背景颜色"></select>
<button class="ql-blockquote" title="引用"></button>
<button class="ql-code-block" title="代码块"></button>
<button class="ql-list" value="ordered" title="数字列表"></button>
<button class="ql-list" value="bullet" title="点列表"></button>
<button class="ql-script" value="sub" title="右下标"></button>
<button class="ql-script" value="super" title="右上标"></button>
<button class="ql-indent" value="-1" title="向左缩进"></button>
<button class="ql-indent" value="+1" title="向右缩进"></button>
<button class="ql-clean" title="清空样式"></button>
<button class="ql-link" title="链接"></button>
<button class="ql-image" title="插入图片" v-if="base64"></button>
<div class="q-menu" title="插入图片" v-if="!base64">
<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="5120"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize"
:before-upload="beforeUpload"
:show-upload-list="false"
ref="qup"
>
<Icon type="md-images" size="20" />
</Upload>
</div>
<button class="ql-video" title="插入视频链接"></button>
<div class="q-menu" title="编辑HTML代码" @click="editHTML" v-if="expandHtml">
<Icon type="md-code-working" size="22" />
</div>
<div class="q-menu" title="预览" @click="fullscreenModal=true" v-if="expandPreview">
<Icon type="ios-eye" size="24" />
</div>
<div class="q-menu q-trash" title="清空" @click="clear" v-if="expandClear">
<Icon type="md-trash" size="18" style="display: block;" />
</div>
</div>
<div :id="id" :style="{minHeight: minHeight}"></div>
<Modal
title="编辑html代码"
v-model="showHTMLModal"
:mask-closable="false"
:width="900"
:fullscreen="full"
>
<Input
v-if="!full"
v-model="dataEdit"
:rows="15"
type="textarea"
style="max-height:60vh;overflow:auto;"
/>
<Input v-if="full" v-model="dataEdit" :rows="32" type="textarea" />
<div slot="footer">
<Button @click="full=!full" icon="md-expand">全屏开/</Button>
<Button @click="editHTMLOk" type="primary" icon="md-checkmark-circle-outline">确定保存</Button>
</div>
</Modal>
<Modal title="预览" v-model="fullscreenModal" fullscreen>
<div v-html="data">{{data}}</div>
<div slot="footer">
<Button @click="fullscreenModal=false">关闭</Button>
</div>
</Modal>
</div>
</template>
<script>
import { uploadFile } from "@/api/index";
import Quill from "quill";
import "quill/dist/quill.snow.css";
import xss from "xss";
var editor = null;
export default {
name: "editor",
props: {
id: {
type: String,
default: "quill"
},
value: String,
base64: {
type: Boolean,
default: false
},
minHeight: {
type: String,
default: "300px"
},
expandHtml: {
type: Boolean,
default: true
},
expandPreview: {
type: Boolean,
default: true
},
expandClear: {
type: Boolean,
default: true
},
openXss: {
type: Boolean,
default: false
}
},
data() {
return {
accessToken: {},
uploadFileUrl: uploadFile,
editor: null,
options: {
theme: "snow",
modules: {
toolbar: "#toolbar"
},
placeholder: "在这输入内容 ..."
},
data: this.value, // 富文本数据
dataEdit: "", // 编辑数据
showHTMLModal: false, // 显示html
full: false, // html全屏开关
fullscreenModal: false // 显示全屏预览
};
},
methods: {
initEditor() {
this.accessToken = {
accessToken: this.getStore("accessToken")
};
editor = new Quill(`#${this.id}`, this.options);
let that = this;
if (this.value) {
editor.pasteHTML(this.value);
}
editor.on("text-change", function(delta, oldDelta, source) {
let html = editor.container.firstChild.innerHTML;
if (that.openXss) {
that.data = xss(html);
} else {
that.data = html;
}
that.$emit("input", that.data);
that.$emit("on-change", that.data);
});
},
handleFormatError(file) {
this.$Notice.warning({
title: "不支持的文件格式",
desc:
"所选文件‘ " +
file.name +
" ’格式不正确, 请选择 .jpg .jpeg .png .gif .bmp格式文件"
});
},
handleMaxSize(file) {
this.$Notice.warning({
title: "文件大小过大",
desc: "所选文件‘ " + file.name + " ’大小过大, 不得超过 5M."
});
},
beforeUpload() {
return true;
},
handleSuccess(res, file) {
if (res.success) {
let url = res.result;
// 获取光标位置
let range = editor.getSelection(true);
// 总元素
let delta = editor.getContents().length;
let index;
if (range) {
index = range.index;
} else {
index = delta;
}
// 插入元素
editor.insertEmbed(index, "image", url);
editor.setSelection(index + 1, 0);
} else {
this.$Message.error(res.message);
}
},
handleError(error, file, fileList) {
this.$Message.error(error.toString());
},
editHTML() {
this.dataEdit = this.data;
this.showHTMLModal = true;
},
editHTMLOk() {
editor.pasteHTML(this.dataEdit);
this.$emit("input", this.data);
this.$emit("on-change", this.data);
this.showHTMLModal = false;
},
clear() {
this.$Modal.confirm({
title: "确认清空",
content: "确认要清空编辑器内容?清空后不能撤回",
onOk: () => {
this.data = "";
editor.pasteHTML(this.data);
this.$emit("input", this.data);
this.$emit("on-change", this.data);
}
});
},
setData(value) {
if (!editor) {
this.initEditor();
}
if (value && value != this.data) {
this.data = value;
let index = editor.selection.savedRange.index;
editor.pasteHTML(this.data);
editor.setSelection(index, 0);
this.$emit("input", this.data);
this.$emit("on-change", this.data);
}
}
},
watch: {
value(val) {
this.setData(val);
}
},
mounted() {
this.initEditor();
}
};
</script>
<style lang="scss" scoped>
.q-menu {
margin: 0 3px;
display: inline-block;
cursor: pointer;
color: #444;
:hover {
color: #06c;
}
}
.q-trash {
margin-bottom: 3px;
}
.ql-tooltip {
left: 30% !important;
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<div class="spinner">
<div class="rect1"></div>
<div class="rect2"></div>
<div class="rect3"></div>
<div class="rect4"></div>
<div class="rect5"></div>
</div>
</template>
<script>
export default {
name: "rectLoading"
};
</script>
<style lang="scss" scoped>
.spinner {
margin-top: 20vh;
margin-bottom: 30vh;
height: 60px;
text-align: center;
font-size: 10px;
span {
display: block;
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
margin-top: 1vh;
}
div {
margin-right: 4px;
background-color: #4e9ff5;
height: 100%;
width: 6px;
display: inline-block;
-webkit-animation: stretchdelay 1.2s infinite ease-in-out;
animation: stretchdelay 1.2s infinite ease-in-out;
}
.rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
.rect3 {
-webkit-animation-delay: -1s;
animation-delay: -1s;
}
.rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}
.rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
}
@-webkit-keyframes stretchdelay {
0%,
40%,
100% {
-webkit-transform: scaleY(0.4);
}
20% {
-webkit-transform: scaleY(1);
}
}
@keyframes stretchdelay {
0%,
40%,
100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
}
20% {
transform: scaleY(1);
-webkit-transform: scaleY(1);
}
}
</style>

View File

@@ -0,0 +1,166 @@
<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>

View File

@@ -0,0 +1,358 @@
<template>
<div class="sku-choose">
<Button @click="showDrawer=true" :icon="icon">{{text}}</Button>
<span @click="clearSelectData" class="clear">清空已选</span>
<Collapse simple class="collapse">
<Panel name="1">
已选择
<span class="select-count">{{selectObj.length}}</span>
<p slot="content">
<Tag
v-for="(item, i) in selectObj"
:key="i"
:name="item.id"
color="default"
closable
@on-close="handleCancelObj"
>{{createName(item)}}
</Tag>
</p>
</Panel>
</Collapse>
<Drawer title="选择SKU" closable v-model="showDrawer" :width="width" draggable>
<Form ref="searchForm" :model="searchForm" inline :label-width="55">
<Form-item label="名称" prop="goodsName">
<Input
type="text"
v-model="searchForm.goodsName"
clearable
placeholder="输入商品名称"
style="width: 200px"
/>
</Form-item>
<Form-item style="margin-left:-35px;" class="br">
<Button @click="handleSearchData" type="primary" icon="ios-search">搜索</Button>
<Button @click="handleResetObj">重置</Button>
</Form-item>
</Form>
<Table
:loading="showLoading"
border
:columns="tableColumns"
:data="tableData"
:height="height"
ref="tableData"
></Table>
<Row type="flex" justify="end" style="margin: 10px 0;">
<Page
:current="searchForm.pageNumber"
:total="tableTotal"
:page-size="searchForm.pageSize"
@on-change="changeDataPage"
@on-page-size-change="changeDataPageSize"
:page-size-opts="[10,20,50]"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
<div class="my-drawer-footer">
已选择
<span class="select-count">{{selectObj.length}}</span>
<Button @click="clearSelectData" style="margin-left:10px">清空已选</Button>
<Button @click="showDrawer=false" type="primary" style="margin-left:10px">关闭</Button>
</div>
</Drawer>
</div>
</template>
<script>
import {getSkuPage} from "@/api/goods";
export default {
name: "userChoose",
props: {
text: {
type: String,
default: "选择SKU"
},
icon: {
type: String,
default: "md-basket"
},
initData: {
type: Array,
default: []
},
createName: {
type: Function,
default: function (item) {
return item.goodsName
}
}
},
mounted(){
this.selectObj = this.initData
console.log(JSON.stringify(this.initData))
},
data() {
return {
//默认值后续计算
height: 500,
width: 500,
//加载状态
showLoading: true,
//展示抽屉
showDrawer: false,
selectObj: [],
searchForm: {
goodsName: "",
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc" // 默认排序方式
},
tableColumns: [
{
type: "index",
width: 60,
align: "center"
},
{
title: "商品名称",
key: "goodsName",
minWidth: 140
},
{
title: "规格",
key: "specName",
minWidth: 140,
},
{
title: "图片",
key: "thumbnail",
width: 80,
align: "center",
render: (h, params) => {
return h("Avatar", {
props: {
src: params.row.face
}
});
}
},
{
title: "状态",
key: "status",
align: "center",
width: 120,
render: (h, params) => {
if (params.row.delFlag == 0) {
return h("div", [
h("Badge", {
props: {
status: "success",
text: "正常"
}
})
]);
} else if (params.row.delFlag == -1) {
return h("div", [
h("Badge", {
props: {
status: "error",
text: "禁用"
}
})
]);
}
}
},
{
title: "创建时间",
key: "createTime",
sortable: true,
sortType: "desc",
width: 170
},
{
title: "操作",
key: "action",
width: 130,
align: "center",
fixed: "right",
render: (h, params) => {
let select;
this.selectObj.forEach(item => {
if (item.id === params.row.id) {
select = params.row
}
});
if (select) {
return h("div", [
h(
"Button",
{
props: {
type: "info",
size: "small"
},
on: {
click: () => {
this.chooseCancel(params.row);
}
}
},
"取消选择"
)
]);
} else {
return h("div", [
h(
"Button",
{
props: {
type: "info",
size: "small"
},
on: {
click: () => {
this.chooseObj(params.row);
}
}
},
"选择"
)
]);
}
}
}
],
tableData: [],
tableTotal: 0
};
},
methods: {
changeDataPage(v) {
this.searchForm.pageNumber = v;
this.searchData();
},
changeDataPageSize(v) {
this.searchForm.pageSize = v;
this.searchData();
},
searchData() {
this.showLoading = true;
getSkuPage(this.searchForm).then(res => {
this.showLoading = false;
if (res.success) {
this.tableData = res.result.records;
this.tableTotal = res.result.total;
}
});
},
handleSearchData() {
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 10;
this.searchData();
},
handleResetObj() {
this.$refs.searchForm.resetFields();
this.searchForm.pageNumber = 1;
this.searchForm.pageSize = 9;
this.searchForm.departmentId = "";
// 重新加载数据
this.searchData();
},
setData(v) {
this.selectObj = v;
this.$emit("on-change", this.selectObj);
},
chooseCancel(v) {
let _index;
this.selectObj.forEach((e, index) => {
if (v.id == e.id) {
_index = index;
}
});
if (_index || _index == 0) {
this.selectObj.splice(_index, 1);
this.$emit("on-change", this.selectObj);
}
},
chooseObj(v) {
// 去重
let that = this;
let flag = true;
this.selectObj.forEach(e => {
if (v.id == e.id) {
flag = false;
}
});
if (flag) {
this.selectObj.push(v);
this.$emit("on-change", this.selectObj);
}
},
clearSelectData() {
this.selectObj = [];
this.$emit("on-change", this.selectObj);
},
handleCancelObj(e, id) {
// 删除所选用户
let newArray = [];
this.selectObj.forEach(e => {
if (id != e.id) {
newArray.push(e);
}
});
this.selectObj = newArray;
this.$emit("on-change", this.selectObj);
}
},
created() {
// 计算高度
this.height = Number(document.documentElement.clientHeight - 230);
this.width = Number(document.documentElement.clientWidth/2)>900?900:Number(document.documentElement.clientWidth/2)
this.searchData();
}
};
</script>
<style lang="scss" scoped>
.sku-choose {
.clear {
font-size: 12px;
margin-left: 15px;
color: #40a9ff;
cursor: pointer;
}
.collapse {
font-size: 12px;
margin-top: 15px;
}
.select-count {
font-weight: 600;
color: #40a9ff;
}
}
.my-drawer-footer {
z-index: 10;
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>

View File

@@ -0,0 +1,172 @@
<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"
>
<Button slot="append" icon="md-eye"></Button>
</Input>
<Poptip transfer trigger="hover" title="图片预览" placement="right" width="350">
<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>
<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="maxSize*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" :icon="icon">上传图片</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: String,
placeholder: {
type: String,
default: "图片链接"
},
showInput: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
},
maxSize: {
type: Number,
default: 5
},
maxlength: Number,
icon: {
type: String,
default: "ios-cloud-upload-outline"
}
},
data() {
return {
accessToken: {},
currentValue: this.value,
loading: false,
viewImage: false,
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: "所选文件‘ " + file.name + " ’大小过大, 不得超过 " + this.maxSize + "M."
});
},
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;
margin-left: -32px;
margin-top: 3px;
padding: 7px;
cursor: pointer;
}
.upload {
display: inline-block;
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,288 @@
<template>
<div>
<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'">
<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="maxSize*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>
<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: Object
},
draggable: {
type: Boolean,
default: true
},
multiple: {
type: Boolean,
default: true
},
maxSize: {
type: Number,
default: 5
},
limit: {
type: Number,
default: 10
}
},
data() {
return {
accessToken: {},
uploadFileUrl: uploadFile,
uploadList: [],
viewImage: false,
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:
"所选文件‘ " +
file.name +
" ’大小过大, 不得超过 " +
this.maxSize +
"M."
});
},
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-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;
}
.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>

View File

@@ -0,0 +1,352 @@
<template>
<div class="user-choose">
<Button @click="userModalVisible=true" :icon="icon">{{text}}</Button>
<span @click="clearSelectData" class="clear">清空已选</span>
<Collapse simple class="collapse">
<Panel name="1">
已选择
<span class="select-count">{{selectUsers.length}}</span>
<p slot="content">
<Tag
v-for="(item, i) in selectUsers"
:key="i"
:name="item.id"
color="default"
closable
@on-close="handleCancelUser"
>{{item.username}}</Tag>
</p>
</Panel>
</Collapse>
<Drawer title="选择用户" closable v-model="userModalVisible" width="800" draggable>
<Form ref="searchUserForm" :model="searchUserForm" inline :label-width="55">
<Form-item label="用户名" prop="username">
<Input
type="text"
v-model="searchUserForm.username"
clearable
placeholder="请输入用户名"
style="width: 200px"
/>
</Form-item>
<Form-item label="部门" prop="department">
<department-choose @on-change="handleSelectDep" style="width: 200px" ref="dep"></department-choose>
</Form-item>
<Form-item style="margin-left:-35px;" class="br">
<Button @click="handleSearchUser" type="primary" icon="ios-search">搜索</Button>
<Button @click="handleResetUser">重置</Button>
</Form-item>
</Form>
<Table
:loading="userLoading"
border
:columns="userColumns"
:data="userData"
:height="height"
ref="userTable"
></Table>
<Row type="flex" justify="end" style="margin: 10px 0;">
<Page
:current="searchUserForm.pageNumber"
:total="totalUser"
:page-size="searchUserForm.pageSize"
@on-change="changeUserPage"
@on-page-size-change="changeUserPageSize"
:page-size-opts="[10,20,50]"
size="small"
show-total
show-elevator
show-sizer
></Page>
</Row>
<div class="my-drawer-footer">
已选择
<span class="select-count">{{selectUsers.length}}</span>
<Button @click="clearSelectData" style="margin-left:10px">清空已选</Button>
<Button @click="userModalVisible=false" type="primary" style="margin-left:10px">关闭</Button>
</div>
</Drawer>
</div>
</template>
<script>
import { getUserListData } from "@/api/index";
import departmentChoose from "./department-choose";
export default {
name: "userChoose",
components: {
departmentChoose
},
props: {
text: {
type: String,
default: "选择用户"
},
icon: {
type: String,
default: "md-person-add"
}
},
data() {
return {
//高度 下方已重新计算
height: 500,
//加载
userLoading: true,
//显示选择器
userModalVisible: false,
//选择的对象
selectUsers: [],
//搜索参数
searchUserForm: {
username: "",
type: "",
status: "",
pageNumber: 1, // 当前页数
pageSize: 10, // 页面大小
sort: "createTime", // 默认排序字段
order: "desc" // 默认排序方式
},
userColumns: [
{
type: "index",
width: 60,
align: "center"
},
{
title: "用户名",
key: "username",
minWidth: 140,
sortable: true
},
{
title: "头像",
key: "avatar",
width: 80,
align: "center",
render: (h, params) => {
return h("Avatar", {
props: {
src: params.row.avatar
}
});
}
},
{
title: "所属部门",
key: "departmentTitle",
width: 120
},
{
title: "手机",
key: "mobile",
width: 125,
sortable: true
},
{
title: "邮箱",
key: "email",
width: 180,
sortable: true
},
{
title: "性别",
key: "sex",
width: 70,
align: "center"
},
{
title: "用户类型",
key: "type",
align: "center",
width: 100,
render: (h, params) => {
let re = "";
if (params.row.type == 1) {
re = "管理员";
} else if (params.row.type == 0) {
re = "普通用户";
}
return h("div", re);
}
},
{
title: "状态",
key: "status",
align: "center",
width: 120,
render: (h, params) => {
if (params.row.status == 0) {
return h("div", [
h("Badge", {
props: {
status: "success",
text: "正常启用"
}
})
]);
} else if (params.row.status == -1) {
return h("div", [
h("Badge", {
props: {
status: "error",
text: "禁用"
}
})
]);
}
}
},
{
title: "创建时间",
key: "createTime",
sortable: true,
sortType: "desc",
width: 170
},
{
title: "操作",
key: "action",
width: 130,
align: "center",
fixed: "right",
render: (h, params) => {
return h("div", [
h(
"Button",
{
props: {
type: "info",
size: "small"
},
on: {
click: () => {
this.chooseUser(params.row);
}
}
},
"添加该用户"
)
]);
}
}
],
userData: [],
totalUser: 0
};
},
methods: {
handleSelectDep(v) {
this.searchUserForm.departmentId = v;
},
changeUserPage(v) {
this.searchUserForm.pageNumber = v;
this.getUserDataList();
},
changeUserPageSize(v) {
this.searchUserForm.pageSize = v;
this.getUserDataList();
},
getUserDataList() {
this.userLoading = true;
getUserListData(this.searchUserForm).then(res => {
this.userLoading = false;
if (res.success) {
this.userData = res.result.records;
this.totalUser = res.result.total;
}
});
},
handleSearchUser() {
this.searchUserForm.pageNumber = 1;
this.searchUserForm.pageSize = 9;
this.getUserDataList();
},
handleResetUser() {
this.$refs.searchUserForm.resetFields();
this.searchUserForm.pageNumber = 1;
this.searchUserForm.pageSize = 9;
this.$refs.dep.clearSelect();
this.searchUserForm.departmentId = "";
// 重新加载数据
this.getUserDataList();
},
setData(v) {
this.selectUsers = v;
this.$emit("on-change", this.selectUsers);
},
chooseUser(v) {
// 去重
let that = this;
let flag = true;
this.selectUsers.forEach(e => {
if (v.id == e.id) {
that.$Message.warning("已经添加过啦,请勿重复选择");
flag = false;
}
});
if (flag) {
let u = {
id: v.id,
username: v.username
};
this.selectUsers.push(u);
this.$emit("on-change", this.selectUsers);
this.$Message.success(`添加用户 ${v.username} 成功`);
}
},
clearSelectData() {
this.selectUsers = [];
this.$emit("on-change", this.selectUsers);
},
handleCancelUser(e, id) {
// 删除所选用户
let newArray = [];
this.selectUsers.forEach(e => {
if (id != e.id) {
newArray.push(e);
}
});
this.selectUsers = newArray;
this.$emit("on-change", this.selectUsers);
this.$Message.success("删除所选用户成功");
}
},
created() {
// 计算高度
this.height = Number(document.documentElement.clientHeight - 230);
this.getUserDataList();
}
};
</script>
<style lang="scss" scoped>
.user-choose {
.clear {
font-size: 12px;
margin-left: 15px;
color: #40a9ff;
cursor: pointer;
}
.collapse {
font-size: 12px;
margin-top: 15px;
width: 500px;
}
.select-count {
font-weight: 600;
color: #40a9ff;
}
}
.my-drawer-footer {
z-index: 10;
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>

View File

@@ -0,0 +1,184 @@
<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'
export default {
name:'map',
data() {
return {
showMap:false,
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 codeObj = {}
const params = {
cityCode: this.addrContent.regeocode.addressComponent.citycode,
townName: this.addrContent.regeocode.addressComponent.township
}
getRegion(params).then(res=>{
if(res.code == 200) {
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: "b440952723253aa9fe483e698057bf7d", // 申请好的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>

View 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;
}
}
}

View 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>

View 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;
}

View 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>

View 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>
);
},
};

View 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>
);
},
};

View 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>
);
}
};

View 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"; }

View 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="&#58931;" 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="&#58929;" 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="&#58930;" 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

View File

@@ -0,0 +1,6 @@
import mixins from './mixins';
import scrollBarWidth from './scrollBarWidth';
export {
mixins,
scrollBarWidth
}

View 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)
);
},
},
};

View File

@@ -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;
}