master
parent
020ddf0588
commit
d55d53528e
|
@ -1,174 +1,197 @@
|
|||
// 统一请求路径前缀在libs/axios.js中修改
|
||||
import { getRequest, postRequest, putRequest, deleteRequest } from '@/libs/axios';
|
||||
|
||||
|
||||
import {
|
||||
getRequest,
|
||||
postRequest,
|
||||
putRequest,
|
||||
deleteRequest
|
||||
} from "@/libs/axios";
|
||||
|
||||
// 获取限时抢购申请列表
|
||||
|
||||
export const getPromotionSeckill = (params) => {
|
||||
export const getPromotionSeckill = params => {
|
||||
return getRequest(`/promotion/seckill/apply`, params);
|
||||
};
|
||||
|
||||
return getRequest(`/promotion/seckill/apply`, params)
|
||||
}
|
||||
// 是否推荐直播间
|
||||
export const whetherStar = params => {
|
||||
return getRequest(`/broadcast/studio/id/${params.id}&recommend=${params.recommend}`);
|
||||
};
|
||||
|
||||
// 获取店铺直播间列表
|
||||
export const getLiveList = params => {
|
||||
return getRequest("/broadcast/studio", params);
|
||||
};
|
||||
|
||||
// 获取直播间详情
|
||||
export const getLiveInfo = studioId => {
|
||||
return getRequest(`/broadcast/studio/studioInfo/${studioId}`);
|
||||
};
|
||||
|
||||
// 获取当前进行中的促销活动商品
|
||||
|
||||
export const getPromotionGoods = (promotionId,params) => {
|
||||
return getRequest(`/promotion/${promotionId}/goods`, params)
|
||||
}
|
||||
export const getPromotionGoods = (promotionId, params) => {
|
||||
return getRequest(`/promotion/${promotionId}/goods`, params);
|
||||
};
|
||||
|
||||
// 获取当前进行中的促销活动
|
||||
export const getAllPromotion = (params) => {
|
||||
return getRequest('/promotion/current', params)
|
||||
}
|
||||
export const getAllPromotion = params => {
|
||||
return getRequest("/promotion/current", params);
|
||||
};
|
||||
|
||||
// 获取拼团数据
|
||||
export const getPintuanList = (params) => {
|
||||
return getRequest('/promotion/pintuan', params)
|
||||
}
|
||||
export const getPintuanList = params => {
|
||||
return getRequest("/promotion/pintuan", params);
|
||||
};
|
||||
|
||||
// 获取拼团详情
|
||||
export const getPintuanDetail = (id) => {
|
||||
return getRequest(`/promotion/pintuan/${id}`)
|
||||
}
|
||||
export const getPintuanDetail = id => {
|
||||
return getRequest(`/promotion/pintuan/${id}`);
|
||||
};
|
||||
|
||||
// 获取拼团商品数据
|
||||
export const getPintuanGoodsList = (params) => {
|
||||
return getRequest(`/promotion/pintuan/goods/${params.pintuanId}`,params)
|
||||
}
|
||||
export const getPintuanGoodsList = params => {
|
||||
return getRequest(`/promotion/pintuan/goods/${params.pintuanId}`, params);
|
||||
};
|
||||
|
||||
// 关闭拼团活动
|
||||
export const closePintuan = (pintuanId) => {
|
||||
return putRequest(`/promotion/pintuan/close/${pintuanId}`)
|
||||
}
|
||||
export const closePintuan = pintuanId => {
|
||||
return putRequest(`/promotion/pintuan/close/${pintuanId}`);
|
||||
};
|
||||
|
||||
// 保存平台优惠券
|
||||
export const savePlatformCoupon = (params) => {
|
||||
return postRequest('/promotion/coupon', params,{'Content-type': 'application/json'})
|
||||
}
|
||||
export const savePlatformCoupon = params => {
|
||||
return postRequest("/promotion/coupon", params, {
|
||||
"Content-type": "application/json"
|
||||
});
|
||||
};
|
||||
// 修改平台优惠券
|
||||
export const editPlatformCoupon = (params) => {
|
||||
return putRequest('/promotion/coupon', params,{'Content-type': 'application/json'})
|
||||
}
|
||||
export const editPlatformCoupon = params => {
|
||||
return putRequest("/promotion/coupon", params, {
|
||||
"Content-type": "application/json"
|
||||
});
|
||||
};
|
||||
|
||||
// 获取平台优惠券
|
||||
export const getPlatformCouponList = (params) => {
|
||||
return getRequest('/promotion/coupon', params)
|
||||
}
|
||||
export const getPlatformCouponList = params => {
|
||||
return getRequest("/promotion/coupon", params);
|
||||
};
|
||||
// 作废优惠券
|
||||
export const deletePlatformCoupon = (ids) => {
|
||||
return deleteRequest(`/promotion/coupon/${ids}`)
|
||||
}
|
||||
export const deletePlatformCoupon = ids => {
|
||||
return deleteRequest(`/promotion/coupon/${ids}`);
|
||||
};
|
||||
// 更新优惠券状态
|
||||
export const updatePlatformCouponStatus = ( params) => {
|
||||
return putRequest(`/promotion/coupon/status`, params)
|
||||
}
|
||||
export const updatePlatformCouponStatus = params => {
|
||||
return putRequest(`/promotion/coupon/status`, params);
|
||||
};
|
||||
// 获取单个优惠券
|
||||
export const getPlatformCoupon = (id) => {
|
||||
return getRequest(`/promotion/coupon/${id}`)
|
||||
}
|
||||
|
||||
export const getPlatformCoupon = id => {
|
||||
return getRequest(`/promotion/coupon/${id}`);
|
||||
};
|
||||
|
||||
// 获取优惠券领取详情
|
||||
export const getMemberReceiveCouponList = (id) => {
|
||||
return getRequest(`/promotion/coupon/member/${id}`)
|
||||
}
|
||||
export const getMemberReceiveCouponList = id => {
|
||||
return getRequest(`/promotion/coupon/member/${id}`);
|
||||
};
|
||||
// 作废会员优惠券
|
||||
export const deleteMemberReceiveCoupon = (id) => {
|
||||
return putRequest(`/promotion/coupon/member/cancellation/${id}`)
|
||||
}
|
||||
export const deleteMemberReceiveCoupon = id => {
|
||||
return putRequest(`/promotion/coupon/member/cancellation/${id}`);
|
||||
};
|
||||
|
||||
// 获取限时抢购数据
|
||||
export const getSeckillList = (params) => {
|
||||
return getRequest('/promotion/seckill', params)
|
||||
}
|
||||
export const getSeckillList = params => {
|
||||
return getRequest("/promotion/seckill", params);
|
||||
};
|
||||
|
||||
// 获取限时抢购审核列表
|
||||
export const seckillGoodsList = (params) => {
|
||||
return getRequest('/promotion/seckill/apply', params)
|
||||
}
|
||||
export const seckillGoodsList = params => {
|
||||
return getRequest("/promotion/seckill/apply", params);
|
||||
};
|
||||
|
||||
// 获取限时抢购详情数据
|
||||
export const seckillDetail = (id, params) => {
|
||||
return getRequest(`/promotion/seckill/${id}`, params)
|
||||
}
|
||||
return getRequest(`/promotion/seckill/${id}`, params);
|
||||
};
|
||||
|
||||
// 删除限时抢购
|
||||
export const delSeckill = (id) => {
|
||||
return deleteRequest(`/promotion/seckill/${id}`)
|
||||
}
|
||||
export const delSeckill = id => {
|
||||
return deleteRequest(`/promotion/seckill/${id}`);
|
||||
};
|
||||
|
||||
// 保存限时抢购
|
||||
export const saveSeckill = (params) => {
|
||||
return postRequest('/promotion/seckill', params)
|
||||
}
|
||||
export const saveSeckill = params => {
|
||||
return postRequest("/promotion/seckill", params);
|
||||
};
|
||||
|
||||
// 修改限时抢购
|
||||
export const updateSeckill = (params) => {
|
||||
return putRequest('/promotion/seckill', params)
|
||||
}
|
||||
export const updateSeckill = params => {
|
||||
return putRequest("/promotion/seckill", params);
|
||||
};
|
||||
|
||||
// 关闭限时抢购
|
||||
export const closeSeckill = (id) => {
|
||||
return putRequest(`/promotion/seckill/close/${id}`)
|
||||
}
|
||||
export const closeSeckill = id => {
|
||||
return putRequest(`/promotion/seckill/close/${id}`);
|
||||
};
|
||||
|
||||
// 审核限时抢购
|
||||
export const auditApplySeckill = (params) => {
|
||||
return putRequest(`/promotion/seckill/apply/audit/${params.ids}`, params)
|
||||
}
|
||||
export const auditApplySeckill = params => {
|
||||
return putRequest(`/promotion/seckill/apply/audit/${params.ids}`, params);
|
||||
};
|
||||
|
||||
// 满优惠列表
|
||||
export const getFullDiscountList = (params) => {
|
||||
return getRequest(`/promotion/fullDiscount`,params)
|
||||
}
|
||||
export const getFullDiscountList = params => {
|
||||
return getRequest(`/promotion/fullDiscount`, params);
|
||||
};
|
||||
// 满优惠列表
|
||||
export const getFullDiscountById = (id) => {
|
||||
return getRequest(`/promotion/fullDiscount/${id}`)
|
||||
}
|
||||
export const getFullDiscountById = id => {
|
||||
return getRequest(`/promotion/fullDiscount/${id}`);
|
||||
};
|
||||
|
||||
// 积分商品列表
|
||||
export const getPointsGoodsList = (params) => {
|
||||
return getRequest(`/promotion/pointsGoods`,params)
|
||||
}
|
||||
export const getPointsGoodsList = params => {
|
||||
return getRequest(`/promotion/pointsGoods`, params);
|
||||
};
|
||||
// 积分商品详情
|
||||
export const getPointsGoodsById = (id) => {
|
||||
return getRequest(`/promotion/pointsGoods/${id}`)
|
||||
}
|
||||
export const getPointsGoodsById = id => {
|
||||
return getRequest(`/promotion/pointsGoods/${id}`);
|
||||
};
|
||||
// 添加积分商品
|
||||
export const addPointsGoods = (params) => {
|
||||
return postRequest(`/promotion/pointsGoods`,params, {'Content-type': 'application/json'})
|
||||
}
|
||||
export const addPointsGoods = params => {
|
||||
return postRequest(`/promotion/pointsGoods`, params, {
|
||||
"Content-type": "application/json"
|
||||
});
|
||||
};
|
||||
// 修改积分商品
|
||||
export const updatePointsGoods = (params) => {
|
||||
return putRequest(`/promotion/pointsGoods`,params, {'Content-type': 'application/json'})
|
||||
}
|
||||
export const updatePointsGoods = params => {
|
||||
return putRequest(`/promotion/pointsGoods`, params, {
|
||||
"Content-type": "application/json"
|
||||
});
|
||||
};
|
||||
// 修改积分商品状态
|
||||
export const editPointsGoodsStatus = (id, params) => {
|
||||
return putRequest(`/promotion/pointsGoods/${id}`,params)
|
||||
}
|
||||
return putRequest(`/promotion/pointsGoods/${id}`, params);
|
||||
};
|
||||
// 删除积分商品
|
||||
export const deletePointsGoodsStatus = (id) => {
|
||||
return deleteRequest(`/promotion/pointsGoods/${id}`)
|
||||
}
|
||||
|
||||
export const deletePointsGoodsStatus = id => {
|
||||
return deleteRequest(`/promotion/pointsGoods/${id}`);
|
||||
};
|
||||
|
||||
// 积分商品分类列表
|
||||
export const getPointsGoodsCategoryList = (params) => {
|
||||
return getRequest(`/promotion/pointsGoodsCategory`,params)
|
||||
}
|
||||
export const getPointsGoodsCategoryList = params => {
|
||||
return getRequest(`/promotion/pointsGoodsCategory`, params);
|
||||
};
|
||||
// 积分商品分类详情
|
||||
export const getPointsGoodsCategoryById = (id) => {
|
||||
return getRequest(`/promotion/pointsGoodsCategory/${id}`)
|
||||
}
|
||||
export const getPointsGoodsCategoryById = id => {
|
||||
return getRequest(`/promotion/pointsGoodsCategory/${id}`);
|
||||
};
|
||||
// 添加积分商品分类
|
||||
export const addPointsGoodsCategory = (params) => {
|
||||
return postRequest(`/promotion/pointsGoodsCategory`, params)
|
||||
}
|
||||
export const addPointsGoodsCategory = params => {
|
||||
return postRequest(`/promotion/pointsGoodsCategory`, params);
|
||||
};
|
||||
// 更新积分商品分类
|
||||
export const updatePointsGoodsCategory = (params) => {
|
||||
return putRequest(`/promotion/pointsGoodsCategory`, params)
|
||||
}
|
||||
export const updatePointsGoodsCategory = params => {
|
||||
return putRequest(`/promotion/pointsGoodsCategory`, params);
|
||||
};
|
||||
// 删除积分商品分类
|
||||
export const deletePointsGoodsCategoryById = (id) => {
|
||||
return deleteRequest(`/promotion/pointsGoodsCategory/${id}`)
|
||||
}
|
||||
export const deletePointsGoodsCategoryById = id => {
|
||||
return deleteRequest(`/promotion/pointsGoodsCategory/${id}`);
|
||||
};
|
||||
|
|
|
@ -17,10 +17,14 @@ export default {
|
|||
* @description api请求基础路径
|
||||
*/
|
||||
api_dev: {
|
||||
common: "http://192.168.0.103:8890",
|
||||
buyer: "http://192.168.0.103:8888",
|
||||
seller: "http://192.168.0.103:8889",
|
||||
manager: "http://192.168.0.103:8887"
|
||||
// common: "https://common-api.pickmall.cn",
|
||||
// buyer: "https://buyer-api.pickmall.cn",
|
||||
// seller: "https://store-api.pickmall.cn",
|
||||
// manager: "https://admin-api.pickmall.cn"
|
||||
common: 'http://192.168.0.109:8890',
|
||||
buyer: 'http://192.168.0.109:8888',
|
||||
seller: 'http://192.168.0.109:8889',
|
||||
manager: 'http://192.168.0.109:8887'
|
||||
},
|
||||
api_prod: {
|
||||
common: "https://common-api.pickmall.cn",
|
||||
|
|
|
@ -296,6 +296,12 @@ export const otherRouter = {
|
|||
title: "短信签名",
|
||||
name: "add-sms-sign",
|
||||
component: () => import("@/views/sys/message/smsSign.vue")
|
||||
},
|
||||
{
|
||||
path: "liveDetail",
|
||||
title: "查看直播",
|
||||
name: "liveDetail",
|
||||
component: () => import("@/views/live/liveDetail.vue")
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -18,6 +18,72 @@ export function unitPrice(val, unit, location) {
|
|||
|
||||
|
||||
|
||||
|
||||
let timer, flag;
|
||||
/**
|
||||
* 节流原理:在一定时间内,只能触发一次
|
||||
*
|
||||
* @param {Function} func 要执行的回调函数
|
||||
* @param {Number} wait 延时的时间
|
||||
* @param {Boolean} immediate 是否立即执行
|
||||
* @return null
|
||||
*/
|
||||
export function throttle(func, wait = 500, immediate = true) {
|
||||
if (immediate) {
|
||||
if (!flag) {
|
||||
flag = true;
|
||||
// 如果是立即执行,则在wait毫秒内开始时执行
|
||||
typeof func === 'function' && func();
|
||||
timer = setTimeout(() => {
|
||||
flag = false;
|
||||
}, wait);
|
||||
}
|
||||
} else {
|
||||
if (!flag) {
|
||||
flag = true
|
||||
// 如果是非立即执行,则在wait毫秒内的结束处执行
|
||||
timer = setTimeout(() => {
|
||||
flag = false
|
||||
typeof func === 'function' && func();
|
||||
}, wait);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let timeout = null;
|
||||
|
||||
/**
|
||||
* 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
|
||||
*
|
||||
* @param {Function} func 要执行的回调函数
|
||||
* @param {Number} wait 延时的时间
|
||||
* @param {Boolean} immediate 是否立即执行
|
||||
* @return null
|
||||
*/
|
||||
export function debounce(func, wait = 500, immediate = false) {
|
||||
// 清除定时器
|
||||
if (timeout !== null) clearTimeout(timeout);
|
||||
// 立即执行,此类情况一般用不到
|
||||
if (immediate) {
|
||||
var callNow = !timeout;
|
||||
timeout = setTimeout(function() {
|
||||
timeout = null;
|
||||
}, wait);
|
||||
if (callNow) typeof func === 'function' && func();
|
||||
} else {
|
||||
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
|
||||
timeout = setTimeout(function() {
|
||||
typeof func === 'function' && func();
|
||||
}, wait);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 货币格式化
|
||||
* @param price
|
||||
|
@ -172,4 +238,4 @@ export function formatLinkType (item) {
|
|||
break;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,67 +10,77 @@
|
|||
<span slot="close">关闭</span>
|
||||
</i-switch>
|
||||
</FormItem>
|
||||
<FormItem label="分销关系绑定天数" prop="distributionDay">
|
||||
<InputNumber :min="1" style="width:100px;" v-model="form.distributionDay" @on-change="handleSubmit"></InputNumber>
|
||||
</FormItem>
|
||||
<FormItem label="分销结算天数" prop="cashDay">
|
||||
<InputNumber :min="1" style="width:100px;" v-model="form.cashDay" @on-change="handleSubmit "></InputNumber>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { setSetting, getSetting } from "@/api/index";
|
||||
|
||||
import {setSetting, getSetting} from "@/api/index";
|
||||
|
||||
export default {
|
||||
name: "distributionSetting",
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
loading: true, // 表单加载状态
|
||||
form: { // 添加或编辑表单对象初始化数据
|
||||
isOpen: ""
|
||||
},
|
||||
// 表单验证规则
|
||||
formValidate: {},
|
||||
submitLoading: false, // 添加或编辑提交状态
|
||||
selectList: [], // 多选数据
|
||||
selectCount: 0, // 多选计数
|
||||
data: [], // 表单数据
|
||||
total: 0 // 表单数据总数
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.getDataList();
|
||||
export default {
|
||||
name: "distributionSetting",
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
loading: true, // 表单加载状态
|
||||
form: {
|
||||
// 添加或编辑表单对象初始化数据
|
||||
isOpen: "",
|
||||
distributionDay: "", //分销关系绑定天数
|
||||
cashDay: "", //分销结算天数
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
// 带多条件搜索参数获取表单数据 请自行修改接口
|
||||
getSetting("DISTRIBUTION_SETTING").then(res => {
|
||||
this.loading = false;
|
||||
if (res.success) {
|
||||
this.form = res.result;
|
||||
}
|
||||
});
|
||||
// 表单验证规则
|
||||
formValidate: {},
|
||||
submitLoading: false, // 添加或编辑提交状态
|
||||
selectList: [], // 多选数据
|
||||
selectCount: 0, // 多选计数
|
||||
data: [], // 表单数据
|
||||
total: 0, // 表单数据总数
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.getDataList();
|
||||
},
|
||||
getDataList() {
|
||||
this.loading = true;
|
||||
// 带多条件搜索参数获取表单数据 请自行修改接口
|
||||
getSetting("DISTRIBUTION_SETTING").then((res) => {
|
||||
this.loading = false;
|
||||
},
|
||||
handleSubmit() {
|
||||
|
||||
this.$refs.form.validate(valid => {
|
||||
if (valid) {
|
||||
setSetting("DISTRIBUTION_SETTING", this.form).then(res => {
|
||||
if (res.success) {
|
||||
this.$Message.success("操作成功");
|
||||
this.getDataList();
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
if (res.success) {
|
||||
this.form = res.result;
|
||||
}
|
||||
});
|
||||
this.loading = false;
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
};
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
// 防抖处理一下
|
||||
this.$options.filters.debounce(this.submit(), 1500);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
submit() {
|
||||
setSetting("DISTRIBUTION_SETTING", this.form).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("操作成功");
|
||||
this.getDataList();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
<template>
|
||||
<div>
|
||||
<Card>
|
||||
|
||||
<Tabs v-model="searchForm.status">
|
||||
<!-- 标签栏 -->
|
||||
<TabPane v-for="(item,index) in tabs" :key="index" :name="item.status" :label="item.title">
|
||||
|
||||
</TabPane>
|
||||
|
||||
</Tabs>
|
||||
<Table :columns="liveColumns" :data="liveData"></Table>
|
||||
<Row type="flex" style="margin:20px;" justify="end" class="page">
|
||||
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePageNumber" @on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]"
|
||||
size="small" show-total show-elevator show-sizer></Page>
|
||||
</Row>
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getLiveList, whetherStar } from "@/api/promotion.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 查询数据的总数
|
||||
total: "",
|
||||
// 查询的form
|
||||
searchForm: {
|
||||
pageSize: 10,
|
||||
pageNumber: 1,
|
||||
status: "NEW",
|
||||
},
|
||||
// 直播tab选项栏
|
||||
tabs: [
|
||||
{
|
||||
title: "直播中",
|
||||
status: "START",
|
||||
},
|
||||
{
|
||||
title: "未开始",
|
||||
status: "NEW",
|
||||
},
|
||||
{
|
||||
title: "已结束",
|
||||
status: "END",
|
||||
},
|
||||
],
|
||||
liveColumns: [
|
||||
{
|
||||
title: "直播标题",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "主播昵称",
|
||||
key: "anchorName",
|
||||
},
|
||||
{
|
||||
title: "直播开始时间",
|
||||
key: "createTime",
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"span",
|
||||
this.$options.filters.unixToDate(params.row.startTime)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "直播结束时间",
|
||||
key: "endTime",
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"span",
|
||||
|
||||
this.$options.filters.unixToDate(params.row.endTime)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "是否推荐",
|
||||
align: "center",
|
||||
render: (h, params) => {
|
||||
return h("div", [
|
||||
h(
|
||||
"i-switch",
|
||||
{
|
||||
// 数据库0 enabled,1 disabled
|
||||
props: {
|
||||
type: "primary",
|
||||
size: "large",
|
||||
value: params.row.recommend == true,
|
||||
},
|
||||
on: {
|
||||
"on-change": () => {
|
||||
this.star(params.row,params.index);
|
||||
},
|
||||
},
|
||||
},
|
||||
[
|
||||
h("span", {
|
||||
slot: "open",
|
||||
domProps: {
|
||||
innerHTML: "是",
|
||||
},
|
||||
}),
|
||||
h("span", {
|
||||
slot: "close",
|
||||
domProps: {
|
||||
innerHTML: "否",
|
||||
},
|
||||
}),
|
||||
]
|
||||
),
|
||||
]);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "直播状态",
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"span",
|
||||
params.row.status == "NEW"
|
||||
? "未开始"
|
||||
: params.row.status == "START"
|
||||
? "直播中"
|
||||
: "已结束"
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
style: {
|
||||
display: "flex",
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
"Button",
|
||||
{
|
||||
props: {
|
||||
type: "error",
|
||||
size: "small",
|
||||
},
|
||||
style: {
|
||||
marginRight: "5px",
|
||||
},
|
||||
on: {
|
||||
click: () => {
|
||||
this.getLiveDetail(params.row);
|
||||
},
|
||||
},
|
||||
},
|
||||
"查看"
|
||||
),
|
||||
]
|
||||
);
|
||||
},
|
||||
},
|
||||
], //table中显示的title
|
||||
liveData: [], //table中显示的直播数据
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
"searchForm.status": {
|
||||
handler() {
|
||||
this.liveData = [];
|
||||
this.getStoreLives();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getStoreLives();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 是否推荐
|
||||
*/
|
||||
async star(val,index) {
|
||||
let switched
|
||||
if(this.liveData[index].recommend){
|
||||
this.$set(this.liveData[index],'recommend',false)
|
||||
switched = false
|
||||
}
|
||||
else{
|
||||
|
||||
this.$set(this.liveData[index],'recommend',true)
|
||||
switched = true
|
||||
}
|
||||
|
||||
await whetherStar({id:val.id,recommend:switched});
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面数据大小分页回调
|
||||
*/
|
||||
changePageSize(val) {
|
||||
this.searchForm.pageSize = val;
|
||||
this.getStoreLives();
|
||||
},
|
||||
/**
|
||||
* 分页回调
|
||||
*/
|
||||
changePageNumber(val) {
|
||||
this.searchForm.pageNumber = val;
|
||||
this.getStoreLives();
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取店铺直播间列表
|
||||
*/
|
||||
async getStoreLives() {
|
||||
let result = await getLiveList(this.searchForm);
|
||||
if (result.success) {
|
||||
this.liveData = result.result.records;
|
||||
this.total = result.result.total;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取直播间详情
|
||||
*/
|
||||
getLiveDetail(val) {
|
||||
this.$router.push({
|
||||
path: "/liveDetail",
|
||||
query: { ...val, liveStatus: this.searchForm.status },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/table-common.scss";
|
||||
.btns {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.page {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,300 @@
|
|||
<template>
|
||||
<div>
|
||||
<Card>
|
||||
|
||||
<Form :model="liveForm" ref="liveForm" :rules="liveRulesForm" :label-width="120">
|
||||
<FormItem label="直播标题" prop="name">
|
||||
<Input disabled v-model="liveForm.name" style="width:460px"></Input>
|
||||
<div class="tips">直播间名字,最短3个汉字,最长17个汉字,1个汉字相当于2个字符</div>
|
||||
</FormItem>
|
||||
<FormItem label="主播昵称" prop="anchorName">
|
||||
<Input disabled v-model="liveForm.anchorName" style="width:360px"></Input>
|
||||
<div class="tips">主播昵称,最短2个汉字,最长15个汉字,1个汉字相当于2个字符</div>
|
||||
</FormItem>
|
||||
<FormItem label="直播时间" prop="startTime">
|
||||
|
||||
<DatePicker disabled format="yyyy-MM-dd HH:mm" type="datetimerange" v-model="times" @on-change="handleChangeTime" :options="optionsTime" placeholder="直播计划开始时间-直播计划结束时间" style="width: 300px">
|
||||
</DatePicker>
|
||||
<div class="tips">直播开播时间需要在当前时间的10分钟后 并且 开始时间不能在 6 个月后</div>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="主播微信号" prop="anchorWechat">
|
||||
<Input disabled v-model="liveForm.anchorWechat" style="width:360px" placeholder="主播微信号"></Input>
|
||||
<div class="tips">主播微信号,如果未实名认证,需要先前往“小程序直播”小程序进行<a target="_black" href="https://res.wx.qq.com/op_res/9rSix1dhHfK4rR049JL0PHJ7TpOvkuZ3mE0z7Ou_Etvjf-w1J_jVX0rZqeStLfwh">实名验证</a></div>
|
||||
</FormItem>
|
||||
|
||||
<!-- 分享卡片 -->
|
||||
<FormItem label="分享卡片封面" prop="feedsImg">
|
||||
<div class="upload-list" v-if="liveForm.feedsImg">
|
||||
<template>
|
||||
<img :src="liveForm.feedsImg">
|
||||
<div class="upload-list-cover">
|
||||
<Icon type="ios-eye-outline" @click.native="handleView(liveForm.feedsImg)"></Icon>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
<Upload v-if="liveForm.feedsImg.length ==0" ref="upload" :show-upload-list="false" :on-success="handleFeedsImgSuccess" :default-file-list="defaultImgList" :format="['jpg','jpeg','png']"
|
||||
:on-format-error="handleFormatError" :max-size="1024" :on-exceeded-size="handleMaxSize" type="drag" :action="action" :headers="accessToken" style="display: inline-block;width:58px;">
|
||||
<div style="width: 58px;height:58px;line-height: 58px;">
|
||||
<Icon type="ios-camera" size="20"></Icon>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="tips">
|
||||
直播间分享图,图片规则:建议像素800*640,大小不超过1M;
|
||||
</div>
|
||||
</FormItem>
|
||||
|
||||
<!-- 直播间背景墙 -->
|
||||
<FormItem label="直播间背景墙" prop="coverImg">
|
||||
|
||||
<div class="upload-list" v-if="liveForm.coverImg">
|
||||
<template>
|
||||
<img :src="liveForm.coverImg">
|
||||
<div class="upload-list-cover">
|
||||
<Icon type="ios-eye-outline" @click.native="handleView(liveForm.coverImg)"></Icon>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<Upload v-if="liveForm.coverImg.length ==0" ref="upload" :show-upload-list="false" :on-success="handleCoverImgSuccess" :default-file-list="defaultImgList" :format="['jpg','jpeg','png']"
|
||||
:on-format-error="handleFormatError" :max-size="2048" :on-exceeded-size="handleMaxSize" type="drag" :action="action" :headers="accessToken" style="display: inline-block;width:58px;">
|
||||
<div style="width: 58px;height:58px;line-height: 58px;">
|
||||
<Icon type="ios-camera" size="20"></Icon>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="tips"> 直播间背景图,图片规则:建议像素1080*1920,大小不超过2M</div>
|
||||
</FormItem>
|
||||
|
||||
<!-- 直播间背景墙 -->
|
||||
<FormItem label="直播间分享图" prop="shareImg">
|
||||
|
||||
<div class="upload-list" v-if="liveForm.shareImg">
|
||||
<template>
|
||||
<img :src="liveForm.shareImg">
|
||||
<div class="upload-list-cover">
|
||||
<Icon type="ios-eye-outline" @click.native="handleView(liveForm.shareImg)"></Icon>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<Upload v-if="liveForm.shareImg.length ==0" ref="upload" :show-upload-list="false" :on-success="handleShareImgSuccess" :default-file-list="defaultImgList" :format="['jpg','jpeg','png']"
|
||||
:on-format-error="handleFormatError" :max-size="2048" :on-exceeded-size="handleMaxSize" type="drag" :action="action" :headers="accessToken" style="display: inline-block;width:58px;">
|
||||
<div style="width: 58px;height:58px;line-height: 58px;">
|
||||
<Icon type="ios-camera" size="20"></Icon>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="tips"> 直播间分享图,图片规则:建议像素800*640,大小不超过1M</div>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="商品" v-if="$route.query.id">
|
||||
<Table class="goods-table" :columns="liveColumns" :data="liveData">
|
||||
<template slot-scope="{ row,index }" slot="goodsName">
|
||||
<div class="flex-goods">
|
||||
<Badge v-if="index == 0 || index ==1" color="volcano"></Badge>
|
||||
<img class="thumbnail" :src="row.thumbnail || row.goodsImage">
|
||||
{{ row.goodsName || row.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template slot-scope="{ row }" class="price" slot="price">
|
||||
<div>
|
||||
<div v-if="row.priceType == 1">{{row.price | unitPrice('¥')}}</div>
|
||||
<div v-if="row.priceType == 2">{{row.price | unitPrice('¥')}}至{{row.price2 | unitPrice('¥')}}</div>
|
||||
<div v-if="row.priceType == 3">{{row.price | unitPrice('¥')}}<span class="original-price">{{row.price2 | unitPrice('¥')}}</span></div>
|
||||
</div>
|
||||
</template>
|
||||
<template slot-scope="{ row }" slot="quantity">
|
||||
<div>{{row.quantity}}</div>
|
||||
</template>
|
||||
</Table>
|
||||
<div class="tips">
|
||||
直播间商品中前两个商品将自动被选为封面,伴随直播间在直播列表中显示
|
||||
</div>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button type="primary" @click="createLives()">保存</Button>
|
||||
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Card>
|
||||
<!-- 浏览图片 -->
|
||||
<Modal title="查看图片" v-model="imageVisible">
|
||||
<img :src="imageSrc" v-if="imageVisible" style="width: 100%">
|
||||
</Modal>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getLiveInfo } from "@/api/promotion";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
imageVisible: false, //查看图片的dailog
|
||||
imageSrc: "", //查看图片的路径
|
||||
liveForm: {
|
||||
name: "", //直播标题
|
||||
anchorName: "", //主播昵称
|
||||
anchorWechat: "", //主播微信号
|
||||
feedsImg: "", //分享卡片封面
|
||||
coverImg: "", //直播间背景墙
|
||||
shareImg: "", //分享图
|
||||
startTime: "",
|
||||
},
|
||||
|
||||
times: [], //接收直播时间数据
|
||||
// 直播商品表格表头
|
||||
liveColumns: [
|
||||
{
|
||||
title: "商品",
|
||||
slot: "goodsName",
|
||||
},
|
||||
{
|
||||
title: "价格",
|
||||
slot: "price",
|
||||
},
|
||||
{
|
||||
title: "库存",
|
||||
slot: "quantity",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
slot: "action",
|
||||
width: 250,
|
||||
},
|
||||
],
|
||||
liveData: [], //直播商品集合
|
||||
commodityList: "", //商品集合
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
/**
|
||||
* 如果query.id有值说明是查看详情
|
||||
* liveStatus 可以判断当前直播状态 从而区分数据 是否是未开始、已开启、已关闭、
|
||||
*/
|
||||
if (this.$route.query.id) {
|
||||
// 获取直播间详情
|
||||
this.getLiveDetail();
|
||||
}
|
||||
this.accessToken = {
|
||||
accessToken: this.getStore("accessToken"),
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 上传图片查看图片
|
||||
*/
|
||||
handleView(src) {
|
||||
this.imageVisible = true;
|
||||
this.imageSrc = src;
|
||||
},
|
||||
/**
|
||||
* 获取直播间详情
|
||||
*/
|
||||
async getLiveDetail() {
|
||||
let result = await getLiveInfo(this.$route.query.id);
|
||||
|
||||
// 将数据回调到liveform里面
|
||||
if (result.success) {
|
||||
let data = result.result;
|
||||
for (let key in data) {
|
||||
this.liveForm[key] = data[key];
|
||||
}
|
||||
// 将选择的商品回调给表格
|
||||
|
||||
this.liveData = data.commodityList;
|
||||
this.commodityList = data.commodityList;
|
||||
|
||||
// 将时间格式化
|
||||
this.$set(
|
||||
this.times,
|
||||
[0],
|
||||
this.$options.filters.unixToDate(data.startTime, "yyyy-MM-dd hh:mm")
|
||||
);
|
||||
this.$set(
|
||||
this.times,
|
||||
[1],
|
||||
this.$options.filters.unixToDate(data.endTime, "yyyy-MM-dd hh:mm")
|
||||
);
|
||||
this.liveStatus = data.status;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.action {
|
||||
display: flex;
|
||||
/deep/ .ivu-btn {
|
||||
margin: 0 5px !important;
|
||||
}
|
||||
}
|
||||
.original-price {
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.thumbnail {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 0.4em;
|
||||
}
|
||||
.flex-goods {
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
> img {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
.goods-table {
|
||||
width: 1000px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.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: 4px;
|
||||
}
|
||||
.upload-list img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
</style>
|
|
@ -22,10 +22,17 @@
|
|||
<Option value="CANCELLED">已取消</Option>
|
||||
</Select>
|
||||
</Form-item>
|
||||
|
||||
<Form-item label="下单时间">
|
||||
<DatePicker v-model="selectDate" type="datetimerange" format="yyyy-MM-dd" clearable @on-change="selectDateRange" placeholder="选择起始时间" style="width: 200px"></DatePicker>
|
||||
</Form-item>
|
||||
<Button @click="handleSearch" type="primary" icon="ios-search" class="search-btn">搜索</Button>
|
||||
|
||||
<download-excel class="export-excel-wrapper" :data="data" :fields="fields" name="商品订单.xls">
|
||||
<Button type="primary" ghost class="search-btn">导出Excel</Button>
|
||||
</download-excel>
|
||||
|
||||
|
||||
</Form>
|
||||
</Row>
|
||||
<Row class="padding-row">
|
||||
|
@ -43,12 +50,60 @@
|
|||
|
||||
<script>
|
||||
import * as API_Order from "@/api/order";
|
||||
|
||||
import JsonExcel from "vue-json-excel";
|
||||
export default {
|
||||
name: "orderList",
|
||||
components: {},
|
||||
components: {
|
||||
"download-excel": JsonExcel,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
// 表格的表头以及内容
|
||||
fields:{
|
||||
"订单编号":"sn",
|
||||
"下单时间":"createTime",
|
||||
"客户名称":"memberName",
|
||||
"客户账号":"",
|
||||
"收货人":"",
|
||||
"收货人手机号":"",
|
||||
"收货人地址":"",
|
||||
"支付方式":{
|
||||
field: "clientType",
|
||||
callback:value=>{
|
||||
if (value == "H5") {
|
||||
return "移动端"
|
||||
} else if (value == "PC") {
|
||||
return "PC端"
|
||||
} else if (value== "WECHAT_MP") {
|
||||
return "小程序端"
|
||||
} else if (value == "APP") {
|
||||
return "移动应用端"
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
},
|
||||
"配送方式":"",
|
||||
"配送费用":"",
|
||||
"订单商品金额":"",
|
||||
"订单优惠金额":"",
|
||||
"订单应付金额":"",
|
||||
"商品SKU编号":"",
|
||||
"商品数量":"groupNum",
|
||||
"买家备注":"",
|
||||
"订单状态":"",
|
||||
"付款状态":{
|
||||
field:"payStatus",
|
||||
callback:value=>{
|
||||
return value == "UNPAID" ? "未付款" : value == "PAID" ? "已付款" : ""
|
||||
}
|
||||
},
|
||||
"发货状态":"",
|
||||
"发票类型":"",
|
||||
"发票抬头":"",
|
||||
"店铺":"storeName",
|
||||
},
|
||||
loading: true, // 表单加载状态
|
||||
searchForm: {
|
||||
// 搜索框初始化对象
|
||||
|
@ -95,16 +150,15 @@ export default {
|
|||
width: 95,
|
||||
render: (h, params) => {
|
||||
if (params.row.clientType == "H5") {
|
||||
return h("div",{},"移动端");
|
||||
}else if(params.row.clientType == "PC") {
|
||||
return h("div",{},"PC端");
|
||||
}else if(params.row.clientType == "WECHAT_MP") {
|
||||
return h("div",{},"小程序端");
|
||||
}else if(params.row.clientType == "APP") {
|
||||
return h("div",{},"移动应用端");
|
||||
}
|
||||
else{
|
||||
return h("div",{},params.row.clientType);
|
||||
return h("div", {}, "移动端");
|
||||
} else if (params.row.clientType == "PC") {
|
||||
return h("div", {}, "PC端");
|
||||
} else if (params.row.clientType == "WECHAT_MP") {
|
||||
return h("div", {}, "小程序端");
|
||||
} else if (params.row.clientType == "APP") {
|
||||
return h("div", {}, "移动应用端");
|
||||
} else {
|
||||
return h("div", {}, params.row.clientType);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
@click="edit(row)"
|
||||
>编辑</Button
|
||||
>
|
||||
|
||||
|
||||
<Button
|
||||
type="info"
|
||||
size="small"
|
||||
|
@ -80,7 +80,7 @@
|
|||
@click="manage(row)"
|
||||
>查看</Button
|
||||
>
|
||||
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
|
@ -89,7 +89,7 @@
|
|||
@click="manage(row)"
|
||||
>管理</Button
|
||||
>
|
||||
|
||||
|
||||
<!-- <Button type="success" size="small" class="mr_5" v-if="row.promotionStatus == 'NEW' || row.promotionStatus == 'END'" @click="upper(row)">上架</Button> -->
|
||||
<Button
|
||||
type="error"
|
||||
|
@ -313,4 +313,7 @@ export default {
|
|||
</script>
|
||||
<style lang="scss">
|
||||
@import "@/styles/table-common.scss";
|
||||
.mr_5{
|
||||
margin: 0 5px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -66,7 +66,8 @@ export default {
|
|||
uvs: 0, // 访客数
|
||||
pvs: 0, // 浏览量
|
||||
|
||||
dateList: [ // 选择项
|
||||
dateList: [
|
||||
// 选择项
|
||||
{
|
||||
title: "今天",
|
||||
selected: false,
|
||||
|
@ -90,7 +91,8 @@ export default {
|
|||
],
|
||||
|
||||
orderChart: "", // 初始化图表
|
||||
params: { // 请求参数
|
||||
params: {
|
||||
// 请求参数
|
||||
searchType: "LAST_SEVEN",
|
||||
year: "",
|
||||
month: "",
|
||||
|
@ -117,6 +119,8 @@ export default {
|
|||
watch: {
|
||||
params: {
|
||||
handler(val) {
|
||||
this.uvs = 0;
|
||||
this.pvs = 0;
|
||||
this.init();
|
||||
},
|
||||
deep: true,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 dandavis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,123 @@
|
|||
# download
|
||||
========
|
||||
|
||||
## Summary
|
||||
---------
|
||||
The download() function is used to trigger a file download from JavaScript.
|
||||
|
||||
It specifies the contents and name of a new file placed in the browser's download directory. The input can be a URL, String, Blob, or Typed Array of data, or via a dataURL representing the file's data as base64 or url-encoded string. No matter the input format, download() saves a file using the specified file name and mime information in the same manner as a server using a Content-Disposition HTTP header.
|
||||
|
||||
## Getting and Using
|
||||
---------
|
||||
|
||||
### Via NPM/Bower
|
||||
`npm install downloadjs` <br />
|
||||
`bower install downloadjs`
|
||||
|
||||
`require("downloadjs")(data, strFileName, strMimeType);`
|
||||
|
||||
### Simple global `download` function via `<script>` include
|
||||
download(data, strFileName, strMimeType);
|
||||
|
||||
### Included via AMD
|
||||
require(['path/to/file'], function(download) {
|
||||
download(data, strFileName, strMimeType);
|
||||
});
|
||||
|
||||
|
||||
### Parameters
|
||||
---------
|
||||
* **data** - The Blob, File, String, or dataURL containing the soon-to-be File's contents.
|
||||
* **strFileName** - The name of the file to be created. Note that older browsers (like FF3.5, Ch5) don't honor the file name you provide, instead they automatically name the downloaded file.
|
||||
* **strMimeType** - The MIME content-type of the file to download. While optional, it helps the browser present friendlier information about the download to the user, encouraging them to accept the download.
|
||||
|
||||
|
||||
|
||||
## Example Usage
|
||||
---------
|
||||
|
||||
|
||||
### Plain Text
|
||||
#### text string - [live demo](http://pagedemos.com/hw24em95rsfq/output/)
|
||||
download("hello world", "dlText.txt", "text/plain");
|
||||
|
||||
#### text dataURL - [live demo](http://pagedemos.com/r9ywm98s6b29/output/)
|
||||
download("data:text/plain,hello%20world", "dlDataUrlText.txt", "text/plain");
|
||||
|
||||
#### text blob - [live demo](http://pagedemos.com/ckcah2vp8kza/output/)
|
||||
download(new Blob(["hello world"]), "dlTextBlob.txt", "text/plain");
|
||||
|
||||
#### text url - [live demo](http://pagedemos.com/pz6hkyqutjtw/output/)
|
||||
download("/robots.txt");
|
||||
|
||||
|
||||
#### text UInt8 Array - [live demo](http://pagedemos.com/zuyk46wbkktq/output/)
|
||||
var str= "hello world", arr= new Uint8Array(str.length);
|
||||
str.split("").forEach(function(a,b){
|
||||
arr[b]=a.charCodeAt();
|
||||
});
|
||||
|
||||
download( arr, "textUInt8Array.txt", "text/plain" );
|
||||
|
||||
### HTML
|
||||
#### html string - [live demo](http://pagedemos.com/k7rwq7msu3eb/output/)
|
||||
download(document.documentElement.outerHTML, "dlHTML.html", "text/html");
|
||||
|
||||
#### html Blob - [live demo](http://pagedemos.com/bxehm2fdf3g4/output/)
|
||||
download(new Blob(["hello world".bold()]), "dlHtmlBlob.html", "text/html");
|
||||
|
||||
#### ajax callback - [live demo](http://pagedemos.com/arr2ym74aw8t/output/)
|
||||
(note that callback mode won't work on vanilla ajax or with binary files)
|
||||
|
||||
$.ajax({
|
||||
url: "/download.html",
|
||||
success: download.bind(true, "text/html", "dlAjaxCallback.html")
|
||||
});
|
||||
|
||||
|
||||
### Binary Files
|
||||
#### image from URL - [live demo](http://pagedemos.com/yvvmxbjrwq7u/output/)
|
||||
download("/diff6.png");
|
||||
|
||||
#### Image via ajax for custom filename - [live demo](http://pagedemos.com/v2848zfgwrju/output/)
|
||||
var x=new XMLHttpRequest();
|
||||
x.open( "GET", "/diff6.png" , true);
|
||||
x.responseType="blob";
|
||||
x.onload= function(e){download(e.target.response, "awesomesauce.png", "image/png");};
|
||||
x.send();
|
||||
|
||||
|
||||
## Compatibility
|
||||
---------
|
||||
download.js works with a wide range of devices and browsers.
|
||||
|
||||
You can expect it to work for the vast majority of your users, with some common-sense limits:
|
||||
|
||||
* Devices without file systems like iPhone, iPad, Wii, et al. have nowhere to save the file to, sorry.
|
||||
* Android support starts at 4.2 for the built-in browser, though chrome 36+ and firefox 20+ on android 2.3+ work well.
|
||||
* Devices without Blob support won't be able to download Blobs or TypedArrays
|
||||
* Legacy devices (no a[download]) support can only download a few hundred kilobytes of data, and can't give the file a custom name.
|
||||
* Devices without window.URL support can only download a couple megabytes of data
|
||||
* IE versions of 9 and before are NOT supported because the don't support a[download] or dataURL frame locations.
|
||||
|
||||
|
||||
## FAQ
|
||||
---------
|
||||
|
||||
* `Can I tell when a download is done/canceled?` No.
|
||||
* `How can I style the temporary download link?` Define CSS class styles for `.download-js-link`.
|
||||
* `What's up with Safari?` I don't know either but pull requests that improve the situation are welcome.
|
||||
* `Why is my binary file corrupted?` Likely: an incorrect MIME or using jQuery ajax, which has no bin support.
|
||||
* `How big of files work?` Depends, try yourself: [File Echo Demo](http://pagedemos.com/gqs6hbmjcpem/)... I do a 1GB dl routinely on a thinkpad...
|
||||
|
||||
|
||||
## Change Log (v4.1)
|
||||
---------
|
||||
* 2008 :: landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
|
||||
* 2012 :: added named files via a[download], msSaveBlob() for IE (10+) support, and window.URL support for larger+faster saves than dataURLs
|
||||
* 2014 :: added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support
|
||||
* 2015 :: converted to amd/commonJS module with browser-friendly fallback
|
||||
* 2015 :: 4.1 added direct URL downloading via a single URL argument.
|
||||
* 2016 :: 4.2 added large dataURL support, a more semantic codebase, and hidden temp links
|
||||
* 2017 :: added support for empty dataURLs
|
||||
* 20XX :: ???? Considering Zip, Tar, and other multi-file outputs, Blob.prototype.download option, and more, stay tuned folks.
|
|
@ -0,0 +1,167 @@
|
|||
//download.js v4.2, by dandavis; 2008-2016. [MIT] see http://danml.com/download.html for tests/usage
|
||||
// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
|
||||
// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
|
||||
// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.
|
||||
// v4 adds AMD/UMD, commonJS, and plain browser support
|
||||
// v4.1 adds url download capability via solo URL argument (same domain/CORS only)
|
||||
// v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
|
||||
// https://github.com/rndme/download
|
||||
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define([], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like environments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory();
|
||||
} else {
|
||||
// Browser globals (root is window)
|
||||
root.download = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
|
||||
return function download(data, strFileName, strMimeType) {
|
||||
|
||||
var self = window, // this script is only for browsers anyway...
|
||||
defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads
|
||||
mimeType = strMimeType || defaultMime,
|
||||
payload = data,
|
||||
url = !strFileName && !strMimeType && payload,
|
||||
anchor = document.createElement("a"),
|
||||
toString = function(a){return String(a);},
|
||||
myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString),
|
||||
fileName = strFileName || "download",
|
||||
blob,
|
||||
reader;
|
||||
myBlob= myBlob.call ? myBlob.bind(self) : Blob ;
|
||||
|
||||
if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
|
||||
payload=[payload, mimeType];
|
||||
mimeType=payload[0];
|
||||
payload=payload[1];
|
||||
}
|
||||
|
||||
|
||||
if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument
|
||||
fileName = url.split("/").pop().split("?")[0];
|
||||
anchor.href = url; // assign href prop to temp anchor
|
||||
if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path:
|
||||
var ajax=new XMLHttpRequest();
|
||||
ajax.open( "GET", url, true);
|
||||
ajax.responseType = 'blob';
|
||||
ajax.onload= function(e){
|
||||
download(e.target.response, fileName, defaultMime);
|
||||
};
|
||||
setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return:
|
||||
return ajax;
|
||||
} // end if valid url?
|
||||
} // end if url?
|
||||
|
||||
|
||||
//go ahead and download dataURLs right away
|
||||
if(/^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(payload)){
|
||||
|
||||
if(payload.length > (1024*1024*1.999) && myBlob !== toString ){
|
||||
payload=dataUrlToBlob(payload);
|
||||
mimeType=payload.type || defaultMime;
|
||||
}else{
|
||||
return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
|
||||
navigator.msSaveBlob(dataUrlToBlob(payload), fileName) :
|
||||
saver(payload) ; // everyone else can save dataURLs un-processed
|
||||
}
|
||||
|
||||
}else{//not data url, is it a string with special needs?
|
||||
if(/([\x80-\xff])/.test(payload)){
|
||||
var i=0, tempUiArr= new Uint8Array(payload.length), mx=tempUiArr.length;
|
||||
for(i;i<mx;++i) tempUiArr[i]= payload.charCodeAt(i);
|
||||
payload=new myBlob([tempUiArr], {type: mimeType});
|
||||
}
|
||||
}
|
||||
blob = payload instanceof myBlob ?
|
||||
payload :
|
||||
new myBlob([payload], {type: mimeType}) ;
|
||||
|
||||
|
||||
function dataUrlToBlob(strUrl) {
|
||||
var parts= strUrl.split(/[:;,]/),
|
||||
type= parts[1],
|
||||
decoder= parts[2] == "base64" ? atob : decodeURIComponent,
|
||||
binData= decoder( parts.pop() ),
|
||||
mx= binData.length,
|
||||
i= 0,
|
||||
uiArr= new Uint8Array(mx);
|
||||
|
||||
for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
|
||||
|
||||
return new myBlob([uiArr], {type: type});
|
||||
}
|
||||
|
||||
function saver(url, winMode){
|
||||
|
||||
if ('download' in anchor) { //html5 A[download]
|
||||
anchor.href = url;
|
||||
anchor.setAttribute("download", fileName);
|
||||
anchor.className = "download-js-link";
|
||||
anchor.innerHTML = "downloading...";
|
||||
anchor.style.display = "none";
|
||||
document.body.appendChild(anchor);
|
||||
setTimeout(function() {
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
|
||||
}, 66);
|
||||
return true;
|
||||
}
|
||||
|
||||
// handle non-a[download] safari as best we can:
|
||||
if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
|
||||
if(/^data:/.test(url)) url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
if(!window.open(url)){ // popup blocked, offer direct download:
|
||||
if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//do iframe dataURL download (old ch+FF):
|
||||
var f = document.createElement("iframe");
|
||||
document.body.appendChild(f);
|
||||
|
||||
if(!winMode && /^data:/.test(url)){ // force a mime that will download:
|
||||
url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
}
|
||||
f.src=url;
|
||||
setTimeout(function(){ document.body.removeChild(f); }, 333);
|
||||
|
||||
}//end saver
|
||||
|
||||
|
||||
|
||||
|
||||
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
|
||||
return navigator.msSaveBlob(blob, fileName);
|
||||
}
|
||||
|
||||
if(self.URL){ // simple fast and modern way using Blob and URL:
|
||||
saver(self.URL.createObjectURL(blob), true);
|
||||
}else{
|
||||
// handle non-Blob()+non-URL browsers:
|
||||
if(typeof blob === "string" || blob.constructor===toString ){
|
||||
try{
|
||||
return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
|
||||
}catch(y){
|
||||
return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
|
||||
}
|
||||
}
|
||||
|
||||
// Blob but not URL support:
|
||||
reader=new FileReader();
|
||||
reader.onload=function(e){
|
||||
saver(this.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
return true;
|
||||
}; /* end download() */
|
||||
}));
|
|
@ -0,0 +1,2 @@
|
|||
//download.js v4.2, by dandavis; 2008-2017. [MIT] see http://danml.com/download.html for tests/usage
|
||||
;(function(r,l){"function"==typeof define&&define.amd?define([],l):"object"==typeof exports?module.exports=l():r.download=l()})(this,function(){return function l(a,e,k){function q(a){var h=a.split(/[:;,]/);a=h[1];var h=("base64"==h[2]?atob:decodeURIComponent)(h.pop()),d=h.length,b=0,c=new Uint8Array(d);for(b;b<d;++b)c[b]=h.charCodeAt(b);return new f([c],{type:a})}function m(a,b){if("download"in d)return d.href=a,d.setAttribute("download",n),d.className="download-js-link",d.innerHTML="downloading...",d.style.display="none",document.body.appendChild(d),setTimeout(function(){d.click(),document.body.removeChild(d),!0===b&&setTimeout(function(){g.URL.revokeObjectURL(d.href)},250)},66),!0;if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent))return/^data:/.test(a)&&(a="data:"+a.replace(/^data:([\w\/\-\+]+)/,"application/octet-stream")),!window.open(a)&&confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")&&(location.href=a),!0;var c=document.createElement("iframe");document.body.appendChild(c),!b&&/^data:/.test(a)&&(a="data:"+a.replace(/^data:([\w\/\-\+]+)/,"application/octet-stream")),c.src=a,setTimeout(function(){document.body.removeChild(c)},333)}var g=window,b=k||"application/octet-stream",c=!e&&!k&&a,d=document.createElement("a");k=function(a){return String(a)};var f=g.Blob||g.MozBlob||g.WebKitBlob||k,n=e||"download",f=f.call?f.bind(g):Blob;"true"===String(this)&&(a=[a,b],b=a[0],a=a[1]);if(c&&2048>c.length&&(n=c.split("/").pop().split("?")[0],d.href=c,-1!==d.href.indexOf(c))){var p=new XMLHttpRequest;return p.open("GET",c,!0),p.responseType="blob",p.onload=function(a){l(a.target.response,n,"application/octet-stream")},setTimeout(function(){p.send()},0),p}if(/^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(a)){if(!(2096103.424<a.length&&f!==k))return navigator.msSaveBlob?navigator.msSaveBlob(q(a),n):m(a);a=q(a),b=a.type||"application/octet-stream"}else if(/([\x80-\xff])/.test(a)){e=0;var c=new Uint8Array(a.length),t=c.length;for(e;e<t;++e)c[e]=a.charCodeAt(e);a=new f([c],{type:b})}a=a instanceof f?a:new f([a],{type:b});if(navigator.msSaveBlob)return navigator.msSaveBlob(a,n);if(g.URL)m(g.URL.createObjectURL(a),!0);else{if("string"==typeof a||a.constructor===k)try{return m("data:"+b+";base64,"+g.btoa(a))}catch(h){return m("data:"+b+","+encodeURIComponent(a))}b=new FileReader,b.onload=function(a){m(this.result)},b.readAsDataURL(a)}return!0}});
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "downloadjs",
|
||||
"main": "download.js",
|
||||
"version": "1.4.7",
|
||||
"description": "file downloading using client-side javascript",
|
||||
"keywords": [
|
||||
"files",
|
||||
"dataURL",
|
||||
"blob",
|
||||
"download"
|
||||
],
|
||||
"homepage": "http://danml.com/download.html",
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "dandavis",
|
||||
"email": "rndme@users.noreply.github.com",
|
||||
"url": "http://danml.com/"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rndme/download.git"
|
||||
},
|
||||
"bugs": {
|
||||
"email": "rndme@users.noreply.github.com"
|
||||
},
|
||||
"files": [
|
||||
"download.js",
|
||||
"download.min.js"
|
||||
],
|
||||
"npmName": "downloadjs",
|
||||
"npmFileMap": [
|
||||
{
|
||||
"basePath": "/",
|
||||
"files": [
|
||||
"*.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
"__npminstall_done": "Thu Jun 03 2021 11:07:53 GMT+0800 (中国标准时间)",
|
||||
"_from": "downloadjs@1.4.7",
|
||||
"_resolved": "https://registry.nlark.com/downloadjs/download/downloadjs-1.4.7.tgz"
|
||||
}
|
360
node_modules/_vue-json-excel@0.3.0@vue-json-excel/JsonExcel.vue
generated
vendored
Executable file
360
node_modules/_vue-json-excel@0.3.0@vue-json-excel/JsonExcel.vue
generated
vendored
Executable file
|
@ -0,0 +1,360 @@
|
|||
<template>
|
||||
<div :id="idName" @click="generate">
|
||||
<slot> Download {{ name }} </slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import download from "downloadjs";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// mime type [xls, csv]
|
||||
type: {
|
||||
type: String,
|
||||
default: "xls",
|
||||
},
|
||||
// Json to download
|
||||
data: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
// fields inside the Json Object that you want to export
|
||||
// if no given, all the properties in the Json are exported
|
||||
fields: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
// this prop is used to fix the problem with other components that use the
|
||||
// variable fields, like vee-validate. exportFields works exactly like fields
|
||||
exportFields: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
// Use as fallback when the row has no field values
|
||||
defaultValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
// Title(s) for the data, could be a string or an array of strings (multiple titles)
|
||||
header: {
|
||||
default: null,
|
||||
},
|
||||
// Footer(s) for the data, could be a string or an array of strings (multiple footers)
|
||||
footer: {
|
||||
default: null,
|
||||
},
|
||||
// filename to export
|
||||
name: {
|
||||
type: String,
|
||||
default: "data.xls",
|
||||
},
|
||||
fetch: {
|
||||
type: Function,
|
||||
},
|
||||
meta: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
worksheet: {
|
||||
type: String,
|
||||
default: "Sheet1",
|
||||
},
|
||||
//event before generate was called
|
||||
beforeGenerate: {
|
||||
type: Function,
|
||||
},
|
||||
//event before download pops up
|
||||
beforeFinish: {
|
||||
type: Function,
|
||||
},
|
||||
// Determine if CSV Data should be escaped
|
||||
escapeCsv: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// long number stringify
|
||||
stringifyLongNum: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// unique identifier
|
||||
idName() {
|
||||
var now = new Date().getTime();
|
||||
return "export_" + now;
|
||||
},
|
||||
|
||||
downloadFields() {
|
||||
if (this.fields) return this.fields;
|
||||
|
||||
if (this.exportFields) return this.exportFields;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async generate() {
|
||||
if (typeof this.beforeGenerate === "function") {
|
||||
await this.beforeGenerate();
|
||||
}
|
||||
let data = this.data;
|
||||
if (typeof this.fetch === "function" || !data) data = await this.fetch();
|
||||
|
||||
if (!data || !data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let json = this.getProcessedJson(data, this.downloadFields);
|
||||
if (this.type === "html") {
|
||||
// this is mainly for testing
|
||||
return this.export(
|
||||
this.jsonToXLS(json),
|
||||
this.name.replace(".xls", ".html"),
|
||||
"text/html"
|
||||
);
|
||||
} else if (this.type === "csv") {
|
||||
return this.export(
|
||||
this.jsonToCSV(json),
|
||||
this.name.replace(".xls", ".csv"),
|
||||
"application/csv"
|
||||
);
|
||||
}
|
||||
return this.export(
|
||||
this.jsonToXLS(json),
|
||||
this.name,
|
||||
"application/vnd.ms-excel"
|
||||
);
|
||||
},
|
||||
/*
|
||||
Use downloadjs to generate the download link
|
||||
*/
|
||||
export: async function (data, filename, mime) {
|
||||
let blob = this.base64ToBlob(data, mime);
|
||||
if (typeof this.beforeFinish === "function") await this.beforeFinish();
|
||||
download(blob, filename, mime);
|
||||
},
|
||||
/*
|
||||
jsonToXLS
|
||||
---------------
|
||||
Transform json data into an xml document with MS Excel format, sadly
|
||||
it shows a prompt when it opens, that is a default behavior for
|
||||
Microsoft office and cannot be avoided. It's recommended to use CSV format instead.
|
||||
*/
|
||||
jsonToXLS(data) {
|
||||
let xlsTemp =
|
||||
'<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>${worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--><style>br {mso-data-placement: same-cell;}</style></head><body><table>${table}</table></body></html>';
|
||||
let xlsData = "<thead>";
|
||||
const colspan = Object.keys(data[0]).length;
|
||||
let _self = this;
|
||||
|
||||
//Header
|
||||
const header = this.header || this.$attrs.title;
|
||||
if (header) {
|
||||
xlsData += this.parseExtraData(
|
||||
header,
|
||||
'<tr><th colspan="' + colspan + '">${data}</th></tr>'
|
||||
);
|
||||
}
|
||||
|
||||
//Fields
|
||||
xlsData += "<tr>";
|
||||
for (let key in data[0]) {
|
||||
xlsData += "<th>" + key + "</th>";
|
||||
}
|
||||
xlsData += "</tr>";
|
||||
xlsData += "</thead>";
|
||||
|
||||
//Data
|
||||
xlsData += "<tbody>";
|
||||
data.map(function (item, index) {
|
||||
xlsData += "<tr>";
|
||||
for (let key in item) {
|
||||
xlsData +=
|
||||
"<td>" +
|
||||
_self.preprocessLongNum(
|
||||
_self.valueReformattedForMultilines(item[key])
|
||||
) +
|
||||
"</td>";
|
||||
}
|
||||
xlsData += "</tr>";
|
||||
});
|
||||
xlsData += "</tbody>";
|
||||
|
||||
//Footer
|
||||
if (this.footer != null) {
|
||||
xlsData += "<tfoot>";
|
||||
xlsData += this.parseExtraData(
|
||||
this.footer,
|
||||
'<tr><td colspan="' + colspan + '">${data}</td></tr>'
|
||||
);
|
||||
xlsData += "</tfoot>";
|
||||
}
|
||||
|
||||
return xlsTemp
|
||||
.replace("${table}", xlsData)
|
||||
.replace("${worksheet}", this.worksheet);
|
||||
},
|
||||
/*
|
||||
jsonToCSV
|
||||
---------------
|
||||
Transform json data into an CSV file.
|
||||
*/
|
||||
jsonToCSV(data) {
|
||||
let _self = this;
|
||||
var csvData = [];
|
||||
|
||||
//Header
|
||||
const header = this.header || this.$attrs.title;
|
||||
if (header) {
|
||||
csvData.push(this.parseExtraData(header, "${data}\r\n"));
|
||||
}
|
||||
|
||||
//Fields
|
||||
for (let key in data[0]) {
|
||||
csvData.push(key);
|
||||
csvData.push(",");
|
||||
}
|
||||
csvData.pop();
|
||||
csvData.push("\r\n");
|
||||
//Data
|
||||
data.map(function (item) {
|
||||
for (let key in item) {
|
||||
let escapedCSV = item[key] + "";
|
||||
// Escaped CSV data to string to avoid problems with numbers or other types of values
|
||||
// this is controlled by the prop escapeCsv
|
||||
if (_self.escapeCsv) {
|
||||
escapedCSV = '="' + escapedCSV + '"'; // cast Numbers to string
|
||||
if (escapedCSV.match(/[,"\n]/)) {
|
||||
escapedCSV = '"' + escapedCSV.replace(/\"/g, '""') + '"';
|
||||
}
|
||||
}
|
||||
csvData.push(escapedCSV);
|
||||
csvData.push(",");
|
||||
}
|
||||
csvData.pop();
|
||||
csvData.push("\r\n");
|
||||
});
|
||||
//Footer
|
||||
if (this.footer != null) {
|
||||
csvData.push(this.parseExtraData(this.footer, "${data}\r\n"));
|
||||
}
|
||||
return csvData.join("");
|
||||
},
|
||||
/*
|
||||
getProcessedJson
|
||||
---------------
|
||||
Get only the data to export, if no fields are set return all the data
|
||||
*/
|
||||
getProcessedJson(data, header) {
|
||||
let keys = this.getKeys(data, header);
|
||||
let newData = [];
|
||||
let _self = this;
|
||||
data.map(function (item, index) {
|
||||
let newItem = {};
|
||||
for (let label in keys) {
|
||||
let property = keys[label];
|
||||
newItem[label] = _self.getValue(property, item);
|
||||
}
|
||||
newData.push(newItem);
|
||||
});
|
||||
|
||||
return newData;
|
||||
},
|
||||
getKeys(data, header) {
|
||||
if (header) {
|
||||
return header;
|
||||
}
|
||||
|
||||
let keys = {};
|
||||
for (let key in data[0]) {
|
||||
keys[key] = key;
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
/*
|
||||
parseExtraData
|
||||
---------------
|
||||
Parse title and footer attribute to the csv format
|
||||
*/
|
||||
parseExtraData(extraData, format) {
|
||||
let parseData = "";
|
||||
if (Array.isArray(extraData)) {
|
||||
for (var i = 0; i < extraData.length; i++) {
|
||||
if (extraData[i])
|
||||
parseData += format.replace("${data}", extraData[i]);
|
||||
}
|
||||
} else {
|
||||
parseData += format.replace("${data}", extraData);
|
||||
}
|
||||
return parseData;
|
||||
},
|
||||
|
||||
getValue(key, item) {
|
||||
const field = typeof key !== "object" ? key : key.field;
|
||||
let indexes = typeof field !== "string" ? [] : field.split(".");
|
||||
let value = this.defaultValue;
|
||||
|
||||
if (!field) value = item;
|
||||
else if (indexes.length > 1)
|
||||
value = this.getValueFromNestedItem(item, indexes);
|
||||
else value = this.parseValue(item[field]);
|
||||
|
||||
if (key.hasOwnProperty("callback"))
|
||||
value = this.getValueFromCallback(value, key.callback);
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
/*
|
||||
convert values with newline \n characters into <br/>
|
||||
*/
|
||||
valueReformattedForMultilines(value) {
|
||||
if (typeof value == "string") return value.replace(/\n/gi, "<br/>");
|
||||
else return value;
|
||||
},
|
||||
preprocessLongNum(value) {
|
||||
if (this.stringifyLongNum) {
|
||||
if (String(value).startsWith("0x")) {
|
||||
return value;
|
||||
}
|
||||
if (!isNaN(value) && value != "") {
|
||||
if (value > 99999999999 || value < 0.0000000000001) {
|
||||
return '="' + value + '"';
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getValueFromNestedItem(item, indexes) {
|
||||
let nestedItem = item;
|
||||
for (let index of indexes) {
|
||||
if (nestedItem) nestedItem = nestedItem[index];
|
||||
}
|
||||
return this.parseValue(nestedItem);
|
||||
},
|
||||
|
||||
getValueFromCallback(item, callback) {
|
||||
if (typeof callback !== "function") return this.defaultValue;
|
||||
const value = callback(item);
|
||||
return this.parseValue(value);
|
||||
},
|
||||
parseValue(value) {
|
||||
return value || value === 0 || typeof value === "boolean"
|
||||
? value
|
||||
: this.defaultValue;
|
||||
},
|
||||
base64ToBlob(data, mime) {
|
||||
let base64 = window.btoa(window.unescape(encodeURIComponent(data)));
|
||||
let bstr = atob(base64);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8ClampedArray(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
return new Blob([u8arr], { type: mime });
|
||||
},
|
||||
}, // end methods
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Pooya Parsa <pooya@pi0.ir>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,321 @@
|
|||
# JSON to Excel for VUE 2
|
||||
|
||||
Download your JSON data as an Excel file directly from the browser. This component is based on the solution proposed on [this thread](https://stackoverflow.com/questions/17142427/javascript-to-export-html-table-to-excel)
|
||||
|
||||
### Important! Extra prompt in Microsoft Excel
|
||||
|
||||
**The method implemented in this component uses HTML tables to draw the .xls files, Microsoft Excel no longer recognize HTML as native content so a warning message will be displayed before opening the file. The content will be rendered perfectly but the message can't be avoided.**
|
||||
|
||||
## Getting started
|
||||
|
||||
Get the package:
|
||||
|
||||
```bash
|
||||
npm install vue-json-excel
|
||||
```
|
||||
|
||||
Register JsonExcel in your vue app entry point:
|
||||
|
||||
```js
|
||||
import Vue from "vue";
|
||||
import JsonExcel from "vue-json-excel";
|
||||
|
||||
Vue.component("downloadExcel", JsonExcel);
|
||||
```
|
||||
|
||||
In your template
|
||||
|
||||
```html
|
||||
<download-excel :data="json_data">
|
||||
Download Data
|
||||
<img src="download_icon.png" />
|
||||
</download-excel>
|
||||
```
|
||||
|
||||
## Props List
|
||||
|
||||
| Name | Type | Description | Default |
|
||||
| :--------------------------- | :----------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: |
|
||||
| data | Array | Data to be exported. |
|
||||
| fields | Object | Fields inside the JSON Object that you want to export. If none provided, all properties in the JSON will be exported. |
|
||||
| export-fields (exportFields) | Object | Used to fix the problem with other components that use the variable fields, like vee-validate. exportFields works exactly like fields |
|
||||
| type | string | Mime type [xls, csv] | xls |
|
||||
| name | string | File name to export. | data.xls |
|
||||
| header | string/Array | Title(s) for the data. Can be a string (one title) or an array of strings (multiple titles). |
|
||||
| title(deprecated) | string/Array | same as header, title is maintained for retro-compatibility purposes but its use is not recommended due to the conflict with the HTML5 title attribute. |
|
||||
| footer | string/Array | Footer(s) for the data. Can be a string (one footer) or an array of strings (multiple footers). |
|
||||
| default-value (defaultValue) | string | Use as fallback when the row has no field values. | '' |
|
||||
| worksheet | string | Name of the worksheet tab. | 'Sheet1' |
|
||||
| fetch | Function | Callback to fetch data before download, if it's set it runs immediately after mouse pressed and before download process.<br>IMPORTANT: only works if no data prop is defined. |
|
||||
| before-generate | Function | Callback to call a method right before the generate / fetch data, eg:show loading progress |
|
||||
| before-finish | Function | Callback to call a method right before the download box pops out, eg:hide loading progress |
|
||||
| stringifyLongNum | Boolean | stringify long number and decimal(solve the problem of loss of digital accuracy), default: false |
|
||||
| escapeCsv | Boolean | This escapes CSV values in order to fix some excel problems with number fields. But this will wrap every csv data with **="** and **"**, to avoid that you have to set this prop to false. default: True |
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Vue from "vue";
|
||||
import JsonExcel from "vue-json-excel";
|
||||
|
||||
Vue.component("downloadExcel", JsonExcel);
|
||||
|
||||
const app = new Vue({
|
||||
el: "#app",
|
||||
data: {
|
||||
json_fields: {
|
||||
"Complete name": "name",
|
||||
City: "city",
|
||||
Telephone: "phone.mobile",
|
||||
"Telephone 2": {
|
||||
field: "phone.landline",
|
||||
callback: (value) => {
|
||||
return `Landline Phone - ${value}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
json_data: [
|
||||
{
|
||||
name: "Tony Peña",
|
||||
city: "New York",
|
||||
country: "United States",
|
||||
birthdate: "1978-03-15",
|
||||
phone: {
|
||||
mobile: "1-541-754-3010",
|
||||
landline: "(541) 754-3010",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Thessaloniki",
|
||||
city: "Athens",
|
||||
country: "Greece",
|
||||
birthdate: "1987-11-23",
|
||||
phone: {
|
||||
mobile: "+1 855 275 5071",
|
||||
landline: "(2741) 2621-244",
|
||||
},
|
||||
},
|
||||
],
|
||||
json_meta: [
|
||||
[
|
||||
{
|
||||
key: "charset",
|
||||
value: "utf-8",
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
In your HTML call it like
|
||||
|
||||
```html
|
||||
<download-excel
|
||||
class="btn btn-default"
|
||||
:data="json_data"
|
||||
:fields="json_fields"
|
||||
worksheet="My Worksheet"
|
||||
name="filename.xls"
|
||||
>
|
||||
Download Excel (you can customize this with html code!)
|
||||
</download-excel>
|
||||
```
|
||||
|
||||
REQUIRED
|
||||
|
||||
- json_data: Contains the data you want to export.
|
||||
- json_fields: You can select what fields to export. Specify nested data and assign labels to the fields. The key is the label, the value is the JSON field. This will export the field data 'as is'. If you need to customize the the exported data you can define a callback function. Thanks to @gucastiliao.
|
||||
|
||||
```js
|
||||
let json_fields = {
|
||||
// regular field (exported data 'as is')
|
||||
fieldLabel: attributeName, // nested attribute supported
|
||||
// callback function for data formatting
|
||||
anotherFieldLabel: {
|
||||
field: anotherAttributeName, // nested attribute supported
|
||||
callback: (value) => {
|
||||
return `formatted value ${value}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
json_fields is a object that represents which columns will be exported. If no object is provided, the component will be use the first object in your data array to extract the keys as columns names. Json field example:
|
||||
|
||||
```
|
||||
:export-fields="{
|
||||
'Human friendly name': '_name_field_from_json',
|
||||
'user's last name': '_last_name_text'
|
||||
}"
|
||||
```
|
||||
|
||||
## Export CSV
|
||||
|
||||
To export JSON as a CSV file, just add the prop `type` with a value of "csv":
|
||||
|
||||
```html
|
||||
<download-excel
|
||||
class="btn btn-default"
|
||||
:data="json_data"
|
||||
:fields="json_fields"
|
||||
type="csv"
|
||||
name="filename.xls"
|
||||
>
|
||||
Download CSV (you can customize this with html code!)
|
||||
</download-excel>
|
||||
```
|
||||
|
||||
## Multi-line values will appear in a single cell
|
||||
|
||||
A single text value in the data that contains newline characters will appear as a single cell in Excel. This avoids the undesired behavior of multi-line values getting split into multiple cells that must be merged before using data filters and pivot tables.
|
||||
|
||||
For example:
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div>
|
||||
<json-excel :data="dataForExcel" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import JsonExcel from "@/components/JsonExcel";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
JsonExcel,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
dataForExcel: [
|
||||
{ colA: "Hello", colB: "World" },
|
||||
{
|
||||
colA: "Multi-line",
|
||||
/* Multi-line value: */
|
||||
colB:
|
||||
"This is a long paragraph\nwith multiple lines\nthat should show in a single cell.",
|
||||
},
|
||||
{ colA: "Another", colB: "Regular cell" },
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
![Example of Excel showing multi-line cell](example-multi-line.png)
|
||||
|
||||
## Fetch Data on Demand
|
||||
|
||||
In case you need to fetch data from the server, you could use the fetch prop that allows you to define a callback function that is executed when your user click the download button. This function has to return a JSON value containing the data to export. A basic use case is:
|
||||
|
||||
```js
|
||||
<template>
|
||||
<div id="app">
|
||||
|
||||
<hr>
|
||||
<h2>Fetch Example</h2>
|
||||
<downloadexcel
|
||||
class = "btn"
|
||||
:fetch = "fetchData"
|
||||
:fields = "json_fields"
|
||||
:before-generate = "startDownload"
|
||||
:before-finish = "finishDownload">
|
||||
Download Excel
|
||||
</downloadexcel>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import downloadexcel from "vue-json-excel";
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
downloadexcel,
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
json_fields: {
|
||||
'Complete name': 'name',
|
||||
'Date': 'date',
|
||||
},
|
||||
}
|
||||
}, //data
|
||||
methods:{
|
||||
async fetchData(){
|
||||
const response = await axios.get('https://holidayapi.com/v1/holidays?key=a4b2083b-1577-4acd-9408-6e529996b129&country=US&year=2017&month=09');
|
||||
console.log(response);
|
||||
return response.data.holidays;
|
||||
},
|
||||
startDownload(){
|
||||
alert('show loading');
|
||||
},
|
||||
finishDownload(){
|
||||
alert('hide loading');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
```
|
||||
|
||||
## Using callbacks
|
||||
|
||||
when using callback functions in the fields description, you have three option to retrieve data:
|
||||
|
||||
- **field: 'path.to.nested.property'** you can retrieve a specific value using the nested property notation.
|
||||
|
||||
```js
|
||||
json_fields: {
|
||||
'Complete name': 'name',
|
||||
'City': 'city',
|
||||
'Telephone': 'phone.mobile',
|
||||
'Telephone 2' : {
|
||||
field: 'phone.landline',
|
||||
callback: (value) => {
|
||||
return `Landline Phone - ${value}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
- **field: 'define.nested.object'** you can retrieve a nested object too.
|
||||
|
||||
```js
|
||||
json_fields: {s
|
||||
'Complete name': 'name',
|
||||
'City': 'city',
|
||||
'Telephone': 'phone.mobile',
|
||||
'Telephone 2' : {
|
||||
field: 'phone',
|
||||
callback: (value) => {
|
||||
return `Landline Phone - ${value.landline}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
- Or **get the whole row** if field is undefined.
|
||||
|
||||
```js
|
||||
json_fields: {
|
||||
'Complete name': 'name',
|
||||
'City': 'city',
|
||||
'Telephone': 'phone.mobile',
|
||||
'Telephone 2' : {
|
||||
callback: (value) => {
|
||||
return `Landline Phone - ${value.phone.landline}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
#### Status
|
||||
|
||||
This project is in an early stage of development. Any contribution is welcome :D
|
654
node_modules/_vue-json-excel@0.3.0@vue-json-excel/dist/vue-json-excel.cjs.js
generated
vendored
Executable file
654
node_modules/_vue-json-excel@0.3.0@vue-json-excel/dist/vue-json-excel.cjs.js
generated
vendored
Executable file
|
@ -0,0 +1,654 @@
|
|||
'use strict';
|
||||
|
||||
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
||||
|
||||
function createCommonjsModule(fn, module) {
|
||||
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
||||
}
|
||||
|
||||
var download = createCommonjsModule(function (module, exports) {
|
||||
//download.js v4.2, by dandavis; 2008-2016. [MIT] see http://danml.com/download.html for tests/usage
|
||||
// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
|
||||
// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
|
||||
// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.
|
||||
// v4 adds AMD/UMD, commonJS, and plain browser support
|
||||
// v4.1 adds url download capability via solo URL argument (same domain/CORS only)
|
||||
// v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
|
||||
// https://github.com/rndme/download
|
||||
|
||||
(function (root, factory) {
|
||||
{
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like environments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory();
|
||||
}
|
||||
}(commonjsGlobal, function () {
|
||||
|
||||
return function download(data, strFileName, strMimeType) {
|
||||
|
||||
var self = window, // this script is only for browsers anyway...
|
||||
defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads
|
||||
mimeType = strMimeType || defaultMime,
|
||||
payload = data,
|
||||
url = !strFileName && !strMimeType && payload,
|
||||
anchor = document.createElement("a"),
|
||||
toString = function(a){return String(a);},
|
||||
myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString),
|
||||
fileName = strFileName || "download",
|
||||
blob,
|
||||
reader;
|
||||
myBlob= myBlob.call ? myBlob.bind(self) : Blob ;
|
||||
|
||||
if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
|
||||
payload=[payload, mimeType];
|
||||
mimeType=payload[0];
|
||||
payload=payload[1];
|
||||
}
|
||||
|
||||
|
||||
if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument
|
||||
fileName = url.split("/").pop().split("?")[0];
|
||||
anchor.href = url; // assign href prop to temp anchor
|
||||
if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path:
|
||||
var ajax=new XMLHttpRequest();
|
||||
ajax.open( "GET", url, true);
|
||||
ajax.responseType = 'blob';
|
||||
ajax.onload= function(e){
|
||||
download(e.target.response, fileName, defaultMime);
|
||||
};
|
||||
setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return:
|
||||
return ajax;
|
||||
} // end if valid url?
|
||||
} // end if url?
|
||||
|
||||
|
||||
//go ahead and download dataURLs right away
|
||||
if(/^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(payload)){
|
||||
|
||||
if(payload.length > (1024*1024*1.999) && myBlob !== toString ){
|
||||
payload=dataUrlToBlob(payload);
|
||||
mimeType=payload.type || defaultMime;
|
||||
}else {
|
||||
return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
|
||||
navigator.msSaveBlob(dataUrlToBlob(payload), fileName) :
|
||||
saver(payload) ; // everyone else can save dataURLs un-processed
|
||||
}
|
||||
|
||||
}else {//not data url, is it a string with special needs?
|
||||
if(/([\x80-\xff])/.test(payload)){
|
||||
var i=0, tempUiArr= new Uint8Array(payload.length), mx=tempUiArr.length;
|
||||
for(i;i<mx;++i) tempUiArr[i]= payload.charCodeAt(i);
|
||||
payload=new myBlob([tempUiArr], {type: mimeType});
|
||||
}
|
||||
}
|
||||
blob = payload instanceof myBlob ?
|
||||
payload :
|
||||
new myBlob([payload], {type: mimeType}) ;
|
||||
|
||||
|
||||
function dataUrlToBlob(strUrl) {
|
||||
var parts= strUrl.split(/[:;,]/),
|
||||
type= parts[1],
|
||||
decoder= parts[2] == "base64" ? atob : decodeURIComponent,
|
||||
binData= decoder( parts.pop() ),
|
||||
mx= binData.length,
|
||||
i= 0,
|
||||
uiArr= new Uint8Array(mx);
|
||||
|
||||
for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
|
||||
|
||||
return new myBlob([uiArr], {type: type});
|
||||
}
|
||||
|
||||
function saver(url, winMode){
|
||||
|
||||
if ('download' in anchor) { //html5 A[download]
|
||||
anchor.href = url;
|
||||
anchor.setAttribute("download", fileName);
|
||||
anchor.className = "download-js-link";
|
||||
anchor.innerHTML = "downloading...";
|
||||
anchor.style.display = "none";
|
||||
document.body.appendChild(anchor);
|
||||
setTimeout(function() {
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
|
||||
}, 66);
|
||||
return true;
|
||||
}
|
||||
|
||||
// handle non-a[download] safari as best we can:
|
||||
if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
|
||||
if(/^data:/.test(url)) url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
if(!window.open(url)){ // popup blocked, offer direct download:
|
||||
if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//do iframe dataURL download (old ch+FF):
|
||||
var f = document.createElement("iframe");
|
||||
document.body.appendChild(f);
|
||||
|
||||
if(!winMode && /^data:/.test(url)){ // force a mime that will download:
|
||||
url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
}
|
||||
f.src=url;
|
||||
setTimeout(function(){ document.body.removeChild(f); }, 333);
|
||||
|
||||
}//end saver
|
||||
|
||||
|
||||
|
||||
|
||||
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
|
||||
return navigator.msSaveBlob(blob, fileName);
|
||||
}
|
||||
|
||||
if(self.URL){ // simple fast and modern way using Blob and URL:
|
||||
saver(self.URL.createObjectURL(blob), true);
|
||||
}else {
|
||||
// handle non-Blob()+non-URL browsers:
|
||||
if(typeof blob === "string" || blob.constructor===toString ){
|
||||
try{
|
||||
return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
|
||||
}catch(y){
|
||||
return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
|
||||
}
|
||||
}
|
||||
|
||||
// Blob but not URL support:
|
||||
reader=new FileReader();
|
||||
reader.onload=function(e){
|
||||
saver(this.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
return true;
|
||||
}; /* end download() */
|
||||
}));
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
var script = {
|
||||
props: {
|
||||
// mime type [xls, csv]
|
||||
type: {
|
||||
type: String,
|
||||
default: "xls",
|
||||
},
|
||||
// Json to download
|
||||
data: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
// fields inside the Json Object that you want to export
|
||||
// if no given, all the properties in the Json are exported
|
||||
fields: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
// this prop is used to fix the problem with other components that use the
|
||||
// variable fields, like vee-validate. exportFields works exactly like fields
|
||||
exportFields: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
// Use as fallback when the row has no field values
|
||||
defaultValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
// Title(s) for the data, could be a string or an array of strings (multiple titles)
|
||||
header: {
|
||||
default: null,
|
||||
},
|
||||
// Footer(s) for the data, could be a string or an array of strings (multiple footers)
|
||||
footer: {
|
||||
default: null,
|
||||
},
|
||||
// filename to export
|
||||
name: {
|
||||
type: String,
|
||||
default: "data.xls",
|
||||
},
|
||||
fetch: {
|
||||
type: Function,
|
||||
},
|
||||
meta: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
worksheet: {
|
||||
type: String,
|
||||
default: "Sheet1",
|
||||
},
|
||||
//event before generate was called
|
||||
beforeGenerate: {
|
||||
type: Function,
|
||||
},
|
||||
//event before download pops up
|
||||
beforeFinish: {
|
||||
type: Function,
|
||||
},
|
||||
// Determine if CSV Data should be escaped
|
||||
escapeCsv: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// long number stringify
|
||||
stringifyLongNum: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// unique identifier
|
||||
idName() {
|
||||
var now = new Date().getTime();
|
||||
return "export_" + now;
|
||||
},
|
||||
|
||||
downloadFields() {
|
||||
if (this.fields) return this.fields;
|
||||
|
||||
if (this.exportFields) return this.exportFields;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async generate() {
|
||||
if (typeof this.beforeGenerate === "function") {
|
||||
await this.beforeGenerate();
|
||||
}
|
||||
let data = this.data;
|
||||
if (typeof this.fetch === "function" || !data) data = await this.fetch();
|
||||
|
||||
if (!data || !data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let json = this.getProcessedJson(data, this.downloadFields);
|
||||
if (this.type === "html") {
|
||||
// this is mainly for testing
|
||||
return this.export(
|
||||
this.jsonToXLS(json),
|
||||
this.name.replace(".xls", ".html"),
|
||||
"text/html"
|
||||
);
|
||||
} else if (this.type === "csv") {
|
||||
return this.export(
|
||||
this.jsonToCSV(json),
|
||||
this.name.replace(".xls", ".csv"),
|
||||
"application/csv"
|
||||
);
|
||||
}
|
||||
return this.export(
|
||||
this.jsonToXLS(json),
|
||||
this.name,
|
||||
"application/vnd.ms-excel"
|
||||
);
|
||||
},
|
||||
/*
|
||||
Use downloadjs to generate the download link
|
||||
*/
|
||||
export: async function (data, filename, mime) {
|
||||
let blob = this.base64ToBlob(data, mime);
|
||||
if (typeof this.beforeFinish === "function") await this.beforeFinish();
|
||||
download(blob, filename, mime);
|
||||
},
|
||||
/*
|
||||
jsonToXLS
|
||||
---------------
|
||||
Transform json data into an xml document with MS Excel format, sadly
|
||||
it shows a prompt when it opens, that is a default behavior for
|
||||
Microsoft office and cannot be avoided. It's recommended to use CSV format instead.
|
||||
*/
|
||||
jsonToXLS(data) {
|
||||
let xlsTemp =
|
||||
'<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>${worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--><style>br {mso-data-placement: same-cell;}</style></head><body><table>${table}</table></body></html>';
|
||||
let xlsData = "<thead>";
|
||||
const colspan = Object.keys(data[0]).length;
|
||||
let _self = this;
|
||||
|
||||
//Header
|
||||
const header = this.header || this.$attrs.title;
|
||||
if (header) {
|
||||
xlsData += this.parseExtraData(
|
||||
header,
|
||||
'<tr><th colspan="' + colspan + '">${data}</th></tr>'
|
||||
);
|
||||
}
|
||||
|
||||
//Fields
|
||||
xlsData += "<tr>";
|
||||
for (let key in data[0]) {
|
||||
xlsData += "<th>" + key + "</th>";
|
||||
}
|
||||
xlsData += "</tr>";
|
||||
xlsData += "</thead>";
|
||||
|
||||
//Data
|
||||
xlsData += "<tbody>";
|
||||
data.map(function (item, index) {
|
||||
xlsData += "<tr>";
|
||||
for (let key in item) {
|
||||
xlsData +=
|
||||
"<td>" +
|
||||
_self.preprocessLongNum(
|
||||
_self.valueReformattedForMultilines(item[key])
|
||||
) +
|
||||
"</td>";
|
||||
}
|
||||
xlsData += "</tr>";
|
||||
});
|
||||
xlsData += "</tbody>";
|
||||
|
||||
//Footer
|
||||
if (this.footer != null) {
|
||||
xlsData += "<tfoot>";
|
||||
xlsData += this.parseExtraData(
|
||||
this.footer,
|
||||
'<tr><td colspan="' + colspan + '">${data}</td></tr>'
|
||||
);
|
||||
xlsData += "</tfoot>";
|
||||
}
|
||||
|
||||
return xlsTemp
|
||||
.replace("${table}", xlsData)
|
||||
.replace("${worksheet}", this.worksheet);
|
||||
},
|
||||
/*
|
||||
jsonToCSV
|
||||
---------------
|
||||
Transform json data into an CSV file.
|
||||
*/
|
||||
jsonToCSV(data) {
|
||||
let _self = this;
|
||||
var csvData = [];
|
||||
|
||||
//Header
|
||||
const header = this.header || this.$attrs.title;
|
||||
if (header) {
|
||||
csvData.push(this.parseExtraData(header, "${data}\r\n"));
|
||||
}
|
||||
|
||||
//Fields
|
||||
for (let key in data[0]) {
|
||||
csvData.push(key);
|
||||
csvData.push(",");
|
||||
}
|
||||
csvData.pop();
|
||||
csvData.push("\r\n");
|
||||
//Data
|
||||
data.map(function (item) {
|
||||
for (let key in item) {
|
||||
let escapedCSV = item[key] + "";
|
||||
// Escaped CSV data to string to avoid problems with numbers or other types of values
|
||||
// this is controlled by the prop escapeCsv
|
||||
if (_self.escapeCsv) {
|
||||
escapedCSV = '="' + escapedCSV + '"'; // cast Numbers to string
|
||||
if (escapedCSV.match(/[,"\n]/)) {
|
||||
escapedCSV = '"' + escapedCSV.replace(/\"/g, '""') + '"';
|
||||
}
|
||||
}
|
||||
csvData.push(escapedCSV);
|
||||
csvData.push(",");
|
||||
}
|
||||
csvData.pop();
|
||||
csvData.push("\r\n");
|
||||
});
|
||||
//Footer
|
||||
if (this.footer != null) {
|
||||
csvData.push(this.parseExtraData(this.footer, "${data}\r\n"));
|
||||
}
|
||||
return csvData.join("");
|
||||
},
|
||||
/*
|
||||
getProcessedJson
|
||||
---------------
|
||||
Get only the data to export, if no fields are set return all the data
|
||||
*/
|
||||
getProcessedJson(data, header) {
|
||||
let keys = this.getKeys(data, header);
|
||||
let newData = [];
|
||||
let _self = this;
|
||||
data.map(function (item, index) {
|
||||
let newItem = {};
|
||||
for (let label in keys) {
|
||||
let property = keys[label];
|
||||
newItem[label] = _self.getValue(property, item);
|
||||
}
|
||||
newData.push(newItem);
|
||||
});
|
||||
|
||||
return newData;
|
||||
},
|
||||
getKeys(data, header) {
|
||||
if (header) {
|
||||
return header;
|
||||
}
|
||||
|
||||
let keys = {};
|
||||
for (let key in data[0]) {
|
||||
keys[key] = key;
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
/*
|
||||
parseExtraData
|
||||
---------------
|
||||
Parse title and footer attribute to the csv format
|
||||
*/
|
||||
parseExtraData(extraData, format) {
|
||||
let parseData = "";
|
||||
if (Array.isArray(extraData)) {
|
||||
for (var i = 0; i < extraData.length; i++) {
|
||||
if (extraData[i])
|
||||
parseData += format.replace("${data}", extraData[i]);
|
||||
}
|
||||
} else {
|
||||
parseData += format.replace("${data}", extraData);
|
||||
}
|
||||
return parseData;
|
||||
},
|
||||
|
||||
getValue(key, item) {
|
||||
const field = typeof key !== "object" ? key : key.field;
|
||||
let indexes = typeof field !== "string" ? [] : field.split(".");
|
||||
let value = this.defaultValue;
|
||||
|
||||
if (!field) value = item;
|
||||
else if (indexes.length > 1)
|
||||
value = this.getValueFromNestedItem(item, indexes);
|
||||
else value = this.parseValue(item[field]);
|
||||
|
||||
if (key.hasOwnProperty("callback"))
|
||||
value = this.getValueFromCallback(value, key.callback);
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
/*
|
||||
convert values with newline \n characters into <br/>
|
||||
*/
|
||||
valueReformattedForMultilines(value) {
|
||||
if (typeof value == "string") return value.replace(/\n/gi, "<br/>");
|
||||
else return value;
|
||||
},
|
||||
preprocessLongNum(value) {
|
||||
if (this.stringifyLongNum) {
|
||||
if (String(value).startsWith("0x")) {
|
||||
return value;
|
||||
}
|
||||
if (!isNaN(value) && value != "") {
|
||||
if (value > 99999999999 || value < 0.0000000000001) {
|
||||
return '="' + value + '"';
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getValueFromNestedItem(item, indexes) {
|
||||
let nestedItem = item;
|
||||
for (let index of indexes) {
|
||||
if (nestedItem) nestedItem = nestedItem[index];
|
||||
}
|
||||
return this.parseValue(nestedItem);
|
||||
},
|
||||
|
||||
getValueFromCallback(item, callback) {
|
||||
if (typeof callback !== "function") return this.defaultValue;
|
||||
const value = callback(item);
|
||||
return this.parseValue(value);
|
||||
},
|
||||
parseValue(value) {
|
||||
return value || value === 0 || typeof value === "boolean"
|
||||
? value
|
||||
: this.defaultValue;
|
||||
},
|
||||
base64ToBlob(data, mime) {
|
||||
let base64 = window.btoa(window.unescape(encodeURIComponent(data)));
|
||||
let bstr = atob(base64);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8ClampedArray(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
return new Blob([u8arr], { type: mime });
|
||||
},
|
||||
}, // end methods
|
||||
};
|
||||
|
||||
function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier
|
||||
/* server only */
|
||||
, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
|
||||
if (typeof shadowMode !== 'boolean') {
|
||||
createInjectorSSR = createInjector;
|
||||
createInjector = shadowMode;
|
||||
shadowMode = false;
|
||||
} // Vue.extend constructor export interop.
|
||||
|
||||
|
||||
var options = typeof script === 'function' ? script.options : script; // render functions
|
||||
|
||||
if (template && template.render) {
|
||||
options.render = template.render;
|
||||
options.staticRenderFns = template.staticRenderFns;
|
||||
options._compiled = true; // functional template
|
||||
|
||||
if (isFunctionalTemplate) {
|
||||
options.functional = true;
|
||||
}
|
||||
} // scopedId
|
||||
|
||||
|
||||
if (scopeId) {
|
||||
options._scopeId = scopeId;
|
||||
}
|
||||
|
||||
var hook;
|
||||
|
||||
if (moduleIdentifier) {
|
||||
// server build
|
||||
hook = function hook(context) {
|
||||
// 2.3 injection
|
||||
context = context || // cached call
|
||||
this.$vnode && this.$vnode.ssrContext || // stateful
|
||||
this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; // functional
|
||||
// 2.2 with runInNewContext: true
|
||||
|
||||
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
|
||||
context = __VUE_SSR_CONTEXT__;
|
||||
} // inject component styles
|
||||
|
||||
|
||||
if (style) {
|
||||
style.call(this, createInjectorSSR(context));
|
||||
} // register component module identifier for async chunk inference
|
||||
|
||||
|
||||
if (context && context._registeredComponents) {
|
||||
context._registeredComponents.add(moduleIdentifier);
|
||||
}
|
||||
}; // used by ssr in case component is cached and beforeCreate
|
||||
// never gets called
|
||||
|
||||
|
||||
options._ssrRegister = hook;
|
||||
} else if (style) {
|
||||
hook = shadowMode ? function () {
|
||||
style.call(this, createInjectorShadow(this.$root.$options.shadowRoot));
|
||||
} : function (context) {
|
||||
style.call(this, createInjector(context));
|
||||
};
|
||||
}
|
||||
|
||||
if (hook) {
|
||||
if (options.functional) {
|
||||
// register for functional component in vue file
|
||||
var originalRender = options.render;
|
||||
|
||||
options.render = function renderWithStyleInjection(h, context) {
|
||||
hook.call(context);
|
||||
return originalRender(h, context);
|
||||
};
|
||||
} else {
|
||||
// inject component registration as beforeCreate hook
|
||||
var existing = options.beforeCreate;
|
||||
options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
|
||||
}
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
var normalizeComponent_1 = normalizeComponent;
|
||||
|
||||
/* script */
|
||||
const __vue_script__ = script;
|
||||
|
||||
/* template */
|
||||
var __vue_render__ = function() {
|
||||
var _vm = this;
|
||||
var _h = _vm.$createElement;
|
||||
var _c = _vm._self._c || _h;
|
||||
return _c(
|
||||
"div",
|
||||
{ attrs: { id: _vm.idName }, on: { click: _vm.generate } },
|
||||
[_vm._t("default", [_vm._v(" Download " + _vm._s(_vm.name) + " ")])],
|
||||
2
|
||||
)
|
||||
};
|
||||
var __vue_staticRenderFns__ = [];
|
||||
__vue_render__._withStripped = true;
|
||||
|
||||
/* style */
|
||||
const __vue_inject_styles__ = undefined;
|
||||
/* scoped */
|
||||
const __vue_scope_id__ = undefined;
|
||||
/* module identifier */
|
||||
const __vue_module_identifier__ = undefined;
|
||||
/* functional template */
|
||||
const __vue_is_functional_template__ = false;
|
||||
/* style inject */
|
||||
|
||||
/* style inject SSR */
|
||||
|
||||
|
||||
|
||||
var JsonExcel = normalizeComponent_1(
|
||||
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
|
||||
__vue_inject_styles__,
|
||||
__vue_script__,
|
||||
__vue_scope_id__,
|
||||
__vue_is_functional_template__,
|
||||
__vue_module_identifier__,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
module.exports = JsonExcel;
|
652
node_modules/_vue-json-excel@0.3.0@vue-json-excel/dist/vue-json-excel.esm.js
generated
vendored
Executable file
652
node_modules/_vue-json-excel@0.3.0@vue-json-excel/dist/vue-json-excel.esm.js
generated
vendored
Executable file
|
@ -0,0 +1,652 @@
|
|||
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
||||
|
||||
function createCommonjsModule(fn, module) {
|
||||
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
||||
}
|
||||
|
||||
var download = createCommonjsModule(function (module, exports) {
|
||||
//download.js v4.2, by dandavis; 2008-2016. [MIT] see http://danml.com/download.html for tests/usage
|
||||
// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
|
||||
// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
|
||||
// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.
|
||||
// v4 adds AMD/UMD, commonJS, and plain browser support
|
||||
// v4.1 adds url download capability via solo URL argument (same domain/CORS only)
|
||||
// v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
|
||||
// https://github.com/rndme/download
|
||||
|
||||
(function (root, factory) {
|
||||
{
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like environments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory();
|
||||
}
|
||||
}(commonjsGlobal, function () {
|
||||
|
||||
return function download(data, strFileName, strMimeType) {
|
||||
|
||||
var self = window, // this script is only for browsers anyway...
|
||||
defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads
|
||||
mimeType = strMimeType || defaultMime,
|
||||
payload = data,
|
||||
url = !strFileName && !strMimeType && payload,
|
||||
anchor = document.createElement("a"),
|
||||
toString = function(a){return String(a);},
|
||||
myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString),
|
||||
fileName = strFileName || "download",
|
||||
blob,
|
||||
reader;
|
||||
myBlob= myBlob.call ? myBlob.bind(self) : Blob ;
|
||||
|
||||
if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
|
||||
payload=[payload, mimeType];
|
||||
mimeType=payload[0];
|
||||
payload=payload[1];
|
||||
}
|
||||
|
||||
|
||||
if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument
|
||||
fileName = url.split("/").pop().split("?")[0];
|
||||
anchor.href = url; // assign href prop to temp anchor
|
||||
if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path:
|
||||
var ajax=new XMLHttpRequest();
|
||||
ajax.open( "GET", url, true);
|
||||
ajax.responseType = 'blob';
|
||||
ajax.onload= function(e){
|
||||
download(e.target.response, fileName, defaultMime);
|
||||
};
|
||||
setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return:
|
||||
return ajax;
|
||||
} // end if valid url?
|
||||
} // end if url?
|
||||
|
||||
|
||||
//go ahead and download dataURLs right away
|
||||
if(/^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(payload)){
|
||||
|
||||
if(payload.length > (1024*1024*1.999) && myBlob !== toString ){
|
||||
payload=dataUrlToBlob(payload);
|
||||
mimeType=payload.type || defaultMime;
|
||||
}else {
|
||||
return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
|
||||
navigator.msSaveBlob(dataUrlToBlob(payload), fileName) :
|
||||
saver(payload) ; // everyone else can save dataURLs un-processed
|
||||
}
|
||||
|
||||
}else {//not data url, is it a string with special needs?
|
||||
if(/([\x80-\xff])/.test(payload)){
|
||||
var i=0, tempUiArr= new Uint8Array(payload.length), mx=tempUiArr.length;
|
||||
for(i;i<mx;++i) tempUiArr[i]= payload.charCodeAt(i);
|
||||
payload=new myBlob([tempUiArr], {type: mimeType});
|
||||
}
|
||||
}
|
||||
blob = payload instanceof myBlob ?
|
||||
payload :
|
||||
new myBlob([payload], {type: mimeType}) ;
|
||||
|
||||
|
||||
function dataUrlToBlob(strUrl) {
|
||||
var parts= strUrl.split(/[:;,]/),
|
||||
type= parts[1],
|
||||
decoder= parts[2] == "base64" ? atob : decodeURIComponent,
|
||||
binData= decoder( parts.pop() ),
|
||||
mx= binData.length,
|
||||
i= 0,
|
||||
uiArr= new Uint8Array(mx);
|
||||
|
||||
for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
|
||||
|
||||
return new myBlob([uiArr], {type: type});
|
||||
}
|
||||
|
||||
function saver(url, winMode){
|
||||
|
||||
if ('download' in anchor) { //html5 A[download]
|
||||
anchor.href = url;
|
||||
anchor.setAttribute("download", fileName);
|
||||
anchor.className = "download-js-link";
|
||||
anchor.innerHTML = "downloading...";
|
||||
anchor.style.display = "none";
|
||||
document.body.appendChild(anchor);
|
||||
setTimeout(function() {
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
|
||||
}, 66);
|
||||
return true;
|
||||
}
|
||||
|
||||
// handle non-a[download] safari as best we can:
|
||||
if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
|
||||
if(/^data:/.test(url)) url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
if(!window.open(url)){ // popup blocked, offer direct download:
|
||||
if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//do iframe dataURL download (old ch+FF):
|
||||
var f = document.createElement("iframe");
|
||||
document.body.appendChild(f);
|
||||
|
||||
if(!winMode && /^data:/.test(url)){ // force a mime that will download:
|
||||
url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
}
|
||||
f.src=url;
|
||||
setTimeout(function(){ document.body.removeChild(f); }, 333);
|
||||
|
||||
}//end saver
|
||||
|
||||
|
||||
|
||||
|
||||
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
|
||||
return navigator.msSaveBlob(blob, fileName);
|
||||
}
|
||||
|
||||
if(self.URL){ // simple fast and modern way using Blob and URL:
|
||||
saver(self.URL.createObjectURL(blob), true);
|
||||
}else {
|
||||
// handle non-Blob()+non-URL browsers:
|
||||
if(typeof blob === "string" || blob.constructor===toString ){
|
||||
try{
|
||||
return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
|
||||
}catch(y){
|
||||
return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
|
||||
}
|
||||
}
|
||||
|
||||
// Blob but not URL support:
|
||||
reader=new FileReader();
|
||||
reader.onload=function(e){
|
||||
saver(this.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
return true;
|
||||
}; /* end download() */
|
||||
}));
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
var script = {
|
||||
props: {
|
||||
// mime type [xls, csv]
|
||||
type: {
|
||||
type: String,
|
||||
default: "xls",
|
||||
},
|
||||
// Json to download
|
||||
data: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
// fields inside the Json Object that you want to export
|
||||
// if no given, all the properties in the Json are exported
|
||||
fields: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
// this prop is used to fix the problem with other components that use the
|
||||
// variable fields, like vee-validate. exportFields works exactly like fields
|
||||
exportFields: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
// Use as fallback when the row has no field values
|
||||
defaultValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
// Title(s) for the data, could be a string or an array of strings (multiple titles)
|
||||
header: {
|
||||
default: null,
|
||||
},
|
||||
// Footer(s) for the data, could be a string or an array of strings (multiple footers)
|
||||
footer: {
|
||||
default: null,
|
||||
},
|
||||
// filename to export
|
||||
name: {
|
||||
type: String,
|
||||
default: "data.xls",
|
||||
},
|
||||
fetch: {
|
||||
type: Function,
|
||||
},
|
||||
meta: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
worksheet: {
|
||||
type: String,
|
||||
default: "Sheet1",
|
||||
},
|
||||
//event before generate was called
|
||||
beforeGenerate: {
|
||||
type: Function,
|
||||
},
|
||||
//event before download pops up
|
||||
beforeFinish: {
|
||||
type: Function,
|
||||
},
|
||||
// Determine if CSV Data should be escaped
|
||||
escapeCsv: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// long number stringify
|
||||
stringifyLongNum: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// unique identifier
|
||||
idName() {
|
||||
var now = new Date().getTime();
|
||||
return "export_" + now;
|
||||
},
|
||||
|
||||
downloadFields() {
|
||||
if (this.fields) return this.fields;
|
||||
|
||||
if (this.exportFields) return this.exportFields;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async generate() {
|
||||
if (typeof this.beforeGenerate === "function") {
|
||||
await this.beforeGenerate();
|
||||
}
|
||||
let data = this.data;
|
||||
if (typeof this.fetch === "function" || !data) data = await this.fetch();
|
||||
|
||||
if (!data || !data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let json = this.getProcessedJson(data, this.downloadFields);
|
||||
if (this.type === "html") {
|
||||
// this is mainly for testing
|
||||
return this.export(
|
||||
this.jsonToXLS(json),
|
||||
this.name.replace(".xls", ".html"),
|
||||
"text/html"
|
||||
);
|
||||
} else if (this.type === "csv") {
|
||||
return this.export(
|
||||
this.jsonToCSV(json),
|
||||
this.name.replace(".xls", ".csv"),
|
||||
"application/csv"
|
||||
);
|
||||
}
|
||||
return this.export(
|
||||
this.jsonToXLS(json),
|
||||
this.name,
|
||||
"application/vnd.ms-excel"
|
||||
);
|
||||
},
|
||||
/*
|
||||
Use downloadjs to generate the download link
|
||||
*/
|
||||
export: async function (data, filename, mime) {
|
||||
let blob = this.base64ToBlob(data, mime);
|
||||
if (typeof this.beforeFinish === "function") await this.beforeFinish();
|
||||
download(blob, filename, mime);
|
||||
},
|
||||
/*
|
||||
jsonToXLS
|
||||
---------------
|
||||
Transform json data into an xml document with MS Excel format, sadly
|
||||
it shows a prompt when it opens, that is a default behavior for
|
||||
Microsoft office and cannot be avoided. It's recommended to use CSV format instead.
|
||||
*/
|
||||
jsonToXLS(data) {
|
||||
let xlsTemp =
|
||||
'<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>${worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--><style>br {mso-data-placement: same-cell;}</style></head><body><table>${table}</table></body></html>';
|
||||
let xlsData = "<thead>";
|
||||
const colspan = Object.keys(data[0]).length;
|
||||
let _self = this;
|
||||
|
||||
//Header
|
||||
const header = this.header || this.$attrs.title;
|
||||
if (header) {
|
||||
xlsData += this.parseExtraData(
|
||||
header,
|
||||
'<tr><th colspan="' + colspan + '">${data}</th></tr>'
|
||||
);
|
||||
}
|
||||
|
||||
//Fields
|
||||
xlsData += "<tr>";
|
||||
for (let key in data[0]) {
|
||||
xlsData += "<th>" + key + "</th>";
|
||||
}
|
||||
xlsData += "</tr>";
|
||||
xlsData += "</thead>";
|
||||
|
||||
//Data
|
||||
xlsData += "<tbody>";
|
||||
data.map(function (item, index) {
|
||||
xlsData += "<tr>";
|
||||
for (let key in item) {
|
||||
xlsData +=
|
||||
"<td>" +
|
||||
_self.preprocessLongNum(
|
||||
_self.valueReformattedForMultilines(item[key])
|
||||
) +
|
||||
"</td>";
|
||||
}
|
||||
xlsData += "</tr>";
|
||||
});
|
||||
xlsData += "</tbody>";
|
||||
|
||||
//Footer
|
||||
if (this.footer != null) {
|
||||
xlsData += "<tfoot>";
|
||||
xlsData += this.parseExtraData(
|
||||
this.footer,
|
||||
'<tr><td colspan="' + colspan + '">${data}</td></tr>'
|
||||
);
|
||||
xlsData += "</tfoot>";
|
||||
}
|
||||
|
||||
return xlsTemp
|
||||
.replace("${table}", xlsData)
|
||||
.replace("${worksheet}", this.worksheet);
|
||||
},
|
||||
/*
|
||||
jsonToCSV
|
||||
---------------
|
||||
Transform json data into an CSV file.
|
||||
*/
|
||||
jsonToCSV(data) {
|
||||
let _self = this;
|
||||
var csvData = [];
|
||||
|
||||
//Header
|
||||
const header = this.header || this.$attrs.title;
|
||||
if (header) {
|
||||
csvData.push(this.parseExtraData(header, "${data}\r\n"));
|
||||
}
|
||||
|
||||
//Fields
|
||||
for (let key in data[0]) {
|
||||
csvData.push(key);
|
||||
csvData.push(",");
|
||||
}
|
||||
csvData.pop();
|
||||
csvData.push("\r\n");
|
||||
//Data
|
||||
data.map(function (item) {
|
||||
for (let key in item) {
|
||||
let escapedCSV = item[key] + "";
|
||||
// Escaped CSV data to string to avoid problems with numbers or other types of values
|
||||
// this is controlled by the prop escapeCsv
|
||||
if (_self.escapeCsv) {
|
||||
escapedCSV = '="' + escapedCSV + '"'; // cast Numbers to string
|
||||
if (escapedCSV.match(/[,"\n]/)) {
|
||||
escapedCSV = '"' + escapedCSV.replace(/\"/g, '""') + '"';
|
||||
}
|
||||
}
|
||||
csvData.push(escapedCSV);
|
||||
csvData.push(",");
|
||||
}
|
||||
csvData.pop();
|
||||
csvData.push("\r\n");
|
||||
});
|
||||
//Footer
|
||||
if (this.footer != null) {
|
||||
csvData.push(this.parseExtraData(this.footer, "${data}\r\n"));
|
||||
}
|
||||
return csvData.join("");
|
||||
},
|
||||
/*
|
||||
getProcessedJson
|
||||
---------------
|
||||
Get only the data to export, if no fields are set return all the data
|
||||
*/
|
||||
getProcessedJson(data, header) {
|
||||
let keys = this.getKeys(data, header);
|
||||
let newData = [];
|
||||
let _self = this;
|
||||
data.map(function (item, index) {
|
||||
let newItem = {};
|
||||
for (let label in keys) {
|
||||
let property = keys[label];
|
||||
newItem[label] = _self.getValue(property, item);
|
||||
}
|
||||
newData.push(newItem);
|
||||
});
|
||||
|
||||
return newData;
|
||||
},
|
||||
getKeys(data, header) {
|
||||
if (header) {
|
||||
return header;
|
||||
}
|
||||
|
||||
let keys = {};
|
||||
for (let key in data[0]) {
|
||||
keys[key] = key;
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
/*
|
||||
parseExtraData
|
||||
---------------
|
||||
Parse title and footer attribute to the csv format
|
||||
*/
|
||||
parseExtraData(extraData, format) {
|
||||
let parseData = "";
|
||||
if (Array.isArray(extraData)) {
|
||||
for (var i = 0; i < extraData.length; i++) {
|
||||
if (extraData[i])
|
||||
parseData += format.replace("${data}", extraData[i]);
|
||||
}
|
||||
} else {
|
||||
parseData += format.replace("${data}", extraData);
|
||||
}
|
||||
return parseData;
|
||||
},
|
||||
|
||||
getValue(key, item) {
|
||||
const field = typeof key !== "object" ? key : key.field;
|
||||
let indexes = typeof field !== "string" ? [] : field.split(".");
|
||||
let value = this.defaultValue;
|
||||
|
||||
if (!field) value = item;
|
||||
else if (indexes.length > 1)
|
||||
value = this.getValueFromNestedItem(item, indexes);
|
||||
else value = this.parseValue(item[field]);
|
||||
|
||||
if (key.hasOwnProperty("callback"))
|
||||
value = this.getValueFromCallback(value, key.callback);
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
/*
|
||||
convert values with newline \n characters into <br/>
|
||||
*/
|
||||
valueReformattedForMultilines(value) {
|
||||
if (typeof value == "string") return value.replace(/\n/gi, "<br/>");
|
||||
else return value;
|
||||
},
|
||||
preprocessLongNum(value) {
|
||||
if (this.stringifyLongNum) {
|
||||
if (String(value).startsWith("0x")) {
|
||||
return value;
|
||||
}
|
||||
if (!isNaN(value) && value != "") {
|
||||
if (value > 99999999999 || value < 0.0000000000001) {
|
||||
return '="' + value + '"';
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getValueFromNestedItem(item, indexes) {
|
||||
let nestedItem = item;
|
||||
for (let index of indexes) {
|
||||
if (nestedItem) nestedItem = nestedItem[index];
|
||||
}
|
||||
return this.parseValue(nestedItem);
|
||||
},
|
||||
|
||||
getValueFromCallback(item, callback) {
|
||||
if (typeof callback !== "function") return this.defaultValue;
|
||||
const value = callback(item);
|
||||
return this.parseValue(value);
|
||||
},
|
||||
parseValue(value) {
|
||||
return value || value === 0 || typeof value === "boolean"
|
||||
? value
|
||||
: this.defaultValue;
|
||||
},
|
||||
base64ToBlob(data, mime) {
|
||||
let base64 = window.btoa(window.unescape(encodeURIComponent(data)));
|
||||
let bstr = atob(base64);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8ClampedArray(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
return new Blob([u8arr], { type: mime });
|
||||
},
|
||||
}, // end methods
|
||||
};
|
||||
|
||||
function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier
|
||||
/* server only */
|
||||
, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
|
||||
if (typeof shadowMode !== 'boolean') {
|
||||
createInjectorSSR = createInjector;
|
||||
createInjector = shadowMode;
|
||||
shadowMode = false;
|
||||
} // Vue.extend constructor export interop.
|
||||
|
||||
|
||||
var options = typeof script === 'function' ? script.options : script; // render functions
|
||||
|
||||
if (template && template.render) {
|
||||
options.render = template.render;
|
||||
options.staticRenderFns = template.staticRenderFns;
|
||||
options._compiled = true; // functional template
|
||||
|
||||
if (isFunctionalTemplate) {
|
||||
options.functional = true;
|
||||
}
|
||||
} // scopedId
|
||||
|
||||
|
||||
if (scopeId) {
|
||||
options._scopeId = scopeId;
|
||||
}
|
||||
|
||||
var hook;
|
||||
|
||||
if (moduleIdentifier) {
|
||||
// server build
|
||||
hook = function hook(context) {
|
||||
// 2.3 injection
|
||||
context = context || // cached call
|
||||
this.$vnode && this.$vnode.ssrContext || // stateful
|
||||
this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; // functional
|
||||
// 2.2 with runInNewContext: true
|
||||
|
||||
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
|
||||
context = __VUE_SSR_CONTEXT__;
|
||||
} // inject component styles
|
||||
|
||||
|
||||
if (style) {
|
||||
style.call(this, createInjectorSSR(context));
|
||||
} // register component module identifier for async chunk inference
|
||||
|
||||
|
||||
if (context && context._registeredComponents) {
|
||||
context._registeredComponents.add(moduleIdentifier);
|
||||
}
|
||||
}; // used by ssr in case component is cached and beforeCreate
|
||||
// never gets called
|
||||
|
||||
|
||||
options._ssrRegister = hook;
|
||||
} else if (style) {
|
||||
hook = shadowMode ? function () {
|
||||
style.call(this, createInjectorShadow(this.$root.$options.shadowRoot));
|
||||
} : function (context) {
|
||||
style.call(this, createInjector(context));
|
||||
};
|
||||
}
|
||||
|
||||
if (hook) {
|
||||
if (options.functional) {
|
||||
// register for functional component in vue file
|
||||
var originalRender = options.render;
|
||||
|
||||
options.render = function renderWithStyleInjection(h, context) {
|
||||
hook.call(context);
|
||||
return originalRender(h, context);
|
||||
};
|
||||
} else {
|
||||
// inject component registration as beforeCreate hook
|
||||
var existing = options.beforeCreate;
|
||||
options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
|
||||
}
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
var normalizeComponent_1 = normalizeComponent;
|
||||
|
||||
/* script */
|
||||
const __vue_script__ = script;
|
||||
|
||||
/* template */
|
||||
var __vue_render__ = function() {
|
||||
var _vm = this;
|
||||
var _h = _vm.$createElement;
|
||||
var _c = _vm._self._c || _h;
|
||||
return _c(
|
||||
"div",
|
||||
{ attrs: { id: _vm.idName }, on: { click: _vm.generate } },
|
||||
[_vm._t("default", [_vm._v(" Download " + _vm._s(_vm.name) + " ")])],
|
||||
2
|
||||
)
|
||||
};
|
||||
var __vue_staticRenderFns__ = [];
|
||||
__vue_render__._withStripped = true;
|
||||
|
||||
/* style */
|
||||
const __vue_inject_styles__ = undefined;
|
||||
/* scoped */
|
||||
const __vue_scope_id__ = undefined;
|
||||
/* module identifier */
|
||||
const __vue_module_identifier__ = undefined;
|
||||
/* functional template */
|
||||
const __vue_is_functional_template__ = false;
|
||||
/* style inject */
|
||||
|
||||
/* style inject SSR */
|
||||
|
||||
|
||||
|
||||
var JsonExcel = normalizeComponent_1(
|
||||
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
|
||||
__vue_inject_styles__,
|
||||
__vue_script__,
|
||||
__vue_scope_id__,
|
||||
__vue_is_functional_template__,
|
||||
__vue_module_identifier__,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
export default JsonExcel;
|
660
node_modules/_vue-json-excel@0.3.0@vue-json-excel/dist/vue-json-excel.umd.js
generated
vendored
Executable file
660
node_modules/_vue-json-excel@0.3.0@vue-json-excel/dist/vue-json-excel.umd.js
generated
vendored
Executable file
|
@ -0,0 +1,660 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||
typeof define === 'function' && define.amd ? define(factory) :
|
||||
(global = global || self, global.JsonExcel = factory());
|
||||
}(this, (function () { 'use strict';
|
||||
|
||||
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
||||
|
||||
function createCommonjsModule(fn, module) {
|
||||
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
||||
}
|
||||
|
||||
var download = createCommonjsModule(function (module, exports) {
|
||||
//download.js v4.2, by dandavis; 2008-2016. [MIT] see http://danml.com/download.html for tests/usage
|
||||
// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
|
||||
// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
|
||||
// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.
|
||||
// v4 adds AMD/UMD, commonJS, and plain browser support
|
||||
// v4.1 adds url download capability via solo URL argument (same domain/CORS only)
|
||||
// v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
|
||||
// https://github.com/rndme/download
|
||||
|
||||
(function (root, factory) {
|
||||
{
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like environments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory();
|
||||
}
|
||||
}(commonjsGlobal, function () {
|
||||
|
||||
return function download(data, strFileName, strMimeType) {
|
||||
|
||||
var self = window, // this script is only for browsers anyway...
|
||||
defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads
|
||||
mimeType = strMimeType || defaultMime,
|
||||
payload = data,
|
||||
url = !strFileName && !strMimeType && payload,
|
||||
anchor = document.createElement("a"),
|
||||
toString = function(a){return String(a);},
|
||||
myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString),
|
||||
fileName = strFileName || "download",
|
||||
blob,
|
||||
reader;
|
||||
myBlob= myBlob.call ? myBlob.bind(self) : Blob ;
|
||||
|
||||
if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
|
||||
payload=[payload, mimeType];
|
||||
mimeType=payload[0];
|
||||
payload=payload[1];
|
||||
}
|
||||
|
||||
|
||||
if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument
|
||||
fileName = url.split("/").pop().split("?")[0];
|
||||
anchor.href = url; // assign href prop to temp anchor
|
||||
if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path:
|
||||
var ajax=new XMLHttpRequest();
|
||||
ajax.open( "GET", url, true);
|
||||
ajax.responseType = 'blob';
|
||||
ajax.onload= function(e){
|
||||
download(e.target.response, fileName, defaultMime);
|
||||
};
|
||||
setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return:
|
||||
return ajax;
|
||||
} // end if valid url?
|
||||
} // end if url?
|
||||
|
||||
|
||||
//go ahead and download dataURLs right away
|
||||
if(/^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(payload)){
|
||||
|
||||
if(payload.length > (1024*1024*1.999) && myBlob !== toString ){
|
||||
payload=dataUrlToBlob(payload);
|
||||
mimeType=payload.type || defaultMime;
|
||||
}else {
|
||||
return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
|
||||
navigator.msSaveBlob(dataUrlToBlob(payload), fileName) :
|
||||
saver(payload) ; // everyone else can save dataURLs un-processed
|
||||
}
|
||||
|
||||
}else {//not data url, is it a string with special needs?
|
||||
if(/([\x80-\xff])/.test(payload)){
|
||||
var i=0, tempUiArr= new Uint8Array(payload.length), mx=tempUiArr.length;
|
||||
for(i;i<mx;++i) tempUiArr[i]= payload.charCodeAt(i);
|
||||
payload=new myBlob([tempUiArr], {type: mimeType});
|
||||
}
|
||||
}
|
||||
blob = payload instanceof myBlob ?
|
||||
payload :
|
||||
new myBlob([payload], {type: mimeType}) ;
|
||||
|
||||
|
||||
function dataUrlToBlob(strUrl) {
|
||||
var parts= strUrl.split(/[:;,]/),
|
||||
type= parts[1],
|
||||
decoder= parts[2] == "base64" ? atob : decodeURIComponent,
|
||||
binData= decoder( parts.pop() ),
|
||||
mx= binData.length,
|
||||
i= 0,
|
||||
uiArr= new Uint8Array(mx);
|
||||
|
||||
for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
|
||||
|
||||
return new myBlob([uiArr], {type: type});
|
||||
}
|
||||
|
||||
function saver(url, winMode){
|
||||
|
||||
if ('download' in anchor) { //html5 A[download]
|
||||
anchor.href = url;
|
||||
anchor.setAttribute("download", fileName);
|
||||
anchor.className = "download-js-link";
|
||||
anchor.innerHTML = "downloading...";
|
||||
anchor.style.display = "none";
|
||||
document.body.appendChild(anchor);
|
||||
setTimeout(function() {
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
|
||||
}, 66);
|
||||
return true;
|
||||
}
|
||||
|
||||
// handle non-a[download] safari as best we can:
|
||||
if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
|
||||
if(/^data:/.test(url)) url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
if(!window.open(url)){ // popup blocked, offer direct download:
|
||||
if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//do iframe dataURL download (old ch+FF):
|
||||
var f = document.createElement("iframe");
|
||||
document.body.appendChild(f);
|
||||
|
||||
if(!winMode && /^data:/.test(url)){ // force a mime that will download:
|
||||
url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
}
|
||||
f.src=url;
|
||||
setTimeout(function(){ document.body.removeChild(f); }, 333);
|
||||
|
||||
}//end saver
|
||||
|
||||
|
||||
|
||||
|
||||
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
|
||||
return navigator.msSaveBlob(blob, fileName);
|
||||
}
|
||||
|
||||
if(self.URL){ // simple fast and modern way using Blob and URL:
|
||||
saver(self.URL.createObjectURL(blob), true);
|
||||
}else {
|
||||
// handle non-Blob()+non-URL browsers:
|
||||
if(typeof blob === "string" || blob.constructor===toString ){
|
||||
try{
|
||||
return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
|
||||
}catch(y){
|
||||
return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
|
||||
}
|
||||
}
|
||||
|
||||
// Blob but not URL support:
|
||||
reader=new FileReader();
|
||||
reader.onload=function(e){
|
||||
saver(this.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
return true;
|
||||
}; /* end download() */
|
||||
}));
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
var script = {
|
||||
props: {
|
||||
// mime type [xls, csv]
|
||||
type: {
|
||||
type: String,
|
||||
default: "xls",
|
||||
},
|
||||
// Json to download
|
||||
data: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
// fields inside the Json Object that you want to export
|
||||
// if no given, all the properties in the Json are exported
|
||||
fields: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
// this prop is used to fix the problem with other components that use the
|
||||
// variable fields, like vee-validate. exportFields works exactly like fields
|
||||
exportFields: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
// Use as fallback when the row has no field values
|
||||
defaultValue: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
// Title(s) for the data, could be a string or an array of strings (multiple titles)
|
||||
header: {
|
||||
default: null,
|
||||
},
|
||||
// Footer(s) for the data, could be a string or an array of strings (multiple footers)
|
||||
footer: {
|
||||
default: null,
|
||||
},
|
||||
// filename to export
|
||||
name: {
|
||||
type: String,
|
||||
default: "data.xls",
|
||||
},
|
||||
fetch: {
|
||||
type: Function,
|
||||
},
|
||||
meta: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
worksheet: {
|
||||
type: String,
|
||||
default: "Sheet1",
|
||||
},
|
||||
//event before generate was called
|
||||
beforeGenerate: {
|
||||
type: Function,
|
||||
},
|
||||
//event before download pops up
|
||||
beforeFinish: {
|
||||
type: Function,
|
||||
},
|
||||
// Determine if CSV Data should be escaped
|
||||
escapeCsv: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// long number stringify
|
||||
stringifyLongNum: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// unique identifier
|
||||
idName() {
|
||||
var now = new Date().getTime();
|
||||
return "export_" + now;
|
||||
},
|
||||
|
||||
downloadFields() {
|
||||
if (this.fields) return this.fields;
|
||||
|
||||
if (this.exportFields) return this.exportFields;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async generate() {
|
||||
if (typeof this.beforeGenerate === "function") {
|
||||
await this.beforeGenerate();
|
||||
}
|
||||
let data = this.data;
|
||||
if (typeof this.fetch === "function" || !data) data = await this.fetch();
|
||||
|
||||
if (!data || !data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let json = this.getProcessedJson(data, this.downloadFields);
|
||||
if (this.type === "html") {
|
||||
// this is mainly for testing
|
||||
return this.export(
|
||||
this.jsonToXLS(json),
|
||||
this.name.replace(".xls", ".html"),
|
||||
"text/html"
|
||||
);
|
||||
} else if (this.type === "csv") {
|
||||
return this.export(
|
||||
this.jsonToCSV(json),
|
||||
this.name.replace(".xls", ".csv"),
|
||||
"application/csv"
|
||||
);
|
||||
}
|
||||
return this.export(
|
||||
this.jsonToXLS(json),
|
||||
this.name,
|
||||
"application/vnd.ms-excel"
|
||||
);
|
||||
},
|
||||
/*
|
||||
Use downloadjs to generate the download link
|
||||
*/
|
||||
export: async function (data, filename, mime) {
|
||||
let blob = this.base64ToBlob(data, mime);
|
||||
if (typeof this.beforeFinish === "function") await this.beforeFinish();
|
||||
download(blob, filename, mime);
|
||||
},
|
||||
/*
|
||||
jsonToXLS
|
||||
---------------
|
||||
Transform json data into an xml document with MS Excel format, sadly
|
||||
it shows a prompt when it opens, that is a default behavior for
|
||||
Microsoft office and cannot be avoided. It's recommended to use CSV format instead.
|
||||
*/
|
||||
jsonToXLS(data) {
|
||||
let xlsTemp =
|
||||
'<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>${worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--><style>br {mso-data-placement: same-cell;}</style></head><body><table>${table}</table></body></html>';
|
||||
let xlsData = "<thead>";
|
||||
const colspan = Object.keys(data[0]).length;
|
||||
let _self = this;
|
||||
|
||||
//Header
|
||||
const header = this.header || this.$attrs.title;
|
||||
if (header) {
|
||||
xlsData += this.parseExtraData(
|
||||
header,
|
||||
'<tr><th colspan="' + colspan + '">${data}</th></tr>'
|
||||
);
|
||||
}
|
||||
|
||||
//Fields
|
||||
xlsData += "<tr>";
|
||||
for (let key in data[0]) {
|
||||
xlsData += "<th>" + key + "</th>";
|
||||
}
|
||||
xlsData += "</tr>";
|
||||
xlsData += "</thead>";
|
||||
|
||||
//Data
|
||||
xlsData += "<tbody>";
|
||||
data.map(function (item, index) {
|
||||
xlsData += "<tr>";
|
||||
for (let key in item) {
|
||||
xlsData +=
|
||||
"<td>" +
|
||||
_self.preprocessLongNum(
|
||||
_self.valueReformattedForMultilines(item[key])
|
||||
) +
|
||||
"</td>";
|
||||
}
|
||||
xlsData += "</tr>";
|
||||
});
|
||||
xlsData += "</tbody>";
|
||||
|
||||
//Footer
|
||||
if (this.footer != null) {
|
||||
xlsData += "<tfoot>";
|
||||
xlsData += this.parseExtraData(
|
||||
this.footer,
|
||||
'<tr><td colspan="' + colspan + '">${data}</td></tr>'
|
||||
);
|
||||
xlsData += "</tfoot>";
|
||||
}
|
||||
|
||||
return xlsTemp
|
||||
.replace("${table}", xlsData)
|
||||
.replace("${worksheet}", this.worksheet);
|
||||
},
|
||||
/*
|
||||
jsonToCSV
|
||||
---------------
|
||||
Transform json data into an CSV file.
|
||||
*/
|
||||
jsonToCSV(data) {
|
||||
let _self = this;
|
||||
var csvData = [];
|
||||
|
||||
//Header
|
||||
const header = this.header || this.$attrs.title;
|
||||
if (header) {
|
||||
csvData.push(this.parseExtraData(header, "${data}\r\n"));
|
||||
}
|
||||
|
||||
//Fields
|
||||
for (let key in data[0]) {
|
||||
csvData.push(key);
|
||||
csvData.push(",");
|
||||
}
|
||||
csvData.pop();
|
||||
csvData.push("\r\n");
|
||||
//Data
|
||||
data.map(function (item) {
|
||||
for (let key in item) {
|
||||
let escapedCSV = item[key] + "";
|
||||
// Escaped CSV data to string to avoid problems with numbers or other types of values
|
||||
// this is controlled by the prop escapeCsv
|
||||
if (_self.escapeCsv) {
|
||||
escapedCSV = '="' + escapedCSV + '"'; // cast Numbers to string
|
||||
if (escapedCSV.match(/[,"\n]/)) {
|
||||
escapedCSV = '"' + escapedCSV.replace(/\"/g, '""') + '"';
|
||||
}
|
||||
}
|
||||
csvData.push(escapedCSV);
|
||||
csvData.push(",");
|
||||
}
|
||||
csvData.pop();
|
||||
csvData.push("\r\n");
|
||||
});
|
||||
//Footer
|
||||
if (this.footer != null) {
|
||||
csvData.push(this.parseExtraData(this.footer, "${data}\r\n"));
|
||||
}
|
||||
return csvData.join("");
|
||||
},
|
||||
/*
|
||||
getProcessedJson
|
||||
---------------
|
||||
Get only the data to export, if no fields are set return all the data
|
||||
*/
|
||||
getProcessedJson(data, header) {
|
||||
let keys = this.getKeys(data, header);
|
||||
let newData = [];
|
||||
let _self = this;
|
||||
data.map(function (item, index) {
|
||||
let newItem = {};
|
||||
for (let label in keys) {
|
||||
let property = keys[label];
|
||||
newItem[label] = _self.getValue(property, item);
|
||||
}
|
||||
newData.push(newItem);
|
||||
});
|
||||
|
||||
return newData;
|
||||
},
|
||||
getKeys(data, header) {
|
||||
if (header) {
|
||||
return header;
|
||||
}
|
||||
|
||||
let keys = {};
|
||||
for (let key in data[0]) {
|
||||
keys[key] = key;
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
/*
|
||||
parseExtraData
|
||||
---------------
|
||||
Parse title and footer attribute to the csv format
|
||||
*/
|
||||
parseExtraData(extraData, format) {
|
||||
let parseData = "";
|
||||
if (Array.isArray(extraData)) {
|
||||
for (var i = 0; i < extraData.length; i++) {
|
||||
if (extraData[i])
|
||||
parseData += format.replace("${data}", extraData[i]);
|
||||
}
|
||||
} else {
|
||||
parseData += format.replace("${data}", extraData);
|
||||
}
|
||||
return parseData;
|
||||
},
|
||||
|
||||
getValue(key, item) {
|
||||
const field = typeof key !== "object" ? key : key.field;
|
||||
let indexes = typeof field !== "string" ? [] : field.split(".");
|
||||
let value = this.defaultValue;
|
||||
|
||||
if (!field) value = item;
|
||||
else if (indexes.length > 1)
|
||||
value = this.getValueFromNestedItem(item, indexes);
|
||||
else value = this.parseValue(item[field]);
|
||||
|
||||
if (key.hasOwnProperty("callback"))
|
||||
value = this.getValueFromCallback(value, key.callback);
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
/*
|
||||
convert values with newline \n characters into <br/>
|
||||
*/
|
||||
valueReformattedForMultilines(value) {
|
||||
if (typeof value == "string") return value.replace(/\n/gi, "<br/>");
|
||||
else return value;
|
||||
},
|
||||
preprocessLongNum(value) {
|
||||
if (this.stringifyLongNum) {
|
||||
if (String(value).startsWith("0x")) {
|
||||
return value;
|
||||
}
|
||||
if (!isNaN(value) && value != "") {
|
||||
if (value > 99999999999 || value < 0.0000000000001) {
|
||||
return '="' + value + '"';
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
getValueFromNestedItem(item, indexes) {
|
||||
let nestedItem = item;
|
||||
for (let index of indexes) {
|
||||
if (nestedItem) nestedItem = nestedItem[index];
|
||||
}
|
||||
return this.parseValue(nestedItem);
|
||||
},
|
||||
|
||||
getValueFromCallback(item, callback) {
|
||||
if (typeof callback !== "function") return this.defaultValue;
|
||||
const value = callback(item);
|
||||
return this.parseValue(value);
|
||||
},
|
||||
parseValue(value) {
|
||||
return value || value === 0 || typeof value === "boolean"
|
||||
? value
|
||||
: this.defaultValue;
|
||||
},
|
||||
base64ToBlob(data, mime) {
|
||||
let base64 = window.btoa(window.unescape(encodeURIComponent(data)));
|
||||
let bstr = atob(base64);
|
||||
let n = bstr.length;
|
||||
let u8arr = new Uint8ClampedArray(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
return new Blob([u8arr], { type: mime });
|
||||
},
|
||||
}, // end methods
|
||||
};
|
||||
|
||||
function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier
|
||||
/* server only */
|
||||
, shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
|
||||
if (typeof shadowMode !== 'boolean') {
|
||||
createInjectorSSR = createInjector;
|
||||
createInjector = shadowMode;
|
||||
shadowMode = false;
|
||||
} // Vue.extend constructor export interop.
|
||||
|
||||
|
||||
var options = typeof script === 'function' ? script.options : script; // render functions
|
||||
|
||||
if (template && template.render) {
|
||||
options.render = template.render;
|
||||
options.staticRenderFns = template.staticRenderFns;
|
||||
options._compiled = true; // functional template
|
||||
|
||||
if (isFunctionalTemplate) {
|
||||
options.functional = true;
|
||||
}
|
||||
} // scopedId
|
||||
|
||||
|
||||
if (scopeId) {
|
||||
options._scopeId = scopeId;
|
||||
}
|
||||
|
||||
var hook;
|
||||
|
||||
if (moduleIdentifier) {
|
||||
// server build
|
||||
hook = function hook(context) {
|
||||
// 2.3 injection
|
||||
context = context || // cached call
|
||||
this.$vnode && this.$vnode.ssrContext || // stateful
|
||||
this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; // functional
|
||||
// 2.2 with runInNewContext: true
|
||||
|
||||
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
|
||||
context = __VUE_SSR_CONTEXT__;
|
||||
} // inject component styles
|
||||
|
||||
|
||||
if (style) {
|
||||
style.call(this, createInjectorSSR(context));
|
||||
} // register component module identifier for async chunk inference
|
||||
|
||||
|
||||
if (context && context._registeredComponents) {
|
||||
context._registeredComponents.add(moduleIdentifier);
|
||||
}
|
||||
}; // used by ssr in case component is cached and beforeCreate
|
||||
// never gets called
|
||||
|
||||
|
||||
options._ssrRegister = hook;
|
||||
} else if (style) {
|
||||
hook = shadowMode ? function () {
|
||||
style.call(this, createInjectorShadow(this.$root.$options.shadowRoot));
|
||||
} : function (context) {
|
||||
style.call(this, createInjector(context));
|
||||
};
|
||||
}
|
||||
|
||||
if (hook) {
|
||||
if (options.functional) {
|
||||
// register for functional component in vue file
|
||||
var originalRender = options.render;
|
||||
|
||||
options.render = function renderWithStyleInjection(h, context) {
|
||||
hook.call(context);
|
||||
return originalRender(h, context);
|
||||
};
|
||||
} else {
|
||||
// inject component registration as beforeCreate hook
|
||||
var existing = options.beforeCreate;
|
||||
options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
|
||||
}
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
var normalizeComponent_1 = normalizeComponent;
|
||||
|
||||
/* script */
|
||||
const __vue_script__ = script;
|
||||
|
||||
/* template */
|
||||
var __vue_render__ = function() {
|
||||
var _vm = this;
|
||||
var _h = _vm.$createElement;
|
||||
var _c = _vm._self._c || _h;
|
||||
return _c(
|
||||
"div",
|
||||
{ attrs: { id: _vm.idName }, on: { click: _vm.generate } },
|
||||
[_vm._t("default", [_vm._v(" Download " + _vm._s(_vm.name) + " ")])],
|
||||
2
|
||||
)
|
||||
};
|
||||
var __vue_staticRenderFns__ = [];
|
||||
__vue_render__._withStripped = true;
|
||||
|
||||
/* style */
|
||||
const __vue_inject_styles__ = undefined;
|
||||
/* scoped */
|
||||
const __vue_scope_id__ = undefined;
|
||||
/* module identifier */
|
||||
const __vue_module_identifier__ = undefined;
|
||||
/* functional template */
|
||||
const __vue_is_functional_template__ = false;
|
||||
/* style inject */
|
||||
|
||||
/* style inject SSR */
|
||||
|
||||
|
||||
|
||||
var JsonExcel = normalizeComponent_1(
|
||||
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
|
||||
__vue_inject_styles__,
|
||||
__vue_script__,
|
||||
__vue_scope_id__,
|
||||
__vue_is_functional_template__,
|
||||
__vue_module_identifier__,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
return JsonExcel;
|
||||
|
||||
})));
|
BIN
node_modules/_vue-json-excel@0.3.0@vue-json-excel/example-multi-line.png
generated
vendored
Executable file
BIN
node_modules/_vue-json-excel@0.3.0@vue-json-excel/example-multi-line.png
generated
vendored
Executable file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
1
node_modules/_vue-json-excel@0.3.0@vue-json-excel/node_modules/downloadjs
generated
vendored
Symbolic link
1
node_modules/_vue-json-excel@0.3.0@vue-json-excel/node_modules/downloadjs
generated
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../_downloadjs@1.4.7@downloadjs
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "vue-json-excel",
|
||||
"version": "0.3.0",
|
||||
"description": "Download your JSON as an excel or CSV file directly from the browser",
|
||||
"main": "dist/vue-json-excel.umd.js",
|
||||
"module": "dist/vue-json-excel.esm.js",
|
||||
"scripts": {
|
||||
"build:dist": "rollup -c ./rollup.config.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jecovier/vue-json-excel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vue",
|
||||
"vuejs",
|
||||
"vue2",
|
||||
"Excel",
|
||||
"xls",
|
||||
"csv",
|
||||
"json",
|
||||
"export",
|
||||
"json excel",
|
||||
"download",
|
||||
"component"
|
||||
],
|
||||
"author": "Jose Javier Espinoza",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jecovier/vue-json-excel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/jecovier/vue-json-excel#readme",
|
||||
"dependencies": {
|
||||
"downloadjs": "^1.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rollup": "^1.7.4",
|
||||
"rollup-plugin-commonjs": "^9.2.2",
|
||||
"rollup-plugin-node-resolve": "^4.0.1",
|
||||
"rollup-plugin-vue": "^4.7.2",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"__npminstall_done": "Thu Jun 03 2021 11:07:53 GMT+0800 (中国标准时间)",
|
||||
"_from": "vue-json-excel@0.3.0",
|
||||
"_resolved": "https://registry.nlark.com/vue-json-excel/download/vue-json-excel-0.3.0.tgz"
|
||||
}
|
27
node_modules/_vue-json-excel@0.3.0@vue-json-excel/rollup.config.js
generated
vendored
Executable file
27
node_modules/_vue-json-excel@0.3.0@vue-json-excel/rollup.config.js
generated
vendored
Executable file
|
@ -0,0 +1,27 @@
|
|||
import vue from 'rollup-plugin-vue';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
|
||||
export default {
|
||||
input: 'JsonExcel.vue',
|
||||
output: [
|
||||
{
|
||||
format: 'cjs',
|
||||
file: 'dist/vue-json-excel.cjs.js'
|
||||
},
|
||||
{
|
||||
format: 'esm',
|
||||
file: 'dist/vue-json-excel.esm.js'
|
||||
},
|
||||
{
|
||||
name: 'JsonExcel',
|
||||
format: 'umd',
|
||||
file: 'dist/vue-json-excel.umd.js'
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
vue(),
|
||||
commonjs(),
|
||||
resolve()
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
_downloadjs@1.4.7@downloadjs
|
|
@ -0,0 +1 @@
|
|||
_vue-json-excel@0.3.0@vue-json-excel
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"vue-json-excel": "^0.3.0"
|
||||
}
|
||||
}
|
|
@ -3,21 +3,49 @@ import { getRequest, postRequest, postRequestWithNoForm, putRequest, deleteReque
|
|||
|
||||
|
||||
// 获取店铺直播间列表
|
||||
export const getLivesList = (params) => {
|
||||
export const getLiveList = (params) => {
|
||||
return getRequest('/broadcast/studio', params)
|
||||
}
|
||||
// 添加直播间
|
||||
export const addLive = (params) => {
|
||||
return postRequest('/broadcast/studio', params)
|
||||
}
|
||||
|
||||
// 获取直播间详情
|
||||
export const getLiveInfo = (studioId) => {
|
||||
return getRequest(`/broadcast/studio/studioInfo/${studioId}`)
|
||||
}
|
||||
|
||||
// 修改直播间
|
||||
export const editLive = (params) => {
|
||||
return putRequest('/broadcast/studio/edit', params)
|
||||
}
|
||||
|
||||
// 获取店铺直播商品
|
||||
export const getLiveGoods = (params) => {
|
||||
return getRequest('/broadcast/commodity', params)
|
||||
}
|
||||
|
||||
// 店铺直播间删除商品
|
||||
export const delLiveGoods = (goodsId) => {
|
||||
return deleteRequest(`/broadcast/commodity/${goodsId}`)
|
||||
}
|
||||
|
||||
// 直播间删除商品
|
||||
export const delRoomLiveGoods = (roomId,liveGoodsId) => {
|
||||
return deleteRequest(`/broadcast/studio/deleteInRoom/${roomId}/${liveGoodsId}`)
|
||||
}
|
||||
|
||||
// 添加店铺直播商品
|
||||
export const addLiveGoods = (params) => {
|
||||
export const addLiveStoreGoods = (params) => {
|
||||
return postRequestWithNoForm('/broadcast/commodity', params)
|
||||
}
|
||||
|
||||
// 店铺直播间添加
|
||||
export const addLiveGoods = (params) => {
|
||||
|
||||
return putRequest(`/broadcast/studio/push/${params.roomId}/${params.liveGoodsId}`)
|
||||
}
|
||||
|
||||
// 获取拼团列表
|
||||
export const getPintuanList = (params) => {
|
||||
|
|
|
@ -22,10 +22,10 @@ export default {
|
|||
// buyer: 'https://buyer-api.pickmall.cn',
|
||||
// seller: 'https://store-api.pickmall.cn',
|
||||
// manager: 'https://admin-api.pickmall.cn'
|
||||
common: 'http://192.168.0.103:8890',
|
||||
buyer: 'http://192.168.0.103:8888',
|
||||
seller: 'http://192.168.0.103:8889',
|
||||
manager: 'http://192.168.0.103:8887'
|
||||
common: 'http://192.168.0.109:8890',
|
||||
buyer: 'http://192.168.0.109:8888',
|
||||
seller: 'http://192.168.0.109:8889',
|
||||
manager: 'http://192.168.0.109:8887'
|
||||
},
|
||||
api_prod: {
|
||||
common: 'https://common-api.pickmall.cn',
|
||||
|
|
|
@ -718,6 +718,9 @@ export default {
|
|||
});
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.init();
|
||||
},
|
||||
activated () {
|
||||
this.init();
|
||||
},
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<template>
|
||||
<div>
|
||||
<Card>
|
||||
<Card style="position:relative;">
|
||||
<Spin size="large" fix v-if="spinShow"></Spin>
|
||||
<Alert type="warning">
|
||||
创建直播
|
||||
|
||||
<template slot="desc">
|
||||
为了方便在创建直播间时从选择商品,请尽量提前提审直播商品
|
||||
</template>
|
||||
|
@ -10,21 +11,23 @@
|
|||
|
||||
<Form :model="liveForm" ref="liveForm" :rules="liveRulesForm" :label-width="120">
|
||||
<FormItem label="直播标题" prop="name">
|
||||
<Input v-model="liveForm.name" style="width:460px"></Input>
|
||||
<Input :disabled="liveStatus!='NEW'" v-model="liveForm.name" style="width:460px"></Input>
|
||||
<div class="tips">直播间名字,最短3个汉字,最长17个汉字,1个汉字相当于2个字符</div>
|
||||
</FormItem>
|
||||
<FormItem label="主播昵称" prop="anchorName">
|
||||
<Input v-model="liveForm.anchorName" style="width:360px"></Input>
|
||||
<Input :disabled="liveStatus!='NEW'" v-model="liveForm.anchorName" style="width:360px"></Input>
|
||||
<div class="tips">主播昵称,最短2个汉字,最长15个汉字,1个汉字相当于2个字符</div>
|
||||
</FormItem>
|
||||
<FormItem label="直播时间" prop="startTime">
|
||||
|
||||
<DatePicker format="yyyy-MM-dd HH:mm" type="datetimerange" @on-change="handleChangeTime" :options="optionsTime" placeholder="直播计划开始时间-直播计划结束时间" style="width: 300px"></DatePicker>
|
||||
<div class="tips">直播开播时间需要在当前时间的10分钟后 并且 开始时间不能在 6 个月后</div>
|
||||
<DatePicker :disabled="liveStatus!='NEW'" format="yyyy-MM-dd HH:mm" type="datetimerange" v-model="times" @on-change="handleChangeTime" :options="optionsTime" placeholder="直播计划开始时间-直播计划结束时间"
|
||||
style="width: 300px">
|
||||
</DatePicker>
|
||||
<div class="tips">直播开播时间需要在当前时间的10分钟后并且,开始时间不能在6个月后,直播计划结束时间(开播时间和结束时间间隔不得短于30分钟,不得超过24小时)</div>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="主播微信号" prop="anchorWechat">
|
||||
<Input v-model="liveForm.anchorWechat" style="width:360px" placeholder="主播微信号"></Input>
|
||||
<Input :disabled="liveStatus!='NEW'" v-model="liveForm.anchorWechat" style="width:360px" placeholder="主播微信号"></Input>
|
||||
<div class="tips">主播微信号,如果未实名认证,需要先前往“小程序直播”小程序进行<a target="_black" href="https://res.wx.qq.com/op_res/9rSix1dhHfK4rR049JL0PHJ7TpOvkuZ3mE0z7Ou_Etvjf-w1J_jVX0rZqeStLfwh">实名验证</a></div>
|
||||
</FormItem>
|
||||
|
||||
|
@ -34,15 +37,14 @@
|
|||
<template>
|
||||
<img :src="liveForm.feedsImg">
|
||||
<div class="upload-list-cover">
|
||||
<Icon type="ios-eye-outline" @click.native="handleView(item.name)"></Icon>
|
||||
<Icon type="ios-trash-outline" @click.native="handleRemove(item)"></Icon>
|
||||
<Icon type="ios-eye-outline" @click.native="handleView(liveForm.feedsImg)"></Icon>
|
||||
<Icon type="ios-trash-outline" @click.native="handleRemove('feedsImg')"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
<Upload v-if="liveForm.feedsImg.length ==0" ref="upload" :show-upload-list="false" :on-success="handleFeedsImgSuccess" :default-file-list="defaultImgList" :format="['jpg','jpeg','png']"
|
||||
:on-format-error="handleFormatError" :max-size="1024" :on-exceeded-size="handleMaxSize" :before-upload="handleBeforeUpload" multiple type="drag" :action="action" :headers="accessToken"
|
||||
style="display: inline-block;width:58px;">
|
||||
:on-format-error="handleFormatError" :max-size="1024" :on-exceeded-size="handleMaxSize" type="drag" :action="action" :headers="accessToken" style="display: inline-block;width:58px;">
|
||||
<div style="width: 58px;height:58px;line-height: 58px;">
|
||||
<Icon type="ios-camera" size="20"></Icon>
|
||||
</div>
|
||||
|
@ -59,28 +61,77 @@
|
|||
<template>
|
||||
<img :src="liveForm.coverImg">
|
||||
<div class="upload-list-cover">
|
||||
<Icon type="ios-eye-outline" @click.native="handleView(item)"></Icon>
|
||||
<Icon type="ios-trash-outline" @click.native="handleRemove(item,'coverImg')"></Icon>
|
||||
<Icon type="ios-eye-outline" @click.native="handleView(liveForm.coverImg)"></Icon>
|
||||
<Icon type="ios-trash-outline" @click.native="handleRemove('coverImg')"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<Upload v-if="liveForm.coverImg.length ==0" ref="upload" :show-upload-list="false" :on-success="handleCoverImgSuccess" :default-file-list="defaultImgList" :format="['jpg','jpeg','png']"
|
||||
:on-format-error="handleFormatError" :max-size="2048" :on-exceeded-size="handleMaxSize" :before-upload="handleBeforeUpload" multiple type="drag" :action="action" :headers="accessToken"
|
||||
style="display: inline-block;width:58px;">
|
||||
:on-format-error="handleFormatError" :max-size="2048" :on-exceeded-size="handleMaxSize" type="drag" :action="action" :headers="accessToken" style="display: inline-block;width:58px;">
|
||||
<div style="width: 58px;height:58px;line-height: 58px;">
|
||||
<Icon type="ios-camera" size="20"></Icon>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="tips"> 直播间背景图,图片规则:建议像素1080*1920,大小不超过2M</div>
|
||||
</FormItem>
|
||||
<FormItem label="商品" >
|
||||
<Button type="primary" ghost icon="md-add">添加商品</Button>
|
||||
<Table class="goods-table" :columns="liveColumns" :data="liveData"></Table>
|
||||
|
||||
<!-- 直播间背景墙 -->
|
||||
<FormItem label="直播间分享图" prop="shareImg">
|
||||
|
||||
<div class="upload-list" v-if="liveForm.shareImg">
|
||||
<template>
|
||||
<img :src="liveForm.shareImg">
|
||||
<div class="upload-list-cover">
|
||||
<Icon type="ios-eye-outline" @click.native="handleView(liveForm.shareImg)"></Icon>
|
||||
<Icon type="ios-trash-outline" @click.native="handleRemove('shareImg')"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<Upload v-if="liveForm.shareImg.length ==0" ref="upload" :show-upload-list="false" :on-success="handleShareImgSuccess" :default-file-list="defaultImgList" :format="['jpg','jpeg','png']"
|
||||
:on-format-error="handleFormatError" :max-size="2048" :on-exceeded-size="handleMaxSize" type="drag" :action="action" :headers="accessToken" style="display: inline-block;width:58px;">
|
||||
<div style="width: 58px;height:58px;line-height: 58px;">
|
||||
<Icon type="ios-camera" size="20"></Icon>
|
||||
</div>
|
||||
</Upload>
|
||||
<div class="tips"> 直播间分享图,图片规则:建议像素800*640,大小不超过1M</div>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="商品" v-if="$route.query.id">
|
||||
<Button type="primary" :disabled="liveStatus!='NEW'" ghost @click="liveGoodsVisible=true" icon="md-add">添加商品</Button>
|
||||
<Table class="goods-table" :columns="liveColumns" :data="liveData">
|
||||
<template slot-scope="{ row,index }" slot="goodsName">
|
||||
<div class="flex-goods">
|
||||
<Badge v-if="index == 0 || index ==1" color="volcano"></Badge>
|
||||
<img class="thumbnail" :src="row.thumbnail || row.goodsImage">
|
||||
{{ row.goodsName || row.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template slot-scope="{ row }" class="price" slot="price">
|
||||
<div>
|
||||
<div v-if="row.priceType == 1">{{row.price | unitPrice('¥')}}</div>
|
||||
<div v-if="row.priceType == 2">{{row.price | unitPrice('¥')}}至{{row.price2 | unitPrice('¥')}}</div>
|
||||
<div v-if="row.priceType == 3">{{row.price | unitPrice('¥')}}<span class="original-price">{{row.price2 | unitPrice('¥')}}</span></div>
|
||||
</div>
|
||||
</template>
|
||||
<template slot-scope="{ row }" slot="quantity">
|
||||
<div>{{row.quantity}}</div>
|
||||
</template>
|
||||
<template slot-scope="{ row,index }" slot="action">
|
||||
<div class="action">
|
||||
<Button size="small" type="primary" :disabled="liveStatus!='NEW'" @click="deleteGoods(row,index)">删除</Button>
|
||||
<Button size="small" ghost type="primary" :disabled="liveStatus!='NEW'" @click="onMove(row.id,1)">上移</Button>
|
||||
<Button size="small" ghost type="primary" :disabled="liveStatus!='NEW'" @click="onMove(row.id,0)">下移</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Table>
|
||||
<div class="tips">
|
||||
直播间商品中前两个商品将自动被选为封面,伴随直播间在直播列表中显示
|
||||
</div>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button type="primary" @click="createLives()">保存</Button>
|
||||
<Button style="margin-left: 8px">取消</Button>
|
||||
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Card>
|
||||
|
@ -88,19 +139,37 @@
|
|||
<Modal title="查看图片" v-model="imageVisible">
|
||||
<img :src="imageSrc" v-if="imageVisible" style="width: 100%">
|
||||
</Modal>
|
||||
|
||||
<Modal width="800" v-model="liveGoodsVisible" @on-ok="addGoods">
|
||||
<liveGoods :init="liveData" @selectedGoods="callBackData" reviewed />
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uploadFile } from "@/api/index";
|
||||
import { getLiveGoods } from "@/api/promotion";
|
||||
import {
|
||||
addLive,
|
||||
addLiveGoods,
|
||||
editLive,
|
||||
getLiveInfo,
|
||||
delLiveGoods,
|
||||
delRoomLiveGoods,
|
||||
} from "@/api/promotion";
|
||||
import liveGoods from "./liveGoods";
|
||||
export default {
|
||||
components: {
|
||||
liveGoods,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
spinShow: false,
|
||||
liveGoodsVisible: false, //选择商品
|
||||
imageVisible: false, //查看图片的dailog
|
||||
imageSrc: "", //查看图片的路径
|
||||
action: uploadFile, // 上传地址
|
||||
accessToken: {}, // 验证token
|
||||
liveStatus: "NEW", //当前直播状态
|
||||
// 不能选择今天以前的时间
|
||||
optionsTime: {
|
||||
disabledDate(date) {
|
||||
|
@ -124,7 +193,6 @@ export default {
|
|||
{
|
||||
required: true,
|
||||
message: "请输入开始时间以及结束时间",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
feedsImg: [
|
||||
|
@ -133,38 +201,149 @@ export default {
|
|||
coverImg: [
|
||||
{ required: true, message: "直播间背景墙不能为空", trigger: "blur" },
|
||||
],
|
||||
shareImg: [
|
||||
{ required: true, message: "直播间分享图不能为空", trigger: "blur" },
|
||||
],
|
||||
},
|
||||
liveForm: {
|
||||
name: "", //直播标题
|
||||
anchorName: "", //主播昵称
|
||||
anchorWechat: "", //主播微信号
|
||||
feedsImg: "", //分享卡片封面
|
||||
coverImg: "", //直播间背景墙
|
||||
shareImg: "", //分享图
|
||||
startTime: "",
|
||||
},
|
||||
|
||||
times: [], //接收直播时间数据
|
||||
// 直播商品表格表头
|
||||
liveColumns: [
|
||||
{
|
||||
title: "商品",
|
||||
slot: "goodsName",
|
||||
},
|
||||
{
|
||||
title: "价格",
|
||||
slot: "price",
|
||||
},
|
||||
{
|
||||
title: "库存",
|
||||
},
|
||||
{
|
||||
title: "链接",
|
||||
slot: "quantity",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
slot: "action",
|
||||
width: 250,
|
||||
},
|
||||
],
|
||||
liveData: [], //直播商品集合
|
||||
commodityList: "", //商品集合
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
/**
|
||||
* 如果query.id有值说明是查看详情
|
||||
* liveStatus 可以判断当前直播状态 从而区分数据 是否是未开始、已开启、已关闭、
|
||||
*/
|
||||
if (this.$route.query.id) {
|
||||
// 获取直播间详情
|
||||
this.getLiveDetail();
|
||||
}
|
||||
this.accessToken = {
|
||||
accessToken: this.getStore("accessToken"),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 删除直播间商品
|
||||
*/
|
||||
async deleteGoods(val, index) {
|
||||
let res = await delRoomLiveGoods(this.liveForm.roomId, val.liveGoodsId);
|
||||
if (res.success) {
|
||||
this.$Message.success("删除成功!");
|
||||
this.liveData.splice(index, 1);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取直播间详情
|
||||
*/
|
||||
async getLiveDetail() {
|
||||
let result = await getLiveInfo(this.$route.query.id);
|
||||
|
||||
// 将数据回调到liveform里面
|
||||
if (result.success) {
|
||||
let data = result.result;
|
||||
for (let key in data) {
|
||||
this.liveForm[key] = data[key];
|
||||
}
|
||||
// 将选择的商品回调给表格
|
||||
|
||||
this.liveData = data.commodityList;
|
||||
this.commodityList = data.commodityList;
|
||||
|
||||
// 将时间格式化
|
||||
this.$set(
|
||||
this.times,
|
||||
[0],
|
||||
this.$options.filters.unixToDate(data.startTime, "yyyy-MM-dd hh:mm")
|
||||
);
|
||||
this.$set(
|
||||
this.times,
|
||||
[1],
|
||||
this.$options.filters.unixToDate(data.endTime, "yyyy-MM-dd hh:mm")
|
||||
);
|
||||
this.liveStatus = data.status;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 上下移动功能
|
||||
* dir 1为上 0为下
|
||||
*/
|
||||
onMove(code, dir) {
|
||||
let moveComm = (curIndex, nextIndex) => {
|
||||
let arr = this.liveData;
|
||||
arr[curIndex] = arr.splice(nextIndex, 1, arr[curIndex])[0];
|
||||
return arr;
|
||||
};
|
||||
this.liveData.some((val, index) => {
|
||||
if (val.id === code) {
|
||||
if (dir === 1 && index === 0) {
|
||||
this.$message.Warning("已在顶部!");
|
||||
} else if (dir === 0 && index === this.liveData.length - 1) {
|
||||
this.$message.Warning("已在底部!");
|
||||
} else {
|
||||
let nextIndex = dir === 1 ? index - 1 : index + 1;
|
||||
this.liveData = moveComm(index, nextIndex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 回调的商品选择数据
|
||||
*/
|
||||
callBackData(way) {
|
||||
this.$set(this, "liveData", way);
|
||||
},
|
||||
|
||||
/**
|
||||
* dialog点击确定时判断
|
||||
*/
|
||||
addGoods() {
|
||||
this.liveData.forEach((item) => {
|
||||
this.commodityList.forEach((oldVal) => {
|
||||
if (oldVal.liveGoodsId != item.liveGoodsId) {
|
||||
addLiveGoods({
|
||||
roomId: this.$route.query.roomId,
|
||||
liveGoodsId: item.liveGoodsId,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 上传图片查看图片
|
||||
*/
|
||||
|
@ -176,11 +355,11 @@ export default {
|
|||
/**
|
||||
* 删除上传的图片
|
||||
*/
|
||||
handleRemove(val, type) {
|
||||
if (type == "coverImg") {
|
||||
this.liveForm.coverImg = "";
|
||||
handleRemove(type) {
|
||||
if (this.liveStatus == "NEW") {
|
||||
this.liveForm[type] = "";
|
||||
} else {
|
||||
this.liveForm.feedsImg = "";
|
||||
this.$Message.error("当前状态禁止修改删除!");
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
@ -189,6 +368,13 @@ export default {
|
|||
handleCoverImgSuccess(res) {
|
||||
this.liveForm.coverImg = res.result;
|
||||
},
|
||||
/**
|
||||
* 直播间分享图上传成功回调
|
||||
*/
|
||||
handleShareImgSuccess(res) {
|
||||
console.log(res);
|
||||
this.liveForm.shareImg = res.result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 分享卡片封面上传成功回调
|
||||
|
@ -201,15 +387,24 @@ export default {
|
|||
* 直播间背景图
|
||||
*/
|
||||
handleCoverImgSuccess(res) {
|
||||
this.liveForm.coverImg.push(res.result);
|
||||
this.liveForm.coverImg = res.result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择时间后的回调
|
||||
*/
|
||||
handleChangeTime(daterange) {
|
||||
this.liveForm.startTime = new Date(daterange[0]).getTime() / 1000;
|
||||
this.liveForm.endTime = new Date(daterange[1]).getTime() / 1000;
|
||||
this.times = daterange;
|
||||
this.$set(
|
||||
this.liveForm,
|
||||
"startTime",
|
||||
new Date(daterange[0]).getTime() / 1000
|
||||
);
|
||||
this.$set(
|
||||
this.liveForm,
|
||||
"endTime",
|
||||
new Date(daterange[1]).getTime() / 1000
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -235,8 +430,8 @@ export default {
|
|||
/**
|
||||
* 限制只能上传一张图片
|
||||
*/
|
||||
handleBeforeUpload() {
|
||||
const check = this.liveForm.feedsImg.length < 1;
|
||||
handleBeforeUpload(type) {
|
||||
const check = this.liveForm[type].length < 1;
|
||||
if (!check) {
|
||||
this.$Notice.warning({
|
||||
title: "最多上传一张图片",
|
||||
|
@ -244,18 +439,77 @@ export default {
|
|||
}
|
||||
return check;
|
||||
},
|
||||
createLives() {},
|
||||
|
||||
/**
|
||||
* 添加直播间 /broadcast/studio/edit
|
||||
*/
|
||||
createLives() {
|
||||
this.$refs["liveForm"].validate((valid) => {
|
||||
if (valid) {
|
||||
// 需判断当前是否是添加商品
|
||||
if (this.$route.query.id && this.liveData.length != 0) {
|
||||
this.spinShow = true;
|
||||
this.liveForm.commodityList = JSON.stringify(this.liveForm.commodityList);
|
||||
// 将当前直播间修改
|
||||
editLive(this.liveForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("修改成功!");
|
||||
|
||||
this.$router.push({ path: "/storePromotion/live" });
|
||||
}
|
||||
this.spinShow = false;
|
||||
});
|
||||
} else {
|
||||
// 此处为创建直播
|
||||
this.spinShow = true;
|
||||
addLive(this.liveForm).then((res) => {
|
||||
if (res.success) {
|
||||
this.$Message.success("添加成功!");
|
||||
|
||||
this.$router.push({ path: "/storePromotion/live" });
|
||||
}
|
||||
this.spinShow = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.action {
|
||||
display: flex;
|
||||
/deep/ .ivu-btn {
|
||||
margin: 0 5px !important;
|
||||
}
|
||||
}
|
||||
.original-price {
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.thumbnail {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 0.4em;
|
||||
}
|
||||
.flex-goods {
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
> img {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
.goods-table {
|
||||
width: 800px;
|
||||
width: 1000px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.upload-list {
|
||||
|
|
|
@ -2,43 +2,40 @@
|
|||
<div>
|
||||
<Card>
|
||||
<Form ref="searchForm" :model="searchForm" inline :label-width="100" class="search-form">
|
||||
<Form-item label="优惠券名称">
|
||||
<Input type="text" v-model="searchForm.couponName" placeholder="请输入优惠券名称" clearable style="width: 200px" />
|
||||
</Form-item>
|
||||
<Form-item label="活动状态" prop="promotionStatus">
|
||||
<Select v-model="searchForm.promotionStatus" placeholder="请选择" clearable style="width: 200px">
|
||||
|
||||
<Form-item label="直播状态" prop="promotionStatus">
|
||||
<Select v-model="searchForm.status" placeholder="请选择" clearable style="width: 200px">
|
||||
<Option value="NEW">未开始</Option>
|
||||
<Option value="START">已开始/上架</Option>
|
||||
<Option value="END">已结束/下架</Option>
|
||||
<Option value="CLOSE">紧急关闭/作废</Option>
|
||||
<Option value="START">直播中</Option>
|
||||
<Option value="END">已结束</Option>
|
||||
|
||||
</Select>
|
||||
</Form-item>
|
||||
<Form-item label="活动时间">
|
||||
<DatePicker v-model="selectDate" type="daterange" clearable placeholder="选择起始时间" style="width: 200px"></DatePicker>
|
||||
</Form-item>
|
||||
|
||||
<Button @click="handleSearch" type="primary" class="search-btn" icon="ios-search">搜索</Button>
|
||||
</Form>
|
||||
<div class="btns">
|
||||
<Button @click="createLive()" type="primary">创建直播</Button>
|
||||
</div>
|
||||
<Tabs>
|
||||
<Tabs v-model="searchForm.status">
|
||||
<!-- 标签栏 -->
|
||||
<TabPane v-for="(item,index) in tabs" :key="index" :label="item.title">
|
||||
<Table :columns="liveColumns" :data="liveData"></Table>
|
||||
<Row type="flex" justify="end" class="page">
|
||||
<Page :current="searchForm.pageNumber + 1" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]"
|
||||
size="small" show-total show-elevator show-sizer></Page>
|
||||
</Row>
|
||||
<TabPane v-for="(item,index) in tabs" :key="index" :name="item.status" :label="item.title">
|
||||
|
||||
</TabPane>
|
||||
|
||||
</Tabs>
|
||||
<Table :columns="liveColumns" :data="liveData"></Table>
|
||||
<Row type="flex" justify="end" class="page">
|
||||
<Page :current="searchForm.pageNumber" :total="total" :page-size="searchForm.pageSize" @on-change="changePageNumber" @on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]"
|
||||
size="small" show-total show-elevator show-sizer></Page>
|
||||
</Row>
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getLivesList } from "@/api/promotion.js";
|
||||
import { getLiveList } from "@/api/promotion.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
@ -48,87 +45,181 @@ export default {
|
|||
searchForm: {
|
||||
pageSize: 10,
|
||||
pageNumber: 1,
|
||||
status: "NEW",
|
||||
},
|
||||
// 直播tab选项栏
|
||||
tabs: [
|
||||
{
|
||||
title: "全部",
|
||||
},
|
||||
{
|
||||
title: "直播中",
|
||||
status: "START",
|
||||
},
|
||||
{
|
||||
title: "未开始",
|
||||
status: "NEW",
|
||||
},
|
||||
{
|
||||
title: "已结束",
|
||||
status: "END",
|
||||
},
|
||||
],
|
||||
liveColumns: [
|
||||
{
|
||||
title: "直播标题",
|
||||
key: "a",
|
||||
},
|
||||
{
|
||||
title: "直播时间",
|
||||
key: "a",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "主播昵称",
|
||||
key: "a",
|
||||
key: "anchorName",
|
||||
},
|
||||
{
|
||||
title: "直播开始时间",
|
||||
key: "createTime",
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"span",
|
||||
|
||||
this.$options.filters.unixToDate(params.row.startTime)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "直播结束时间",
|
||||
key: "endTime",
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"span",
|
||||
|
||||
this.$options.filters.unixToDate(params.row.endTime)
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "直播状态",
|
||||
key: "a",
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"span",
|
||||
params.row.status == "NEW"
|
||||
? "未开始"
|
||||
: params.row.status == "START"
|
||||
? "直播中"
|
||||
: "已结束"
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "a",
|
||||
key: "action",
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
style: {
|
||||
display: "flex",
|
||||
},
|
||||
},
|
||||
[
|
||||
h(
|
||||
"Button",
|
||||
{
|
||||
props: {
|
||||
type: "error",
|
||||
size: "small",
|
||||
},
|
||||
style: {
|
||||
marginRight: "5px",
|
||||
},
|
||||
on: {
|
||||
click: () => {
|
||||
this.getLiveDetail(params.row);
|
||||
},
|
||||
},
|
||||
},
|
||||
"查看/添加商品"
|
||||
),
|
||||
]
|
||||
);
|
||||
},
|
||||
},
|
||||
], //table中显示的title
|
||||
liveData: [], //table中显示的直播数据
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
"searchForm.status": {
|
||||
handler() {
|
||||
this.liveData = [];
|
||||
this.getStoreLives();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getStoreLives();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 搜索直播间状态
|
||||
*/
|
||||
handleSearch() {
|
||||
this.getStoreLives();
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面数据大小分页回调
|
||||
*/
|
||||
changePageSize(val) {
|
||||
console.log(val)
|
||||
this.searchForm.pageSize = val;
|
||||
this.getStoreLives();
|
||||
},
|
||||
/**
|
||||
* 分页回调
|
||||
*/
|
||||
changePageNumber(val) {
|
||||
console.log(val)
|
||||
this.searchForm.pageNumber = val;
|
||||
this.getStoreLives();
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取店铺直播间列表
|
||||
*/
|
||||
async getStoreLives() {
|
||||
let result = await getLivesList();
|
||||
let result = await getLiveList(this.searchForm);
|
||||
if (result.success) {
|
||||
this.liveData = result.records;
|
||||
this.liveData = result.result.records;
|
||||
this.total = result.result.total;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取
|
||||
* 获取直播间详情
|
||||
*/
|
||||
|
||||
getLiveDetail(val) {
|
||||
this.$router.push({
|
||||
path: "/add-live",
|
||||
query: { ...val, liveStatus: this.searchForm.status },
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 创建直播
|
||||
*/
|
||||
createLive(){
|
||||
this.$router.push({path:'/add-live'})
|
||||
createLive() {
|
||||
this.$router.push({ path: "/add-live" });
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击分页数据回调
|
||||
*/
|
||||
changePageSize() {},
|
||||
/**
|
||||
* 点击分页回调
|
||||
*/
|
||||
changePage() {},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/table-common.scss";
|
||||
.btns{
|
||||
.btns {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.page {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,72 +1,102 @@
|
|||
<template>
|
||||
<div class="wrapper">
|
||||
<Card>
|
||||
<Alert banner type="warning">
|
||||
<Form ref="searchForm" :model="params" inline :label-width="100" class="search-form">
|
||||
<Form-item label="商品名称">
|
||||
<Input type="text" v-model="params.name" placeholder="请输入商品名称" clearable style="width: 200px" />
|
||||
</Form-item>
|
||||
|
||||
<template slot="desc">
|
||||
由于直播商品需经过小程序直播平台的审核,你需要在此先提审商品,为了不影响直播间选取商品,请提前1天提审商品;
|
||||
每次最多可提审20款商品,每个店铺最多可维护200款商品,每款SPU只需选择其中一款SKU即可;
|
||||
</template>
|
||||
|
||||
</Alert>
|
||||
<Button @click="getLiveGoodsMethods('clear')" type="primary" class="search-btn" icon="ios-search">搜索</Button>
|
||||
</Form>
|
||||
<h4 v-if="!reviewed">
|
||||
由于直播商品需经过小程序直播平台的审核,你需要在此先提审商品,为了不影响直播间选取商品,请提前1天提审商品;
|
||||
</h4>
|
||||
|
||||
<div>
|
||||
<Tabs :value="params.auditStatus">
|
||||
<Tabs v-model="params.auditStatus">
|
||||
<TabPane v-for="(item,index) in liveTabWay" :key="index" :label="item.label" :name="item.type">
|
||||
|
||||
</TabPane>
|
||||
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<Button type="primary" style="margin-bottom:10px;" @click="addNewLiveGoods" icon="md-add">选择商品</Button>
|
||||
<Button v-if="!reviewed" type="primary" style="margin-bottom:10px;" @click="addNewLiveGoods" icon="md-add">选择商品</Button>
|
||||
<Button type="primary" v-if="params.auditStatus == 0" ghost style="margin:0 0 10px 10px" @click="getLiveGoodsMethods('clear')">更新状态</Button>
|
||||
<div style="position:relative">
|
||||
<Spin size="large" fix v-if="tableLoading">
|
||||
</Spin>
|
||||
<Table disabled-hover :columns="liveGoodsColumns" :data="liveGoodsData">
|
||||
|
||||
<Table disabled-hover :columns="liveGoodsColumns" :data="liveGoodsData">
|
||||
<template slot-scope="{ row }" slot="goodsName">
|
||||
<div class="flex-goods">
|
||||
<img class="thumbnail" :src="row.thumbnail">
|
||||
{{ row.goodsName }}
|
||||
</div>
|
||||
</template>
|
||||
<template slot-scope="{ row }" slot="price">
|
||||
<RadioGroup v-model="row.priceType">
|
||||
<div class="price-item">
|
||||
<Radio label="1">一口价:</Radio> <Input v-if="row.priceType == '1'" style="width:100px" v-model="row.price"></Input>
|
||||
<template slot-scope="{ row }" slot="goodsName">
|
||||
<div class="flex-goods">
|
||||
<img class="thumbnail" :src="row.thumbnail || row.goodsImage">
|
||||
{{ row.goodsName || row.name }}
|
||||
</div>
|
||||
<div class="price-item">
|
||||
<Radio label="2">区间价:</Radio> <span v-if="row.priceType == '2'"><Input style="width:100px" v-model="row.price" />至<Input style="width:100px" v-model="row.price2" /></span>
|
||||
</div>
|
||||
<div class="price-item">
|
||||
<Radio label="3">折扣价:</Radio> <span v-if="row.priceType == '3'">现价<Input style="width:100px" v-model="row.price"></Input>原价<Input style="width:100px" v-model="row.price2" /></span>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</template>
|
||||
</template>
|
||||
<template slot-scope="{ row ,index }" class="price" slot="price">
|
||||
<!-- 如果为新增商品显示 -->
|
||||
|
||||
<template slot-scope="{ row,index }" slot="action">
|
||||
<Button type="primary" @click="()=>{liveGoodsData.splice(index,1)}">删除</Button>
|
||||
</template>
|
||||
</Table>
|
||||
<RadioGroup v-if="params.auditStatus == 99" @on-change="changeRadio(row,'priceType')" v-model="row.priceType">
|
||||
<div class="price-item">
|
||||
<Radio :label="1">一口价:</Radio>
|
||||
<InputNumber :min="0.1" v-if="liveGoodsData[index].priceType == 1" style="width:100px" v-model="liveGoodsData[index].price"></InputNumber>
|
||||
</div>
|
||||
<div class="price-item">
|
||||
<Radio :label="2">区间价:</Radio> <span v-if="liveGoodsData[index].priceType == 2">
|
||||
<InputNumber :min="0.1" style="width:100px" v-model="liveGoodsData[index].price" />至
|
||||
<InputNumber :min="0.1" style="width:100px" v-model="liveGoodsData[index].price2" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="price-item">
|
||||
<Radio :label="3">折扣价:</Radio> <span v-if="liveGoodsData[index].priceType == 3">原价<InputNumber :min="0.1" style="width:100px" v-model="liveGoodsData[index].price"></InputNumber>现价
|
||||
<InputNumber :min="0.1" style="width:100px" v-model="liveGoodsData[index].price2" />
|
||||
</span>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div v-else>
|
||||
<div v-if="row.priceType == 1">{{row.price | unitPrice('¥')}}</div>
|
||||
<div v-if="row.priceType == 2">{{row.price | unitPrice('¥')}}至{{row.price2 | unitPrice('¥')}}</div>
|
||||
<div v-if="row.priceType == 3">{{row.price | unitPrice('¥')}}<span class="original-price">{{row.price2 | unitPrice('¥')}}</span></div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<template slot-scope="{ row,index }" slot="action">
|
||||
<Button v-if="params.auditStatus == 99" type="primary" @click="()=>{liveGoodsData.splice(index,1)}">删除</Button>
|
||||
<Button v-if="params.auditStatus != 99 && !reviewed" ghost type="primary" @click="()=>{$router.push({path:'/goods-operation-edit',query:{id:row.goodsId}})}">查看</Button>
|
||||
<Button v-if="reviewed" :type="row.__selected ? 'primary' : ''" @click="selectedLiveGoods(row,index)">{{row.__selected ? '已':''}}选择</Button>
|
||||
</template>
|
||||
</Table>
|
||||
<div class="flex">
|
||||
<Page size="small" :total="goodsTotal" @on-change="changePageNumber" class="pageration" @on-page-size-change="changePageSize" :page-size="params.pageSize" show-total show-elevator
|
||||
show-sizer>
|
||||
</Page>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<sku-select ref="skuSelect" @selectedGoodsData="selectedGoodsData"></sku-select>
|
||||
<div class="submit">
|
||||
<Button type="primary" @click="saveLiveGoods">保存商品</Button>
|
||||
<div v-if="params.auditStatus == 99" class="submit">
|
||||
<Button type="primary" :loading="saveGoodsLoading" @click="saveLiveGoods">保存商品</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import skuSelect from "@/views/lili-dialog"; //选择商品组件
|
||||
import { addLiveGoods } from "@/api/promotion.js";
|
||||
import { addLiveStoreGoods, getLiveGoods } from "@/api/promotion.js";
|
||||
export default {
|
||||
components: {
|
||||
skuSelect,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
goodsTotal: "", //商品总数
|
||||
saveGoodsLoading: false, //保存商品加载
|
||||
tableLoading: false, //表格是否加载
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
auditStatus: 0,
|
||||
auditStatus: 2, //商品状态
|
||||
},
|
||||
// 商品审核状态
|
||||
liveTabWay: [
|
||||
|
@ -74,14 +104,16 @@ export default {
|
|||
label: "待提审",
|
||||
type: 0,
|
||||
},
|
||||
{
|
||||
label: "审核中",
|
||||
type: 1,
|
||||
},
|
||||
{
|
||||
label: "已审核",
|
||||
type: 2,
|
||||
},
|
||||
|
||||
{
|
||||
label: "审核中",
|
||||
type: 1,
|
||||
},
|
||||
|
||||
{
|
||||
label: "审核未通过",
|
||||
type: 3,
|
||||
|
@ -112,33 +144,161 @@ export default {
|
|||
],
|
||||
// 表格商品详情
|
||||
liveGoodsData: [],
|
||||
selectedGoods: [],
|
||||
};
|
||||
},
|
||||
props: {
|
||||
// 是否是已审核,此处为组件模式时使用。去除添加等功能 只保留查询以及新增选择回调数据
|
||||
reviewed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 初始化信息,此处为组件模式时使用。父级将数据传输到此方法上
|
||||
init: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
//此处为组件模式时使用 监听此处为开启则需要删除tab上面的数据只显示已审核
|
||||
reviewed: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.liveTabWay = this.liveTabWay.filter((item) => {
|
||||
return item.label == "已审核";
|
||||
});
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
//此处为组件模式时使用 监听父级给传值
|
||||
init: {
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => {
|
||||
// 将当前父级返回的数据和当前数据进行匹配
|
||||
this.selectedGoods = val;
|
||||
this.liveGoodsData.forEach((item, index) => {
|
||||
val.forEach((callback) => {
|
||||
if (item.id == callback.id) {
|
||||
this.$set(this.liveGoodsData[index], "__selected", true);
|
||||
// this.selectedGoods.push(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
// 监听如果次数变化说明用户再点击tab
|
||||
"params.auditStatus": {
|
||||
handler(val) {
|
||||
this.liveGoodsData = [];
|
||||
if (val != 99) {
|
||||
this.params.pageNumber = 1;
|
||||
this.getLiveGoodsMethods();
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 回调参数补充
|
||||
*/
|
||||
selectedLiveGoods(val, index) {
|
||||
if (!val.__selected) {
|
||||
val.__selected = true;
|
||||
this.$set(this.liveGoodsData[index], "__selected", true);
|
||||
this.selectedGoods.push(this.liveGoodsData[index]);
|
||||
} else {
|
||||
this.$nextTick(() => {
|
||||
val.__selected = false;
|
||||
|
||||
this.$set(this.liveGoodsData[index], "__selected", true);
|
||||
this.selectedGoods.splice(index, 1);
|
||||
});
|
||||
}
|
||||
|
||||
this.$emit("selectedGoods", this.selectedGoods);
|
||||
},
|
||||
/**
|
||||
* 解决radio数据不回显问题
|
||||
*/
|
||||
changeRadio(val) {
|
||||
this.$set(this.liveGoodsData[val._index], "priceType", val.priceType);
|
||||
},
|
||||
/**
|
||||
* 页面数据大小分页回调
|
||||
*/
|
||||
changePageSize(val) {
|
||||
this.params.pageSize = val;
|
||||
this.getLiveGoodsMethods("clear");
|
||||
},
|
||||
/**
|
||||
* 分页回调
|
||||
*/
|
||||
changePageNumber(val) {
|
||||
this.params.pageNumber = val;
|
||||
this.getLiveGoodsMethods("clear");
|
||||
},
|
||||
/**
|
||||
* 清除新增的tab
|
||||
*/
|
||||
clearNewLiveTab() {
|
||||
this.liveTabWay.map((item, index) => {
|
||||
return item.type == 99 && this.liveTabWay.splice(index, 1);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询商品
|
||||
*/
|
||||
async getLiveGoodsMethods(type) {
|
||||
this.tableLoading = true;
|
||||
|
||||
let result = await getLiveGoods(this.params);
|
||||
if (result.success) {
|
||||
// 将表格数据清除
|
||||
if (type == "clear") {
|
||||
this.liveGoodsData = [];
|
||||
}
|
||||
this.liveGoodsData.push(...result.result.records);
|
||||
this.goodsTotal = result.result.total;
|
||||
}
|
||||
this.tableLoading = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存直播商品
|
||||
*/
|
||||
async saveLiveGoods() {
|
||||
// this.$Spin.show();
|
||||
this.saveGoodsLoading = true;
|
||||
let submit = this.liveGoodsData.map((element) => {
|
||||
console.log(element.priceType);
|
||||
return {
|
||||
goodsId: element.goodsId, //商品id
|
||||
goodsImage: element.small, //商品图片
|
||||
name: element.goodsName, //商品昵称
|
||||
price: parseInt(element.price), //商品价格
|
||||
quantity: element.quantity, //库存
|
||||
price2: element.price2 ? parseInt(element.price2) : "", //商品价格
|
||||
priceType: element.priceType, // priceType Number 是 价格类型,1:一口价(只需要传入price,price2不传) 2:价格区间(price字段为左边界,price2字段为右边界,price和price2必传) 3:显示折扣价(price字段为原价,price2字段为现价, price和price2必传)
|
||||
skuId: element.id,
|
||||
url: `/pages/product/goods?id=${element.id}&goodsId=${element.goodsId}`, //小程序地址
|
||||
url: `pages/product/goods?id=${element.id}&goodsId=${element.goodsId}`, //小程序地址
|
||||
};
|
||||
});
|
||||
|
||||
let result = await addLiveGoods(submit);
|
||||
let result = await addLiveStoreGoods(submit);
|
||||
if (result.success) {
|
||||
this.$Message.success({
|
||||
content: `添加成功!`,
|
||||
});
|
||||
|
||||
this.params.auditStatus = 0;
|
||||
}
|
||||
this.saveGoodsLoading = false;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -146,11 +306,8 @@ export default {
|
|||
*/
|
||||
selectedGoodsData(goods) {
|
||||
goods.map((item) => {
|
||||
return (item.priceType = "1");
|
||||
return (item.priceType = 1);
|
||||
});
|
||||
this.liveTabWay.map((item,index)=>{
|
||||
return item.type == 99 && this.liveTabWay.splice(index,1)
|
||||
})
|
||||
|
||||
this.liveGoodsData.push(...goods);
|
||||
},
|
||||
|
@ -159,18 +316,32 @@ export default {
|
|||
* 新增商品
|
||||
*/
|
||||
addNewLiveGoods() {
|
||||
this.clearNewLiveTab();
|
||||
this.liveTabWay.push({
|
||||
type:99,
|
||||
label:"新增商品"
|
||||
})
|
||||
this.params.auditStatus = 99
|
||||
type: 99,
|
||||
label: "新增商品",
|
||||
});
|
||||
this.$set(this, "liveGoodsData", []);
|
||||
this.params.auditStatus = 99;
|
||||
this.$refs.skuSelect.open("goods");
|
||||
this.$refs.skuSelect.goodsData = JSON.parse(
|
||||
JSON.stringify(this.liveGoodsData)
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/table-common.scss";
|
||||
.search-form {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -206,4 +377,20 @@ export default {
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.original-price {
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 10px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #ddd;
|
||||
background-color: #f8f8f8;
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
line-height: 40px;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,29 +2,12 @@
|
|||
<div class="seckill">
|
||||
<Card>
|
||||
<Row>
|
||||
<Form
|
||||
ref="searchForm"
|
||||
:model="searchForm"
|
||||
inline
|
||||
:label-width="70"
|
||||
class="search-form"
|
||||
>
|
||||
<Form ref="searchForm" :model="searchForm" inline :label-width="70" class="search-form">
|
||||
<Form-item label="活动名称" prop="goodsName">
|
||||
<Input
|
||||
type="text"
|
||||
v-model="searchForm.promotionName"
|
||||
placeholder="请输入活动名称"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
<Input type="text" v-model="searchForm.promotionName" placeholder="请输入活动名称" clearable style="width: 200px" />
|
||||
</Form-item>
|
||||
<Form-item label="活动状态" prop="promotionStatus">
|
||||
<Select
|
||||
v-model="searchForm.promotionStatus"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<Select v-model="searchForm.promotionStatus" placeholder="请选择" clearable style="width: 200px">
|
||||
<Option value="NEW">未开始</Option>
|
||||
<Option value="START">已开始/上架</Option>
|
||||
<Option value="END">已结束/下架</Option>
|
||||
|
@ -32,13 +15,7 @@
|
|||
</Select>
|
||||
</Form-item>
|
||||
<Form-item label="活动时间">
|
||||
<DatePicker
|
||||
v-model="selectDate"
|
||||
type="daterange"
|
||||
clearable
|
||||
placeholder="选择起始时间"
|
||||
style="width: 200px"
|
||||
></DatePicker>
|
||||
<DatePicker v-model="selectDate" type="daterange" clearable placeholder="选择起始时间" style="width: 200px"></DatePicker>
|
||||
</Form-item>
|
||||
<Button @click="handleSearch" type="primary" icon="ios-search" class="search-btn">搜索</Button>
|
||||
<Button @click="handleReset" class="ml_10">重置</Button>
|
||||
|
@ -46,14 +23,7 @@
|
|||
</Row>
|
||||
|
||||
<Row class="padding-row">
|
||||
<Table
|
||||
:loading="loading"
|
||||
border
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
ref="table"
|
||||
sortable="custom"
|
||||
>
|
||||
<Table :loading="loading" border :columns="columns" :data="data" ref="table" sortable="custom">
|
||||
<template slot-scope="{ row }" slot="applyEndTime">
|
||||
{{ unixDate(row.applyEndTime) }}
|
||||
</template>
|
||||
|
@ -63,36 +33,14 @@
|
|||
}}</Tag>
|
||||
</template>
|
||||
<template slot-scope="{ row }" slot="action">
|
||||
<Button
|
||||
v-if="row.promotionStatus === 'NEW'"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="manage(row)"
|
||||
>管理</Button
|
||||
>
|
||||
<Button
|
||||
v-else
|
||||
type="info"
|
||||
size="small"
|
||||
@click="manage(row)"
|
||||
>查看</Button
|
||||
>
|
||||
<Button v-if="row.promotionStatus === 'NEW'" type="primary" size="small" @click="manage(row)">管理</Button>
|
||||
<Button v-else type="info" size="small" @click="manage(row)">查看</Button>
|
||||
</template>
|
||||
</Table>
|
||||
</Row>
|
||||
<Row type="flex" justify="end" class="page">
|
||||
<Page
|
||||
:current="searchForm.pageNumber + 1"
|
||||
:total="total"
|
||||
:page-size="searchForm.pageSize"
|
||||
@on-change="changePage"
|
||||
@on-page-size-change="changePageSize"
|
||||
:page-size-opts="[10, 20, 50]"
|
||||
size="small"
|
||||
show-total
|
||||
show-elevator
|
||||
show-sizer
|
||||
></Page>
|
||||
<Page :current="searchForm.pageNumber + 1" :total="total" :page-size="searchForm.pageSize" @on-change="changePage" @on-page-size-change="changePageSize" :page-size-opts="[10, 20, 50]"
|
||||
size="small" show-total show-elevator show-sizer></Page>
|
||||
</Row>
|
||||
</Card>
|
||||
</div>
|
||||
|
@ -193,8 +141,8 @@ export default {
|
|||
this.getDataList();
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = {}
|
||||
this.selectDate = ''
|
||||
this.searchForm = {};
|
||||
this.selectDate = "";
|
||||
this.searchForm.pageNumber = 0;
|
||||
this.searchForm.pageSize = 10;
|
||||
this.getDataList();
|
||||
|
@ -234,9 +182,12 @@ export default {
|
|||
return hourArr;
|
||||
},
|
||||
},
|
||||
activated () {
|
||||
activated() {
|
||||
this.init();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -174,7 +174,8 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
total: "0", // 订单总数
|
||||
orderType: [ // tab切换状态
|
||||
orderType: [
|
||||
// tab切换状态
|
||||
{
|
||||
title: "订单",
|
||||
selected: true,
|
||||
|
@ -195,13 +196,15 @@ export default {
|
|||
TAKE: "已完成",
|
||||
},
|
||||
|
||||
serviceTypeList: { // 服务类型
|
||||
serviceTypeList: {
|
||||
// 服务类型
|
||||
CANCEL: "取消",
|
||||
RETURN_GOODS: "退货",
|
||||
EXCHANGE_GOODS: "换货",
|
||||
RETURN_MONEY: "退款",
|
||||
},
|
||||
serviceStatusList: { // 服务类型
|
||||
serviceStatusList: {
|
||||
// 服务类型
|
||||
APPLY: "申请售后",
|
||||
PASS: "通过售后",
|
||||
REFUSE: "拒绝售后",
|
||||
|
@ -220,7 +223,8 @@ export default {
|
|||
|
||||
columns: [], // 定退单title
|
||||
|
||||
orderColumns: [ // 订单表头
|
||||
orderColumns: [
|
||||
// 订单表头
|
||||
{
|
||||
type: "expand",
|
||||
width: 50,
|
||||
|
@ -271,7 +275,8 @@ export default {
|
|||
},
|
||||
},
|
||||
],
|
||||
refundColumns: [ // 退货单表头
|
||||
refundColumns: [
|
||||
// 退货单表头
|
||||
{
|
||||
type: "expand",
|
||||
width: 50,
|
||||
|
@ -333,7 +338,8 @@ export default {
|
|||
key: "applyRefundPrice",
|
||||
render: (h, params) => {
|
||||
return h(
|
||||
"div", this.$options.filters.unitPrice(params.row.applyRefundPrice, '¥')
|
||||
"div",
|
||||
this.$options.filters.unitPrice(params.row.applyRefundPrice, "¥")
|
||||
);
|
||||
},
|
||||
},
|
||||
|
@ -403,20 +409,20 @@ export default {
|
|||
orderParams: {
|
||||
searchType: "LAST_SEVEN", // TODAY , YESTERDAY , LAST_SEVEN , LAST_THIRTY
|
||||
year: "",
|
||||
storeId: JSON.parse(Cookies.get("userInfo")).id || '',
|
||||
storeId: JSON.parse(Cookies.get("userInfo")).id || "",
|
||||
memberId: "",
|
||||
},
|
||||
// 订单概念
|
||||
overViewParams: {
|
||||
month: "",
|
||||
searchType: "LAST_SEVEN", // TODAY , YESTERDAY , LAST_SEVEN , LAST_THIRTY
|
||||
storeId: JSON.parse(Cookies.get("userInfo")).id || '',
|
||||
storeId: JSON.parse(Cookies.get("userInfo")).id || "",
|
||||
year: "",
|
||||
},
|
||||
defaultParams: {
|
||||
month: "",
|
||||
searchType: "LAST_SEVEN", // TODAY , YESTERDAY , LAST_SEVEN , LAST_THIRTY
|
||||
storeId: JSON.parse(Cookies.get("userInfo")).id || '',
|
||||
storeId: JSON.parse(Cookies.get("userInfo")).id || "",
|
||||
year: "",
|
||||
},
|
||||
|
||||
|
@ -426,7 +432,7 @@ export default {
|
|||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
searchType: "LAST_SEVEN",
|
||||
storeId:JSON.parse(Cookies.get("userInfo")).id || '',
|
||||
storeId: JSON.parse(Cookies.get("userInfo")).id || "",
|
||||
year: "",
|
||||
},
|
||||
|
||||
|
@ -478,34 +484,29 @@ export default {
|
|||
// 默认已经加载 legend-filter 交互
|
||||
let data = this.chartList;
|
||||
|
||||
this.orderChart.data(data);
|
||||
this.orderChart.scale({
|
||||
finalPrice: {
|
||||
range: [0, 1],
|
||||
nice: true,
|
||||
},
|
||||
data.forEach((item) => {
|
||||
item.createTime = item.createTime.split(" ")[0];
|
||||
item.title = "交易额";
|
||||
});
|
||||
this.orderChart.data(data);
|
||||
|
||||
this.orderChart.tooltip({
|
||||
showCrosshairs: true,
|
||||
shared: true,
|
||||
});
|
||||
this.orderChart.axis("finalPrice", {
|
||||
label: {
|
||||
formatter: (val) => {
|
||||
return val + "元";
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
this.orderChart
|
||||
.line()
|
||||
.position("createTime*price")
|
||||
|
||||
.label("price")
|
||||
.color("title")
|
||||
.shape("smooth");
|
||||
|
||||
this.orderChart
|
||||
.point()
|
||||
.position("createTime*price")
|
||||
|
||||
.label("price")
|
||||
.color("title")
|
||||
.shape("circle")
|
||||
.style({
|
||||
stroke: "#fff",
|
||||
|
@ -528,7 +529,6 @@ export default {
|
|||
const res = await API_Goods.getOrderOverView(this.overViewParams);
|
||||
if (res.success) {
|
||||
this.overViewList = res.result;
|
||||
console.log(res.result);
|
||||
}
|
||||
},
|
||||
// 实例化订单图表
|
||||
|
@ -598,9 +598,9 @@ export default {
|
|||
background: $theme_color;
|
||||
}
|
||||
}
|
||||
.breadcrumb{
|
||||
span{
|
||||
cursor: pointer;
|
||||
.breadcrumb {
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,8 @@ export default {
|
|||
uvs: "", // 访客数
|
||||
pvs: "", // 浏览量
|
||||
|
||||
dateList: [ // 日期选择列表
|
||||
dateList: [
|
||||
// 日期选择列表
|
||||
{
|
||||
title: "今天",
|
||||
selected: false,
|
||||
|
@ -118,6 +119,8 @@ export default {
|
|||
watch: {
|
||||
params: {
|
||||
handler(val) {
|
||||
this.uvs = 0;
|
||||
this.pvs = 0;
|
||||
this.init();
|
||||
},
|
||||
deep: true,
|
||||
|
|
Loading…
Reference in New Issue