!26 接口层兼容

Merge pull request !26 from Bluemark/vue3_tmp
pull/28/head
芋道源码 2023-12-13 03:17:05 +00:00 committed by Gitee
commit 82407aa87c
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
28 changed files with 3467 additions and 3409 deletions

20
.env
View File

@ -1,20 +0,0 @@
# 版本号
SHOPRO_VERSION = v1.8.3
# 正式环境接口域名
SHOPRO_BASE_URL = https://api.shopro.sheepjs.com
# 开发环境接口域名
SHOPRO_DEV_BASE_URL = https://api.shopro.sheepjs.com
# 开发环境运行端口
SHOPRO_DEV_PORT = 3000
# 接口地址前缀
SHOPRO_API_PATH = /shop/api/
# 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀
SHOPRO_STATIC_URL = https://file.sheepjs.com
# 是否开启直播 1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启)
SHOPRO_MPLIVE_ON = 0

42
LICENSE
View File

@ -1,21 +1,21 @@
MIT License
Copyright (c) 2022 lidongtony
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.
MIT License
Copyright (c) 2022 lidongtony
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.

View File

@ -148,11 +148,20 @@
});
if (res.code === 0) {
//
let obj2 = {
2: '折扣',
1: '满减'
}
let obj = {
1: '可用',
2: '已用',
3: '过期'
}
let obj3 = {
1: '已领取',
2: '已使用',
3: '已过期'
}
res.data.list = res.data.list.map(item => {
return {
...item,
@ -160,7 +169,8 @@
amount: (item.discountPrice / 100).toFixed(2),
use_start_time: sheep.$helper.timeFormat(item.validStartTime, 'yyyy-mm-dd hh:MM:ss'),
use_end_time: sheep.$helper.timeFormat(item.validEndTime, 'yyyy-mm-dd hh:MM:ss'),
status_text: obj[item.status]
status_text: obj[item.status],
type_text: obj2[item.discountType]
}
});
if (page >= 2) {

View File

@ -1,171 +1,165 @@
<template>
<su-fixed bottom placeholder bg="bg-white">
<view class="ui-tabbar-box">
<view class="ui-tabbar ss-flex ss-col-center ss-row-between">
<view
v-if="collectIcon"
class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
@tap="onFavorite"
>
<block v-if="modelValue.favorite">
<image
class="item-icon"
:src="sheep.$url.static('/static/img/shop/goods/collect_1.gif')"
mode="aspectFit"
></image>
<view class="item-title">已收藏</view>
</block>
<block v-else>
<image
class="item-icon"
:src="sheep.$url.static('/static/img/shop/goods/collect_0.png')"
mode="aspectFit"
></image>
<view class="item-title">收藏</view>
</block>
</view>
<view
v-if="serviceIcon"
class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
@tap="onChat"
>
<image
class="item-icon"
:src="sheep.$url.static('/static/img/shop/goods/message.png')"
mode="aspectFit"
></image>
<view class="item-title">客服</view>
</view>
<view
v-if="shareIcon"
class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
@tap="showShareModal"
>
<image
class="item-icon"
:src="sheep.$url.static('/static/img/shop/goods/share.png')"
mode="aspectFit"
></image>
<view class="item-title">分享</view>
</view>
<slot></slot>
</view>
</view>
</su-fixed>
<su-fixed bottom placeholder bg="bg-white">
<view class="ui-tabbar-box">
<view class="ui-tabbar ss-flex ss-col-center ss-row-between">
<view v-if="collectIcon" class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
@tap="onFavorite">
<block v-if="modelValue.favorite">
<image class="item-icon" :src="sheep.$url.static('/static/img/shop/goods/collect_1.gif')"
mode="aspectFit"></image>
<view class="item-title">已收藏</view>
</block>
<block v-else>
<image class="item-icon" :src="sheep.$url.static('/static/img/shop/goods/collect_0.png')"
mode="aspectFit"></image>
<view class="item-title">收藏</view>
</block>
</view>
<view v-if="serviceIcon" class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
@tap="onChat">
<image class="item-icon" :src="sheep.$url.static('/static/img/shop/goods/message.png')"
mode="aspectFit"></image>
<view class="item-title">客服</view>
</view>
<view v-if="shareIcon" class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
@tap="showShareModal">
<image class="item-icon" :src="sheep.$url.static('/static/img/shop/goods/share.png')"
mode="aspectFit"></image>
<view class="item-title">分享</view>
</view>
<slot></slot>
</view>
</view>
</su-fixed>
</template>
<script setup>
/**
*
* 底部导航
*
* @property {String} bg - 背景颜色Class
* @property {String} ui - 自定义样式Class
* @property {Boolean} noFixed - 是否定位
* @property {Boolean} topRadius - 上圆角
*
*
*/
/**
*
* 底部导航
*
* @property {String} bg - 背景颜色Class
* @property {String} ui - 自定义样式Class
* @property {Boolean} noFixed - 是否定位
* @property {Boolean} topRadius - 上圆角
*
*
*/
import { computed, reactive } from 'vue';
import sheep from '@/sheep';
import { showShareModal } from '@/sheep/hooks/useModal';
import {
computed,
reactive
} from 'vue';
import sheep from '@/sheep';
import {
showShareModal
} from '@/sheep/hooks/useModal';
//
const state = reactive({});
//
const state = reactive({});
//
const props = defineProps({
modelValue: {
type: Object,
default() {},
},
bg: {
type: String,
default: 'bg-white',
},
bgStyles: {
type: Object,
default() {},
},
ui: {
type: String,
default: '',
},
//
const props = defineProps({
modelValue: {
type: Object,
default () {},
},
bg: {
type: String,
default: 'bg-white',
},
bgStyles: {
type: Object,
default () {},
},
ui: {
type: String,
default: '',
},
noFixed: {
type: Boolean,
default: false,
},
topRadius: {
type: Number,
default: 0,
},
collectIcon: {
type: Boolean,
default: true,
},
serviceIcon: {
type: Boolean,
default: true,
},
shareIcon: {
type: Boolean,
default: true,
},
});
const elStyles = computed(() => {
return {
'border-top-left-radius': props.topRadius + 'rpx',
'border-top-right-radius': props.topRadius + 'rpx',
overflow: 'hidden',
};
});
noFixed: {
type: Boolean,
default: false,
},
topRadius: {
type: Number,
default: 0,
},
collectIcon: {
type: Boolean,
default: true,
},
serviceIcon: {
type: Boolean,
default: true,
},
shareIcon: {
type: Boolean,
default: true,
},
});
const elStyles = computed(() => {
return {
'border-top-left-radius': props.topRadius + 'rpx',
'border-top-right-radius': props.topRadius + 'rpx',
overflow: 'hidden',
};
});
const tabbarheight = (e) => {
uni.setStorageSync('tabbar', e);
};
async function onFavorite() {
const { error } = await sheep.$api.user.favorite.do(props.modelValue.id);
if (error === 0) {
if (props.modelValue.favorite) {
props.modelValue.favorite = 0;
} else {
props.modelValue.favorite = 1;
}
}
}
const tabbarheight = (e) => {
uni.setStorageSync('tabbar', e);
};
async function onFavorite() {
// const { error } = await sheep.$api.user.favorite.do(props.modelValue.id);
// if (error === 0) {
// if (props.modelValue.favorite) {
// props.modelValue.favorite = 0;
// } else {
// props.modelValue.favorite = 1;
// }
// }
let data;
if (props.modelValue.favorite) {
data = await sheep.$api.user.favorite.dos(props.modelValue.id);
} else {
data = await sheep.$api.user.favorite.do(props.modelValue.id);
}
if (data.data) {
props.modelValue.favorite = !props.modelValue.favorite;
}
}
const onChat = () => {
sheep.$router.go('/pages/chat/index', {
id: props.modelValue.id,
});
};
const onChat = () => {
sheep.$router.go('/pages/chat/index', {
id: props.modelValue.id,
});
};
</script>
<style lang="scss" scoped>
.ui-tabbar-box {
box-shadow: 0px -6px 10px 0px rgba(51, 51, 51, 0.2);
}
.ui-tabbar {
display: flex;
height: 50px;
background: #fff;
.ui-tabbar-box {
box-shadow: 0px -6px 10px 0px rgba(51, 51, 51, 0.2);
}
.detail-tabbar-item {
width: 100rpx;
.ui-tabbar {
display: flex;
height: 50px;
background: #fff;
.item-icon {
width: 40rpx;
height: 40rpx;
}
.detail-tabbar-item {
width: 100rpx;
.item-title {
font-size: 20rpx;
font-weight: 500;
line-height: 20rpx;
margin-top: 12rpx;
}
}
}
</style>
.item-icon {
width: 40rpx;
height: 40rpx;
}
.item-title {
font-size: 20rpx;
font-weight: 500;
line-height: 20rpx;
margin-top: 12rpx;
}
}
}
</style>

View File

@ -12,9 +12,7 @@
<block v-else>
<view class="detail-swiper-selector">
<!-- 商品轮播图 -->
<su-swiper class="ss-m-b-14" isPreview :list="formatGoodsSwiper(state.goodsInfo.sliderPicUrls)"
dotStyle="tag" imageMode="widthFix" dotCur="bg-mask-40" :seizeHeight="750" />
<su-swiper class="ss-m-b-14" isPreview :list="formatGoodsSwiper(state.goodsInfo.sliderPicUrls)" dotStyle="tag" imageMode="widthFix" dotCur="bg-mask-40" :seizeHeight="750" />
<!-- 价格+标题 -->
<view class="title-card detail-card ss-p-y-40 ss-p-x-20">
<view class="ss-flex ss-row-between ss-col-center ss-m-b-26">
@ -31,6 +29,8 @@
</view>
</view>
<view class="discounts-box ss-flex ss-row-between ss-m-b-28">
<!-- 满减送/限时折扣活动的提示 TODO 芋艿promos 未写 -->
<div class="tag-content">
<!-- 满减送/限时折扣活动的提示 TODO 芋艿promos 未写 -->
<div class="tag-content">
<view class="tag-box ss-flex">
@ -39,8 +39,7 @@
</view>
</view>
</div>
<!-- 优惠劵 -->
<!-- 优惠劵 -->
<view class="get-coupon-box ss-flex ss-col-center ss-m-l-20" @tap="state.showModel = true"
v-if="state.couponInfo.length">
<view class="discounts-title ss-m-r-8">领券</view>
@ -54,8 +53,8 @@
<!-- 功能卡片 -->
<view class="detail-cell-card detail-card ss-flex-col">
<detail-cell-sku v-model="state.selectedSku.goods_sku_text" :sku="state.selectedSku"
@tap="state.showSelectSku = true" />
<!-- TODO 芋艿可能暂时不考虑使用 -->
@tap="state.showSelectSku = true" />
<!-- TODO 芋艿可能暂时不考虑使用 -->
<detail-cell-service v-if="state.goodsInfo.service" v-model="state.goodsInfo.service" />
<detail-cell-params v-if="state.goodsInfo.params" v-model="state.goodsInfo.params" />
</view>
@ -87,12 +86,13 @@
<button class="ss-reset-button disabled-btn" disabled> 已售罄 </button>
</view>
</detail-tabbar>
<!-- 优惠劵弹窗 -->
<s-coupon-get v-model="state.couponInfo" :show="state.showModel" @close="state.showModel = false" @get="onGet" />
<!-- 优惠劵弹窗 -->
<s-coupon-get v-model="state.couponInfo" :show="state.showModel" @close="state.showModel = false"
@get="onGet" />
<!-- 满减送/限时折扣活动弹窗 -->
<!-- 满减送/限时折扣活动弹窗 -->
<!-- 优惠劵弹窗 -->
<s-coupon-get v-model="state.couponInfo" :show="state.showModel" @close="state.showModel = false" @get="onGet" />
<!-- 满减送/限时折扣活动弹窗 -->
<s-activity-pop v-model="state.activityInfo" :show="state.showActivityModel"
@close="state.showActivityModel = false" />
</block>
@ -104,9 +104,16 @@
import { reactive, computed } from 'vue';
import { onLoad, onPageScroll } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import CouponApi from '@/sheep/api/promotion/coupon';
import ActivityApi from '@/sheep/api/promotion/activity';
import { formatSales, formatGoodsSwiper, fen2yuan, } from '@/sheep/hooks/useGoods';
import CouponApi from '@/sheep/api/promotion/coupon';
import ActivityApi from '@/sheep/api/promotion/activity';
import {
formatSales,
formatGoodsSwiper,
fen2yuan,
} from '@/sheep/hooks/useGoods';
import CouponApi from '@/sheep/api/promotion/coupon';
import ActivityApi from '@/sheep/api/promotion/activity';
import { formatSales, formatGoodsSwiper, fen2yuan, } from '@/sheep/hooks/useGoods';
import detailNavbar from './components/detail/detail-navbar.vue';
import detailCellSku from './components/detail/detail-cell-sku.vue';
import detailCellService from './components/detail/detail-cell-service.vue';
@ -130,16 +137,17 @@
couponInfo: [], // Coupon
showActivityModel: false, // / Activity
activityInfo: [], // / Activity
activityList: [], // // Activity
});
activityList: [], // // Activity
});
//
function onSkuChange(e) {
state.selectedSku = e;
}
//
// TODO
function onAddCart(e) {
console.log(e, '加入购物车');
sheep.$store('cart').add(e);
}
@ -166,10 +174,10 @@
// TODO
async function onGet(id) {
const {
error,
code,
msg
} = await sheep.$api.coupon.get(id);
if (error === 0) {
if (code === 0) {
uni.showToast({
title: msg,
});
@ -180,6 +188,7 @@
}
// TODO
const shareInfo = computed(() => {
if (isEmpty(state.goodsInfo)) return {};
return sheep.$platform.share.getShareInfo({
@ -207,6 +216,75 @@
}
state.goodsId = options.id;
// 1.
sheep.$api.goods.detail(state.goodsId).then(async (res) => {
//
if (res.code !== 0 || !res.data) {
state.goodsInfo = null;
return;
}
//
state.skeletonLoading = false;
//
let dasa = await sheep.$api.goods.exits(options.id);
res.data.favorite = dasa.data;
state.goodsInfo = res.data;
console.log(state.goodsInfo, '商品信息');
//
// 2.
CouponApi.getCouponTemplateList({
price: state.goodsInfo.price,
spuIds: [state.goodsInfo.id],
skuIds: state.goodsInfo.skus.map(item => item.id),
//
categoryIds: [52]
}).then((res) => {
console.log(res, '优惠券信息进行对接')
if (res.code !== 0) {
return;
}
//
let obj2 = {
2: '折扣',
1: '满减'
}
let obj = {
1: '可用',
2: '已用',
3: '过期'
}
let obj3 = {
1: '已领取',
2: '已使用',
3: '已过期'
}
res.data = res.data.map(item => {
return {
...item,
enough: (item.usePrice / 100).toFixed(2),
amount: (item.discountPrice / 100).toFixed(2),
use_start_time: sheep.$helper.timeFormat(item
.validStartTime,
'yyyy-mm-dd hh:MM:ss'),
use_end_time: sheep.$helper.timeFormat(item.validEndTime,
'yyyy-mm-dd hh:MM:ss'),
status_text: obj[item.status],
type_text: obj2[item.discountType],
get_status_text: obj3[item.status],
type_text: obj2[item.discountType]
}
});
state.couponInfo = res.data;
});
});
// return;
// 3.
ActivityApi.getActivityListBySpuId(state.goodsId).then((res) => {
if (res.code !== 0) {
return;
}
state.activityList = res.data;
});
sheep.$api.goods.detail(state.goodsId).then((res) => {
//
if (res.code !== 0 || !res.data) {

View File

@ -1,41 +1,42 @@
<template>
<s-layout
title="我的"
tabbar="/pages/index/user"
navbar="custom"
:bgStyle="template.page"
:navbarStyle="template.style?.navbar"
onShareAppMessage
>
<s-block v-for="(item, index) in template.components" :key="index" :styles="item.property.style">
<s-block-item :type="item.id" :data="item.property" :styles="item.property.style" />
</s-block>
</s-layout>
<s-layout title="我的" tabbar="/pages/index/user" navbar="custom" :bgStyle="template.page"
:navbarStyle="template.style?.navbar" onShareAppMessage>
<s-block v-for="(item, index) in template.components" :key="index" :styles="item.property.style">
<s-block-item :type="item.id" :data="item.property" :styles="item.property.style" />
</s-block>
</s-layout>
</template>
<script setup>
import { computed } from 'vue';
import { onShow, onPageScroll, onPullDownRefresh } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import {
computed
} from 'vue';
import {
onShow,
onPageScroll,
onPullDownRefresh
} from '@dcloudio/uni-app';
import sheep from '@/sheep';
// tabBar
uni.hideTabBar();
// tabBar
uni.hideTabBar();
const template = computed(() => sheep.$store('app').template.user);
const isLogin = computed(() => sheep.$store('user').isLogin);
const template = computed(() => sheep.$store('app').template.user);
const isLogin = computed(() => sheep.$store('user').isLogin);
onShow(() => {
sheep.$store('user').updateUserData();
});
onShow(() => {
sheep.$store('user').updateUserData();
});
onPullDownRefresh(() => {
sheep.$store('user').updateUserData();
setTimeout(function () {
uni.stopPullDownRefresh();
}, 800);
});
onPullDownRefresh(() => {
sheep.$store('user').updateUserData();
setTimeout(function() {
uni.stopPullDownRefresh();
}, 800);
});
onPageScroll(() => {});
onPageScroll(() => {});
</script>
<style></style>
<style></style>

View File

@ -1,10 +1,9 @@
import request from '@/sheep/request';
import request2 from '@/sheep/request2';
export default {
list: (data) =>
request2({
url: 'trade/cart/list',
request({
url: '/app-api/trade/cart/list',
method: 'GET',
custom: {
showLoading: false,
@ -13,7 +12,7 @@ export default {
}),
append: (data) =>
request({
url: 'cart',
url: '/app-api/trade/cart/add',
method: 'POST',
// TODO 芋艿:这里没提示
custom: {
@ -22,18 +21,31 @@ export default {
},
data: {
...data,
type: 'inc',
// type: 'inc',
},
}),
// append: (data) =>
// request({
// url: 'cart',
// method: 'POST',
// custom: {
// showSuccess: true,
// successMsg: '已添加到购物车~',
// },
// data: {
// ...data,
// type: 'inc',
// },
// }),
// 删除购物车
delete: (ids) =>
request2({
url: 'trade/cart/delete?ids=' + ids,
request({
url: '/app-api/trade/cart/delete?ids=' + ids,
method: 'DELETE',
}),
update: (data) =>
request2({
url: 'trade/cart/update-count',
request({
url: '/app-api/trade/cart/update-count',
method: 'PUT',
data: {
...data,

View File

@ -1,10 +1,8 @@
import request2 from '@/sheep/request2';
export default {
list: (params) =>
request2({
url: 'product/category/list',
method: 'GET',
params,
}),
};
list: (params) =>
request({
url: '/app-api/product/category/list',
method: 'GET',
params,
}),
};

View File

@ -1,5 +1,4 @@
import request from '@/sheep/request';
import request2 from '@/sheep/request2';
export default {
// 我的拼团
@ -13,8 +12,8 @@ export default {
},
}),
userCoupon: (params) =>
request2({
url: 'promotion/coupon/page',
request({
url: '/app-api/promotion/coupon/page',
method: 'GET',
params,
}),
@ -34,9 +33,20 @@ export default {
}),
get: (id) =>
request({
url: 'coupon/get/' + id,
url: '/app-api/promotion/coupon/take',
method: 'POST',
data: {
templateId: id
},
params: {
templateId: id
},
}),
// get: (id) =>
// request({
// url: 'coupon/get/' + id,
// method: 'POST',
// }),
listByGoods: (id) =>
request({
url: 'coupon/listByGoods/' + id,

View File

@ -1,25 +1,24 @@
import request from '@/sheep/request';
import request2 from '@/sheep/request2';
export default {
area: () =>
request2({
url: 'system/area/tree',
method: 'GET',
}),
// area: () =>
// request({
// url: 'data/area',
// method: 'GET',
// }),
faq: () =>
request({
url: 'data/faq',
method: 'GET',
}),
richtext: (id) =>
request({
url: 'data/richtext/' + id,
method: 'GET',
}),
};
area: () =>
request({
url: '/app-api/system/area/tree',
method: 'GET',
}),
// area: () =>
// request({
// url: 'data/area',
// method: 'GET',
// }),
faq: () =>
request({
url: 'data/faq',
method: 'GET',
}),
richtext: (id) =>
request({
url: 'data/richtext/' + id,
method: 'GET',
}),
};

View File

@ -1,80 +1,84 @@
import request from '@/sheep/request';
import request2 from '@/sheep/request2';
export default {
// 商品详情
detail: (id, params = {}) =>
request2({
url: 'product/spu/get-detail?id=' + id,
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
}),
// 商品详情
detail: (id, params = {}) =>
request({
url: '/app-api/product/spu/get-detail?id=' + id,
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
}),
// 商品列表
list: (params) =>
request2({
url: 'product/spu/page',
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
}),
// 商品列表
list: (params) =>
request({
url: '/app-api/product/spu/page',
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
}),
// 商品查询
ids: (params = {}) =>
request({
url: 'goods/goods/ids',
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
}),
// 商品评价列表
comment: (id, params = {}) =>
request2({
url: 'product/comment/list?spuId=' + id,
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
}),
// 商品评价类型
getType: (id) =>
request({
url: 'goods/comment/getType/' + id,
method: 'GET',
custom: {
showLoading: false,
showError: false,
},
}),
// 活动商品查询
// 商品查询
activity: (params = {}) =>
request({
url: 'goods/goods/activity',
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
}),
activityList: (params = {}) =>
request({
url: 'goods/goods/activityList',
method: 'GET',
params,
}),
};
// 商品查询
ids: (params = {}) =>
request({
url: 'goods/goods/ids',
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
}),
// 商品评价列表
comment: (id, params = {}) =>
request({
url: '/app-api/product/comment/list?spuId=' + id,
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
}),
// 商品评价类型
getType: (id) =>
request({
url: 'goods/comment/getType/' + id,
method: 'GET',
custom: {
showLoading: false,
showError: false,
},
}),
// 活动商品查询
// 商品查询
activity: (params = {}) =>
request({
url: 'goods/goods/activity',
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
}),
activityList: (params = {}) =>
request({
url: 'goods/goods/activityList',
method: 'GET',
params,
}),
// 检查是否收藏商品
exits: (id) =>
request({
url: '/app-api/product/favorite/exits?spuId=' + id,
method: 'GET',
}),
};

View File

@ -1,14 +1,14 @@
import request2 from '@/sheep/request2';
import request from '@/sheep/request';
export default {
decorate: () =>
request2({
url: 'promotion/decorate/list?page=1',
method: 'GET',
}),
spids: () =>
request2({
url: 'product/spu/page?recommendType=best&pageNo=1&pageSize=10',
method: 'GET',
}),
};
decorate: () =>
request({
url: '/app-api/promotion/decorate/list?page=1',
method: 'GET',
}),
spids: () =>
request({
url: '/app-api/product/spu/page?recommendType=best&pageNo=1&pageSize=10',
method: 'GET',
}),
};

View File

@ -1,11 +1,10 @@
import request from '@/sheep/request';
import request2 from '@/sheep/request2';
export default {
// 订单详情
detail: (id, params) =>
request2({
url: 'trade/order/get-detail?id=' + id,
request({
url: '/app-api/trade/order/get-detail?id=' + id,
method: 'GET',
params,
}),
@ -40,8 +39,8 @@ export default {
}),
// 订单列表
list: (params) =>
request2({
url: 'trade/order/page',
request({
url: '/app-api/trade/order/page',
method: 'GET',
params,
custom: {
@ -65,16 +64,25 @@ export default {
// 解决 SpringMVC 接受 List<Item> 参数的问题
delete data2.items
for (let i = 0; i < data.items.length; i++) {
// 此处转码问题,待解决方案
data2[encodeURIComponent('items[' + i + '' + '].skuId')] = data.items[i].skuId + '';
data2[encodeURIComponent('items[' + i + '' + '].count')] = data.items[i].count + '';
if (data.items[i].cartId) {
data2[encodeURIComponent('items[' + i + '' + '].cartId')] = data.items[i].cartId + '';
}
data2[encodeURIComponent('items[' + i + '' + '].cartId')] = data.items[i].cartId + '';
// data2['items' + `[${i}]` + '.skuId'] = data.items[i].skuId + '';
// data2['items' + `[${i}]` + '.count'] = data.items[i].count + '';
// data2['items' + `[${i}]` + '.cartId'] = data.items[i].cartId + '';
// data2['items' + `%5B${i}%5D` + '.skuId'] = data.items[i].skuId + '';
// data2['items' + `%5B${i}%5D` + '.count'] = data.items[i].count + '';
// data2['items' + `%5B${i}%5D` + '.cartId'] = data.items[i].cartId + '';
}
const queryString= Object.keys(data2).map(key => key + '=' + data2[key]).join('&')
return request2({
url: `trade/order/settlement?${queryString}`,
method: 'GET'
console.log(data2, '手动转码的参数')
return request({
url: '/app-api/trade/order/settlement',
method: 'GET',
// data: data2,
params: data2
})
},
// 创建订单
@ -99,8 +107,8 @@ export default {
}),
// 评价订单
comment: (data) =>
request2({
url: 'trade/order/item/create-comment',
request({
url: '/app-api/trade/order/item/create-comment',
method: 'POST',
data,
}),
@ -138,8 +146,8 @@ export default {
data,
}),
list: (params) =>
request2({
url: 'trade/after-sale/page',
request({
url: '/app-api/trade/after-sale/page',
method: 'GET',
params,
custom: {
@ -169,8 +177,8 @@ export default {
}),
// 售后详情
detail: (id) =>
request2({
url: 'trade/after-sale/get?id=' + id,
request({
url: '/app-api/trade/after-sale/get?id=' + id,
method: 'GET',
}),
},

View File

@ -1,18 +1,19 @@
import request from '@/sheep/request';
const CommentApi = {
// 获得商品评价分页
getCommentPage: (spuId, pageNo, pageSize, type) => {
return request({
url: '/app-api/product/comment/page',
method: 'GET',
params: {
spuId,
pageNo,
pageSize,
type
},
});
},
// 获得商品评价分页
getCommentPage: (spuId, pageNo, pageSize, type) => {
return request({
url: '/app-api/product/comment/page',
method: 'GET',
params: {
spuId,
pageNo,
pageSize,
type
},
});
},
};
export default CommentApi;
export default CommentApi;

View File

@ -1,16 +1,16 @@
import request2 from '@/sheep/request2';
import request from '@/sheep/request';
const ActivityApi = {
// 获得单个商品,近期参与的每个活动
getActivityListBySpuId: (spuId) => {
return request2({
url: '/app-api/promotion/activity/list-by-spu-id',
method: 'GET',
params: {
spuId,
},
});
},
// 获得单个商品,近期参与的每个活动
getActivityListBySpuId: (spuId) => {
return request({
url: '/app-api/promotion/activity/list-by-spu-id',
method: 'GET',
params: {
spuId,
},
});
},
};
export default ActivityApi;

View File

@ -1,67 +1,69 @@
import request2 from "@/sheep/request2";
import request from "@/sheep/request";
// 拼团 API
const CombinationApi = {
// 获得拼团活动列表
getCombinationActivityList: (count) => {
return request2({
url: "promotion/combination-activity/list",
method: 'GET',
params: {count}
});
},
// 获得拼团活动列表
getCombinationActivityList: (count) => {
return request({
url: "/app-api/promotion/combination-activity/list",
method: 'GET',
params: {
count
}
});
},
// 获得拼团活动分页
getCombinationActivityPage: (params) => {
return request2({
url: "promotion/combination-activity/page",
method: 'GET',
params
});
},
// 获得拼团活动分页
getCombinationActivityPage: (params) => {
return request({
url: "/app-api/promotion/combination-activity/page",
method: 'GET',
params
});
},
// 获得拼团活动明细
getCombinationActivity: (id) => {
return request2({
url: "promotion/combination-activity/get-detail",
method: 'GET',
params: {
id
}
});
},
// 获得拼团活动明细
getCombinationActivity: (id) => {
return request({
url: "/app-api/promotion/combination-activity/get-detail",
method: 'GET',
params: {
id
}
});
},
// 获得最近 n 条拼团记录(团长发起的)
getHeadCombinationRecordList: (activityId, status, count) => {
return request2({
url: "promotion/combination-record/get-head-list",
method: 'GET',
params: {
activityId,
status,
count
}
});
},
// 获得最近 n 条拼团记录(团长发起的)
getHeadCombinationRecordList: (activityId, status, count) => {
return request({
url: "/app-api/promotion/combination-record/get-head-list",
method: 'GET',
params: {
activityId,
status,
count
}
});
},
// 获得拼团记录明细
getCombinationRecordDetail: (id) => {
return request2({
url: "promotion/combination-record/get-detail",
method: 'GET',
params: {
id
}
});
},
// 获得拼团记录明细
getCombinationRecordDetail: (id) => {
return request({
url: "/app-api/promotion/combination-record/get-detail",
method: 'GET',
params: {
id
}
});
},
// 获得拼团记录的概要信息
getCombinationRecordSummary: () => {
return request2({
url: "promotion/combination-record/get-summary",
method: 'GET',
});
}
// 获得拼团记录的概要信息
getCombinationRecordSummary: () => {
return request({
url: "/app-api/promotion/combination-record/get-summary",
method: 'GET',
});
}
}
export default CombinationApi
export default CombinationApi

View File

@ -1,33 +1,44 @@
import request2 from "@/sheep/request2";
import request from "@/sheep/request";
const SeckillApi = {
// 获得秒杀时间段列表
getSeckillConfigList: () => {
return request2({ url: 'promotion/seckill-config/list', method: 'GET' });
},
// 获得秒杀时间段列表
getSeckillConfigList: () => {
return request({
url: '/app-api/promotion/seckill-config/list',
method: 'GET'
});
},
// 获得当前秒杀活动
getNowSeckillActivity: () => {
return request2({ url: 'promotion/seckill-activity/get-now', method: 'GET' });
},
// 获得当前秒杀活动
getNowSeckillActivity: () => {
return request({
url: '/app-api/promotion/seckill-activity/get-now',
method: 'GET'
});
},
// 获得秒杀活动分页
getSeckillActivityPage: () => {
return request2({ url: 'promotion/seckill-activity/page', method: 'GET' });
},
// 获得秒杀活动分页
getSeckillActivityPage: () => {
return request({
url: '/app-api/promotion/seckill-activity/page',
method: 'GET'
});
},
/**
* 获得秒杀活动明细
* @param {number} id 秒杀活动编号
* @return {*}
*/
getSeckillActivity: (id) => {
return request2({
url: 'promotion/seckill-activity/get-detail',
method: 'GET',
params: { id }
});
}
/**
* 获得秒杀活动明细
* @param {number} id 秒杀活动编号
* @return {*}
*/
getSeckillActivity: (id) => {
return request({
url: '/app-api/promotion/seckill-activity/get-detail',
method: 'GET',
params: {
id
}
});
}
}
export default SeckillApi;
export default SeckillApi;

View File

@ -1,11 +1,10 @@
import request from '@/sheep/request';
import request2 from '@/sheep/request2';
import $platform from '@/sheep/platform';
export default {
getUnused: () =>
request2({
url: 'promotion/coupon/get-unused-count',
request({
url: '/app-api/promotion/coupon/get-unused-count',
method: 'GET',
custom: {
showLoading: false,
@ -13,8 +12,8 @@ export default {
},
}),
profile: () =>
request2({
url: 'member/user/get',
request({
url: '/app-api/member/user/get',
method: 'GET',
custom: {
showLoading: false,
@ -22,7 +21,7 @@ export default {
},
}),
balance: () =>
request2({
request({
url: '/app-api/pay/wallet/get',
method: 'GET',
custom: {
@ -30,28 +29,9 @@ export default {
auth: true,
},
}),
// profile: () =>
// request({
// url: '/user/api/user/profile',
// method: 'GET',
// custom: {
// showLoading: false,
// auth: true,
// },
// }),
// update: (data) =>
// request({
// url: '/user/api/user/update',
// method: 'POST',
// custom: {
// showSuccess: true,
// auth: true,
// },
// data,
// }),
update: (data) =>
request2({
url: 'member/user/update',
request({
url: '/app-api/member/user/update',
method: 'PUT',
custom: {
showSuccess: true,
@ -196,90 +176,48 @@ export default {
}),
address: {
// default: () =>
// request({
// url: 'user/address/default',
// method: 'GET',
// custom: {
// showError: false,
// },
// }),
default: () =>
request2({
url: 'member/address/get-default',
request({
url: '/app-api/member/address/get-default',
method: 'GET',
custom: {
showError: false,
},
}),
list: () =>
request2({
url: 'member/address/list',
request({
url: '/app-api/member/address/list',
method: 'GET',
custom: {},
}),
// list: () =>
// request({
// url: 'user/address',
// method: 'GET',
// custom: {},
// }),
create: (data) =>
request2({
url: 'member/address/create',
request({
url: '/app-api/member/address/create',
method: 'POST',
data,
custom: {
showSuccess: true,
},
}),
// create: (data) =>
// request({
// url: 'user/address',
// method: 'POST',
// data,
// custom: {
// showSuccess: true,
// },
// }),
update: (data) =>
request2({
url: 'member/address/update',
request({
url: '/app-api/member/address/update',
method: 'PUT',
data,
custom: {
showSuccess: true,
},
}),
// update: (id, data) =>
// request({
// url: 'user/address/' + id,
// method: 'PUT',
// data,
// custom: {
// showSuccess: true,
// },
// }),
detail: (id) =>
request2({
url: 'member/address/get?id=' + id,
request({
url: '/app-api/member/address/get?id=' + id,
method: 'GET',
}),
// detail: (id) =>
// request({
// url: 'user/address/' + id,
// method: 'GET',
// }),
delete: (id) =>
request2({
url: 'member/address/delete?id=' + id,
request({
url: '/app-api/member/address/delete?id=' + id,
method: 'DELETE',
}),
// delete: (id) =>
// request({
// url: 'user/address/' + id,
// method: 'DELETE',
// }),
},
invoice: {
list: () =>
@ -319,17 +257,29 @@ export default {
},
favorite: {
list: (params) =>
request2({
url: 'product/favorite/page',
request({
url: '/app-api/product/favorite/page',
method: 'GET',
params,
}),
do: (id) =>
request({
url: 'user/goodsLog/favorite',
url: '/app-api/product/favorite/create',
method: 'POST',
data: {
goods_id: id,
spuId: id,
},
custom: {
showSuccess: true,
auth: true,
},
}),
dos: (id) =>
request({
url: '/app-api/product/favorite/delete',
method: 'DELETE',
data: {
spuId: id,
},
custom: {
showSuccess: true,
@ -338,8 +288,8 @@ export default {
}),
// 取消收藏
cancel: (id) =>
request2({
url: 'product/favorite/delete-list',
request({
url: '/app-api/product/favorite/delete-list',
method: 'DELETE',
data: {
spuIds: id.split(',').map(item => item * 1),
@ -350,18 +300,6 @@ export default {
auth: true,
},
}),
// cancel: (id) =>
// request({
// url: 'user/goodsLog/favorite',
// method: 'POST',
// data: {
// goods_ids: id,
// },
// custom: {
// showSuccess: true,
// auth: true,
// },
// }),
},
view: {
list: (params) =>
@ -383,28 +321,21 @@ export default {
},
wallet: {
log: (params) =>
request2({
request({
// url: 'member/point/record/page',
url: 'pay/wallet-transaction/page',
url: '/app-api/pay/wallet-transaction/page',
method: 'GET',
params,
custom: {},
}),
log2: (params) =>
request2({
url: 'member/point/record/page',
request({
url: '/app-api/member/point/record/page',
// url: 'pay/wallet-transaction/page',
method: 'GET',
params,
custom: {},
}),
// log: (params) =>
// request({
// url: '/user/api/walletLog',
// method: 'GET',
// params,
// custom: {},
// }),
},
account: {
info: (params) =>
@ -429,18 +360,9 @@ export default {
}),
},
//数量接口
// data: () =>
// request({
// url: 'user/user/data',
// method: 'GET',
// custom: {
// showLoading: false,
// auth: true,
// },
// }),
data: () =>
request2({
url: 'trade/order/get-count',
request({
url: '/app-api/trade/order/get-count',
method: 'GET',
custom: {
showLoading: false,
@ -448,8 +370,8 @@ export default {
},
}),
data2: () =>
request2({
url: 'trade/after-sale/get-applying-count',
request({
url: '/app-api/trade/after-sale/get-applying-count',
method: 'GET',
custom: {
showLoading: false,

View File

@ -1,108 +1,104 @@
<template>
<su-popup
:show="show"
type="bottom"
round="20"
@close="emits('close')"
showClose
backgroundColor="#f2f2f2"
>
<view class="model-box">
<view class="title ss-m-t-16 ss-m-l-20 ss-flex">优惠券</view>
<scroll-view
class="model-content"
scroll-y
:scroll-with-animation="false"
:enable-back-to-top="true"
>
<view class="subtitle ss-m-l-20">可使用优惠券</view>
<view v-for="item in state.couponInfo" :key="item.id">
<s-coupon-list :data="item">
<template #default>
<button
class="ss-reset-button card-btn ss-flex ss-row-center ss-col-center"
:class="
<su-popup :show="show" type="bottom" round="20" @close="emits('close')" showClose backgroundColor="#f2f2f2">
<view class="model-box">
<view class="title ss-m-t-16 ss-m-l-20 ss-flex">优惠券</view>
<scroll-view class="model-content" scroll-y :scroll-with-animation="false" :enable-back-to-top="true">
<view class="subtitle ss-m-l-20">可使用优惠券</view>
<view v-for="item in state.couponInfo" :key="item.id">
<s-coupon-list :data="item">
<template #default>
<button class="ss-reset-button card-btn ss-flex ss-row-center ss-col-center" :class="
item.get_status != 'can_get' && item.get_status != 'can_use' ? 'boder-btn' : ''
"
@click.stop="getBuy(item.id)"
:disabled="item.get_status != 'can_get' && item.get_status != 'can_use'"
>
{{ item.get_status_text }}
</button>
</template>
</s-coupon-list>
</view>
</scroll-view>
</view>
</su-popup>
" @click.stop="getBuy(item.id)">
<!-- 此处对接领取优惠券先将限制解除 -->
<!-- :disabled="item.get_status != 'can_get' && item.get_status != 'can_use'" -->
{{ item.get_status_text }}
</button>
</template>
</s-coupon-list>
</view>
</scroll-view>
</view>
</su-popup>
</template>
<script setup>
import { computed, reactive } from 'vue';
const props = defineProps({
modelValue: {
type: Object,
default() {},
},
show: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(['get', 'close']);
const state = reactive({
couponInfo: computed(() => props.modelValue),
currentValue: -1,
couponId: '',
});
const getBuy = (id) => {
emits('get', id);
};
//
import {
computed,
reactive
} from 'vue';
const props = defineProps({
modelValue: {
type: Object,
default () {},
},
show: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(['get', 'close']);
const state = reactive({
couponInfo: computed(() => props.modelValue),
currentValue: -1,
couponId: '',
});
const getBuy = (id) => {
console.log('应该是详情页领取优惠券')
emits('get', id);
};
//
</script>
<style lang="scss" scoped>
.model-box {
height: 60vh;
.title {
font-size: 36rpx;
height: 80rpx;
font-weight: bold;
color: #333333;
}
.subtitle {
font-size: 26rpx;
font-weight: 500;
color: #333333;
}
}
.model-content {
height: 54vh;
}
.modal-footer {
width: 100%;
height: 120rpx;
background: #fff;
}
.confirm-btn {
width: 710rpx;
margin-left: 20rpx;
height: 80rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 40rpx;
color: #fff;
}
//
.card-btn {
// width: 144rpx;
padding: 0 16rpx;
height: 50rpx;
border-radius: 40rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
color: #ffffff;
font-size: 24rpx;
font-weight: 400;
}
.boder-btn {
background: linear-gradient(90deg, var(--ui-BG-Main-opacity-4), var(--ui-BG-Main-light));
color: #fff !important;
}
</style>
.model-box {
height: 60vh;
.title {
font-size: 36rpx;
height: 80rpx;
font-weight: bold;
color: #333333;
}
.subtitle {
font-size: 26rpx;
font-weight: 500;
color: #333333;
}
}
.model-content {
height: 54vh;
}
.modal-footer {
width: 100%;
height: 120rpx;
background: #fff;
}
.confirm-btn {
width: 710rpx;
margin-left: 20rpx;
height: 80rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 40rpx;
color: #fff;
}
//
.card-btn {
// width: 144rpx;
padding: 0 16rpx;
height: 50rpx;
border-radius: 40rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
color: #ffffff;
font-size: 24rpx;
font-weight: 400;
}
.boder-btn {
background: linear-gradient(90deg, var(--ui-BG-Main-opacity-4), var(--ui-BG-Main-light));
color: #fff !important;
}
</style>

View File

@ -1,13 +1,9 @@
<template>
<view class="ss-m-20" :style="{ opacity: disabled ? '0.5' : '1' }">
<view class="content">
<!-- <view
class="tag ss-flex ss-row-center"
:class="
<view class="tag ss-flex ss-row-center" :class="
data.status == 'expired' || data.status == 'used' ? 'disabled-bg-color' : 'info-bg-color'
"
>{{ data.type_text }}</view
> -->
">{{ data.type_text }}</view>
<view class="title ss-m-x-30 ss-p-t-18">
<view class="ss-flex ss-row-between">
<view class="value-text ss-flex-1 ss-m-r-10" :class="

View File

@ -55,7 +55,8 @@ const http = new Request({
method: 'GET',
header: {
Accept: 'text/json',
'Content-Type': 'application/json;charset=UTF-8',
'Content-Type': 'application/json',
// ;charset=UTF-8
platform: $platform.name,
},
// #ifdef APP-PLUS
@ -97,6 +98,7 @@ http.interceptors.request.use(
config.header['tenant-id'] = '1';
config.header['Authorization'] = 'Bearer test247';
}
// console.log(config, '看参数')
return config;
},
(error) => {

View File

@ -100,4 +100,4 @@ const cart = defineStore({
},
});
export default cart;
export default cart;

View File

@ -1,191 +1,191 @@
## 为减小组件包的大小默认组件包中不包含编辑、latex 公式等扩展功能,需要使用扩展功能的请参考下方的 插件扩展 栏的说明
## 功能介绍
- 全端支持(含 `v3、NVUE`
- 支持丰富的标签(包括 `table`、`video`、`svg` 等)
- 支持丰富的事件效果(自动预览图片、链接处理等)
- 支持设置占位图(加载中、出错时、预览时)
- 支持锚点跳转、长按复制等丰富功能
- 支持大部分 *html* 实体
- 丰富的插件(关键词搜索、内容编辑、`latex` 公式等)
- 效率高、容错性强且轻量化
查看 [功能介绍](https://jin-yufeng.gitee.io/mp-html/#/overview/feature) 了解更多
## 使用方法
- `uni_modules` 方式
1. 点击右上角的 `使用 HBuilder X 导入插件` 按钮直接导入项目或点击 `下载插件 ZIP` 按钮下载插件包并解压到项目的 `uni_modules/mp-html` 目录下
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<!-- 不需要引入,可直接使用 -->
<mp-html :content="html" />
```
```javascript
export default {
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
3. 需要更新版本时在 `HBuilder X` 中右键 `uni_modules/mp-html` 目录选择 `从插件市场更新` 即可
- 源码方式
1. 从 [github](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 或 [gitee](https://gitee.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 下载源码
插件市场的 **非 uni_modules 版本** 无法更新,不建议从插件市场获取
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<mp-html :content="html" />
```
```javascript
import mpHtml from '@/components/mp-html/mp-html'
export default {
// HBuilderX 2.5.5+ 可以通过 easycom 自动引入
components: {
mpHtml
},
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
- npm 方式
1. 在项目根目录下执行
```bash
npm install mp-html
```
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<mp-html :content="html" />
```
```javascript
import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html'
export default {
// 不可省略
components: {
mpHtml
},
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
3. 需要更新版本时执行以下命令即可
```bash
npm update mp-html
```
使用 *cli* 方式运行的项目,通过 *npm* 方式引入时,需要在 *vue.config.js* 中配置 *transpileDependencies*,详情可见 [#330](https://github.com/jin-yufeng/mp-html/issues/330#issuecomment-913617687)
如果在 **nvue** 中使用还要将 `dist/uni-app/static` 目录下的内容拷贝到项目的 `static` 目录下,否则无法运行
查看 [快速开始](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart) 了解更多
## 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|:---:|:---:|:---:|---|
| container-style | String | | 容器的样式([2.1.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v210) |
| content | String | | 用于渲染的 html 字符串 |
| copy-link | Boolean | true | 是否允许外部链接被点击时自动复制 |
| domain | String | | 主域名(用于链接拼接) |
| error-img | String | | 图片出错时的占位图链接 |
| lazy-load | Boolean | false | 是否开启图片懒加载 |
| loading-img | String | | 图片加载过程中的占位图链接 |
| pause-video | Boolean | true | 是否在播放一个视频时自动暂停其他视频 |
| preview-img | Boolean | true | 是否允许图片被点击时自动预览 |
| scroll-table | Boolean | false | 是否给每个表格添加一个滚动层使其能单独横向滚动 |
| selectable | Boolean | false | 是否开启文本长按复制 |
| set-title | Boolean | true | 是否将 title 标签的内容设置到页面标题 |
| show-img-menu | Boolean | true | 是否允许图片被长按时显示菜单 |
| tag-style | Object | | 设置标签的默认样式 |
| use-anchor | Boolean | false | 是否使用锚点链接 |
查看 [属性](https://jin-yufeng.gitee.io/mp-html/#/basic/prop) 了解更多
## 组件事件
| 名称 | 触发时机 |
|:---:|---|
| load | dom 树加载完毕时 |
| ready | 图片加载完毕时 |
| error | 发生渲染错误时 |
| imgtap | 图片被点击时 |
| linktap | 链接被点击时 |
| play | 音视频播放时 |
查看 [事件](https://jin-yufeng.gitee.io/mp-html/#/basic/event) 了解更多
## api
组件实例上提供了一些 `api` 方法可供调用
| 名称 | 作用 |
|:---:|---|
| in | 将锚点跳转的范围限定在一个 scroll-view 内 |
| navigateTo | 锚点跳转 |
| getText | 获取文本内容 |
| getRect | 获取富文本内容的位置和大小 |
| setContent | 设置富文本内容 |
| imgList | 获取所有图片的数组 |
| pauseMedia | 暂停播放音视频([2.2.2+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v222) |
| setPlaybackRate | 设置音视频播放速率([2.4.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v240) |
查看 [api](https://jin-yufeng.gitee.io/mp-html/#/advanced/api) 了解更多
## 插件扩展
除基本功能外,本组件还提供了丰富的扩展,可按照需要选用
| 名称 | 作用 |
|:---:|---|
| audio | 音乐播放器 |
| editable | 富文本 **编辑**[示例项目](https://mp-html.oss-cn-hangzhou.aliyuncs.com/editable.zip) |
| emoji | 解析 emoji |
| highlight | 代码块高亮显示 |
| markdown | 渲染 markdown |
| search | 关键词搜索 |
| style | 匹配 style 标签中的样式 |
| txv-video | 使用腾讯视频 |
| img-cache | 图片缓存 by [@PentaTea](https://github.com/PentaTea) |
| latex | 渲染 latex 公式 by [@Zeng-J](https://github.com/Zeng-J) |
从插件市场导入的包中 **不含有** 扩展插件,使用插件需通过微信小程序 `富文本插件` 获取或参考以下方法进行打包:
1. 获取完整组件包
```bash
npm install mp-html
```
2. 编辑 `tools/config.js` 中的 `plugins` 项,选择需要的插件
3. 生成新的组件包
`node_modules/mp-html` 目录下执行
```bash
npm install
npm run build:uni-app
```
4. 拷贝 `dist/uni-app` 中的内容到项目根目录
查看 [插件](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin) 了解更多
## 关于 nvue
`nvue` 使用原生渲染,不支持部分 `css` 样式,为实现和 `html` 相同的效果,组件内部通过 `web-view` 进行渲染,性能上差于原生,根据 `weex` 官方建议,`web` 标签仅应用在非常规的降级场景。因此,如果通过原生的方式(如 `richtext`)能够满足需要,则不建议使用本组件,如果有较多的富文本内容,则可以直接使用 `vue` 页面
由于渲染方式与其他端不同,有以下限制:
1. 不支持 `lazy-load` 属性
2. 视频不支持全屏播放
3. 如果在 `flex-direction: row` 的容器中使用,需要给组件设置宽度或设置 `flex: 1` 占满剩余宽度
`nvue` 模式下,[此问题](https://ask.dcloud.net.cn/question/119678) 修复前,不支持通过 `uni_modules` 引入,需要本地引入(将 [dist/uni-app](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 中的内容拷贝到项目根目录下)
## 立即体验
![富文本插件](https://mp-html.oss-cn-hangzhou.aliyuncs.com/qrcode.jpg)
## 问题反馈
遇到问题时,请先查阅 [常见问题](https://jin-yufeng.gitee.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题
可通过 [issue](https://github.com/jin-yufeng/mp-html/issues/new/choose) 、插件问答或发送邮件到 [mp_html@126.com](mailto:mp_html@126.com) 提问,不建议在评论区提问(不方便回复)
提问请严格按照 [issue 模板](https://github.com/jin-yufeng/mp-html/issues/new/choose) ,描述清楚使用环境、`html` 内容或可复现的 `demo` 项目以及复现方式,对于 **描述不清**、**无法复现** 或重复的问题将不予回复
欢迎加入 `QQ` 交流群:`699734691`
查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多
## 为减小组件包的大小默认组件包中不包含编辑、latex 公式等扩展功能,需要使用扩展功能的请参考下方的 插件扩展 栏的说明
## 功能介绍
- 全端支持(含 `v3、NVUE`
- 支持丰富的标签(包括 `table`、`video`、`svg` 等)
- 支持丰富的事件效果(自动预览图片、链接处理等)
- 支持设置占位图(加载中、出错时、预览时)
- 支持锚点跳转、长按复制等丰富功能
- 支持大部分 *html* 实体
- 丰富的插件(关键词搜索、内容编辑、`latex` 公式等)
- 效率高、容错性强且轻量化
查看 [功能介绍](https://jin-yufeng.gitee.io/mp-html/#/overview/feature) 了解更多
## 使用方法
- `uni_modules` 方式
1. 点击右上角的 `使用 HBuilder X 导入插件` 按钮直接导入项目或点击 `下载插件 ZIP` 按钮下载插件包并解压到项目的 `uni_modules/mp-html` 目录下
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<!-- 不需要引入,可直接使用 -->
<mp-html :content="html" />
```
```javascript
export default {
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
3. 需要更新版本时在 `HBuilder X` 中右键 `uni_modules/mp-html` 目录选择 `从插件市场更新` 即可
- 源码方式
1. 从 [github](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 或 [gitee](https://gitee.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 下载源码
插件市场的 **非 uni_modules 版本** 无法更新,不建议从插件市场获取
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<mp-html :content="html" />
```
```javascript
import mpHtml from '@/components/mp-html/mp-html'
export default {
// HBuilderX 2.5.5+ 可以通过 easycom 自动引入
components: {
mpHtml
},
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
- npm 方式
1. 在项目根目录下执行
```bash
npm install mp-html
```
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<mp-html :content="html" />
```
```javascript
import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html'
export default {
// 不可省略
components: {
mpHtml
},
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
3. 需要更新版本时执行以下命令即可
```bash
npm update mp-html
```
使用 *cli* 方式运行的项目,通过 *npm* 方式引入时,需要在 *vue.config.js* 中配置 *transpileDependencies*,详情可见 [#330](https://github.com/jin-yufeng/mp-html/issues/330#issuecomment-913617687)
如果在 **nvue** 中使用还要将 `dist/uni-app/static` 目录下的内容拷贝到项目的 `static` 目录下,否则无法运行
查看 [快速开始](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart) 了解更多
## 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|:---:|:---:|:---:|---|
| container-style | String | | 容器的样式([2.1.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v210) |
| content | String | | 用于渲染的 html 字符串 |
| copy-link | Boolean | true | 是否允许外部链接被点击时自动复制 |
| domain | String | | 主域名(用于链接拼接) |
| error-img | String | | 图片出错时的占位图链接 |
| lazy-load | Boolean | false | 是否开启图片懒加载 |
| loading-img | String | | 图片加载过程中的占位图链接 |
| pause-video | Boolean | true | 是否在播放一个视频时自动暂停其他视频 |
| preview-img | Boolean | true | 是否允许图片被点击时自动预览 |
| scroll-table | Boolean | false | 是否给每个表格添加一个滚动层使其能单独横向滚动 |
| selectable | Boolean | false | 是否开启文本长按复制 |
| set-title | Boolean | true | 是否将 title 标签的内容设置到页面标题 |
| show-img-menu | Boolean | true | 是否允许图片被长按时显示菜单 |
| tag-style | Object | | 设置标签的默认样式 |
| use-anchor | Boolean | false | 是否使用锚点链接 |
查看 [属性](https://jin-yufeng.gitee.io/mp-html/#/basic/prop) 了解更多
## 组件事件
| 名称 | 触发时机 |
|:---:|---|
| load | dom 树加载完毕时 |
| ready | 图片加载完毕时 |
| error | 发生渲染错误时 |
| imgtap | 图片被点击时 |
| linktap | 链接被点击时 |
| play | 音视频播放时 |
查看 [事件](https://jin-yufeng.gitee.io/mp-html/#/basic/event) 了解更多
## api
组件实例上提供了一些 `api` 方法可供调用
| 名称 | 作用 |
|:---:|---|
| in | 将锚点跳转的范围限定在一个 scroll-view 内 |
| navigateTo | 锚点跳转 |
| getText | 获取文本内容 |
| getRect | 获取富文本内容的位置和大小 |
| setContent | 设置富文本内容 |
| imgList | 获取所有图片的数组 |
| pauseMedia | 暂停播放音视频([2.2.2+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v222) |
| setPlaybackRate | 设置音视频播放速率([2.4.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v240) |
查看 [api](https://jin-yufeng.gitee.io/mp-html/#/advanced/api) 了解更多
## 插件扩展
除基本功能外,本组件还提供了丰富的扩展,可按照需要选用
| 名称 | 作用 |
|:---:|---|
| audio | 音乐播放器 |
| editable | 富文本 **编辑**[示例项目](https://mp-html.oss-cn-hangzhou.aliyuncs.com/editable.zip) |
| emoji | 解析 emoji |
| highlight | 代码块高亮显示 |
| markdown | 渲染 markdown |
| search | 关键词搜索 |
| style | 匹配 style 标签中的样式 |
| txv-video | 使用腾讯视频 |
| img-cache | 图片缓存 by [@PentaTea](https://github.com/PentaTea) |
| latex | 渲染 latex 公式 by [@Zeng-J](https://github.com/Zeng-J) |
从插件市场导入的包中 **不含有** 扩展插件,使用插件需通过微信小程序 `富文本插件` 获取或参考以下方法进行打包:
1. 获取完整组件包
```bash
npm install mp-html
```
2. 编辑 `tools/config.js` 中的 `plugins` 项,选择需要的插件
3. 生成新的组件包
`node_modules/mp-html` 目录下执行
```bash
npm install
npm run build:uni-app
```
4. 拷贝 `dist/uni-app` 中的内容到项目根目录
查看 [插件](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin) 了解更多
## 关于 nvue
`nvue` 使用原生渲染,不支持部分 `css` 样式,为实现和 `html` 相同的效果,组件内部通过 `web-view` 进行渲染,性能上差于原生,根据 `weex` 官方建议,`web` 标签仅应用在非常规的降级场景。因此,如果通过原生的方式(如 `richtext`)能够满足需要,则不建议使用本组件,如果有较多的富文本内容,则可以直接使用 `vue` 页面
由于渲染方式与其他端不同,有以下限制:
1. 不支持 `lazy-load` 属性
2. 视频不支持全屏播放
3. 如果在 `flex-direction: row` 的容器中使用,需要给组件设置宽度或设置 `flex: 1` 占满剩余宽度
`nvue` 模式下,[此问题](https://ask.dcloud.net.cn/question/119678) 修复前,不支持通过 `uni_modules` 引入,需要本地引入(将 [dist/uni-app](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 中的内容拷贝到项目根目录下)
## 立即体验
![富文本插件](https://mp-html.oss-cn-hangzhou.aliyuncs.com/qrcode.jpg)
## 问题反馈
遇到问题时,请先查阅 [常见问题](https://jin-yufeng.gitee.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题
可通过 [issue](https://github.com/jin-yufeng/mp-html/issues/new/choose) 、插件问答或发送邮件到 [mp_html@126.com](mailto:mp_html@126.com) 提问,不建议在评论区提问(不方便回复)
提问请严格按照 [issue 模板](https://github.com/jin-yufeng/mp-html/issues/new/choose) ,描述清楚使用环境、`html` 内容或可复现的 `demo` 项目以及复现方式,对于 **描述不清**、**无法复现** 或重复的问题将不予回复
欢迎加入 `QQ` 交流群:`699734691`
查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多

View File

@ -1,498 +1,498 @@
<template>
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
<slot v-if="!nodes[0]" />
<!-- #ifndef APP-PLUS-NVUE -->
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" />
<!-- #endif -->
<!-- #ifdef APP-PLUS-NVUE -->
<web-view ref="web" src="/uni_modules/mp-html/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
<!-- #endif -->
</view>
</template>
<script>
/**
* mp-html v2.4.1
* @description 富文本组件
* @tutorial https://github.com/jin-yufeng/mp-html
* @property {String} container-style 容器的样式
* @property {String} content 用于渲染的 html 字符串
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
* @property {String} domain 主域名用于拼接链接
* @property {String} error-img 图片出错时的占位图链接
* @property {Boolean} lazy-load 是否开启图片懒加载
* @property {string} loading-img 图片加载过程中的占位图链接
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
* @property {Boolean} preview-img 是否允许图片被点击时自动预览
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
* @property {Boolean | String} selectable 是否开启长按复制
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
* @property {Object} tag-style 标签的默认样式
* @property {Boolean | Number} use-anchor 是否使用锚点链接
* @event {Function} load dom 结构加载完毕时触发
* @event {Function} ready 所有图片加载完毕时触发
* @event {Function} imgtap 图片被点击时触发
* @event {Function} linktap 链接被点击时触发
* @event {Function} play 音视频播放时触发
* @event {Function} error 媒体加载出错时触发
*/
// #ifndef APP-PLUS-NVUE
import node from './node/node'
// #endif
import Parser from './parser'
const plugins=[]
// #ifdef APP-PLUS-NVUE
const dom = weex.requireModule('dom')
// #endif
export default {
name: 'mp-html',
data () {
return {
nodes: [],
// #ifdef APP-PLUS-NVUE
height: 3
// #endif
}
},
props: {
containerStyle: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
copyLink: {
type: [Boolean, String],
default: true
},
domain: String,
errorImg: {
type: String,
default: ''
},
lazyLoad: {
type: [Boolean, String],
default: false
},
loadingImg: {
type: String,
default: ''
},
pauseVideo: {
type: [Boolean, String],
default: true
},
previewImg: {
type: [Boolean, String],
default: true
},
scrollTable: [Boolean, String],
selectable: [Boolean, String],
setTitle: {
type: [Boolean, String],
default: true
},
showImgMenu: {
type: [Boolean, String],
default: true
},
tagStyle: Object,
useAnchor: [Boolean, Number]
},
// #ifdef VUE3
emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
// #endif
// #ifndef APP-PLUS-NVUE
components: {
node
},
// #endif
watch: {
content (content) {
this.setContent(content)
}
},
created () {
this.plugins = []
for (let i = plugins.length; i--;) {
this.plugins.push(new plugins[i](this))
}
},
mounted () {
if (this.content && !this.nodes.length) {
this.setContent(this.content)
}
},
beforeDestroy () {
this._hook('onDetached')
},
methods: {
/**
* @description 将锚点跳转的范围限定在一个 scroll-view
* @param {Object} page scroll-view 所在页面的示例
* @param {String} selector scroll-view 的选择器
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
*/
in (page, selector, scrollTop) {
// #ifndef APP-PLUS-NVUE
if (page && selector && scrollTop) {
this._in = {
page,
selector,
scrollTop
}
}
// #endif
},
/**
* @description 锚点跳转
* @param {String} id 要跳转的锚点 id
* @param {Number} offset 跳转位置的偏移量
* @returns {Promise}
*/
navigateTo (id, offset) {
return new Promise((resolve, reject) => {
if (!this.useAnchor) {
reject(Error('Anchor is disabled'))
return
}
offset = offset || parseInt(this.useAnchor) || 0
// #ifdef APP-PLUS-NVUE
if (!id) {
dom.scrollToElement(this.$refs.web, {
offset
})
resolve()
} else {
this._navigateTo = {
resolve,
reject,
offset
}
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
}
// #endif
// #ifndef APP-PLUS-NVUE
let deep = ' '
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
deep = '>>>'
// #endif
const selector = uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this._in ? this._in.page : this)
// #endif
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
if (this._in) {
selector.select(this._in.selector).scrollOffset()
.select(this._in.selector).boundingClientRect()
} else {
// scroll-view
selector.selectViewport().scrollOffset() //
}
selector.exec(res => {
if (!res[0]) {
reject(Error('Label not found'))
return
}
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
if (this._in) {
// scroll-view
this._in.page[this._in.scrollTop] = scrollTop
} else {
//
uni.pageScrollTo({
scrollTop,
duration: 300
})
}
resolve()
})
// #endif
})
},
/**
* @description 获取文本内容
* @return {String}
*/
getText (nodes) {
let text = '';
(function traversal (nodes) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === 'text') {
text += node.text.replace(/&amp;/g, '&')
} else if (node.name === 'br') {
text += '\n'
} else {
//
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
if (isBlock && text && text[text.length - 1] !== '\n') {
text += '\n'
}
//
if (node.children) {
traversal(node.children)
}
if (isBlock && text[text.length - 1] !== '\n') {
text += '\n'
} else if (node.name === 'td' || node.name === 'th') {
text += '\t'
}
}
}
})(nodes || this.nodes)
return text
},
/**
* @description 获取内容大小和位置
* @return {Promise}
*/
getRect () {
return new Promise((resolve, reject) => {
uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this)
// #endif
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
})
},
/**
* @description 暂停播放媒体
*/
pauseMedia () {
for (let i = (this._videos || []).length; i--;) {
this._videos[i].pause()
}
// #ifdef APP-PLUS
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
// #ifndef APP-PLUS-NVUE
let page = this.$parent
while (!page.$scope) page = page.$parent
page.$scope.$getAppWebview().evalJS(command)
// #endif
// #ifdef APP-PLUS-NVUE
this.$refs.web.evalJs(command)
// #endif
// #endif
},
/**
* @description 设置媒体播放速率
* @param {Number} rate 播放速率
*/
setPlaybackRate (rate) {
this.playbackRate = rate
for (let i = (this._videos || []).length; i--;) {
this._videos[i].playbackRate(rate)
}
// #ifdef APP-PLUS
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate
// #ifndef APP-PLUS-NVUE
let page = this.$parent
while (!page.$scope) page = page.$parent
page.$scope.$getAppWebview().evalJS(command)
// #endif
// #ifdef APP-PLUS-NVUE
this.$refs.web.evalJs(command)
// #endif
// #endif
},
/**
* @description 设置内容
* @param {String} content html 内容
* @param {Boolean} append 是否在尾部追加
*/
setContent (content, append) {
if (!append || !this.imgList) {
this.imgList = []
}
const nodes = new Parser(this).parse(content)
// #ifdef APP-PLUS-NVUE
if (this._ready) {
this._set(nodes, append)
}
// #endif
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
// #ifndef APP-PLUS-NVUE
this._videos = []
this.$nextTick(() => {
this._hook('onLoad')
this.$emit('load')
})
if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {
// 350ms
let height = 0
const callback = rect => {
if (!rect || !rect.height) rect = {}
// 350ms ready
if (rect.height === height) {
this.$emit('ready', rect)
} else {
height = rect.height
setTimeout(() => {
this.getRect().then(callback).catch(callback)
}, 350)
}
}
this.getRect().then(callback).catch(callback)
} else {
//
if (!this.imgList._unloadimgs) {
this.getRect().then(rect => {
this.$emit('ready', rect)
}).catch(() => {
this.$emit('ready', {})
})
}
}
// #endif
},
/**
* @description 调用插件钩子函数
*/
_hook (name) {
for (let i = plugins.length; i--;) {
if (this.plugins[i][name]) {
this.plugins[i][name]()
}
}
},
// #ifdef APP-PLUS-NVUE
/**
* @description 设置内容
*/
_set (nodes, append) {
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
},
/**
* @description 接收到 web-view 消息
*/
_onMessage (e) {
const message = e.detail.data[0]
switch (message.action) {
// web-view
case 'onJSBridgeReady':
this._ready = true
if (this.nodes) {
this._set(this.nodes)
}
break
// dom
case 'onLoad':
this.height = message.height
this._hook('onLoad')
this.$emit('load')
break
//
case 'onReady':
this.getRect().then(res => {
this.$emit('ready', res)
}).catch(() => {
this.$emit('ready', {})
})
break
//
case 'onHeightChange':
this.height = message.height
break
//
case 'onImgTap':
this.$emit('imgtap', message.attrs)
if (this.previewImg) {
uni.previewImage({
current: parseInt(message.attrs.i),
urls: this.imgList
})
}
break
//
case 'onLinkTap': {
const href = message.attrs.href
this.$emit('linktap', message.attrs)
if (href) {
//
if (href[0] === '#') {
if (this.useAnchor) {
dom.scrollToElement(this.$refs.web, {
offset: message.offset
})
}
} else if (href.includes('://')) {
//
if (this.copyLink) {
plus.runtime.openWeb(href)
}
} else {
uni.navigateTo({
url: href,
fail () {
uni.switchTab({
url: href
})
}
})
}
}
break
}
case 'onPlay':
this.$emit('play')
break
//
case 'getOffset':
if (typeof message.offset === 'number') {
dom.scrollToElement(this.$refs.web, {
offset: message.offset + this._navigateTo.offset
})
this._navigateTo.resolve()
} else {
this._navigateTo.reject(Error('Label not found'))
}
break
//
case 'onClick':
this.$emit('tap')
this.$emit('click')
break
//
case 'onError':
this.$emit('error', {
source: message.source,
attrs: message.attrs
})
}
}
// #endif
}
}
</script>
<style>
/* #ifndef APP-PLUS-NVUE */
/* 根节点样式 */
._root {
padding: 1px 0;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}
/* 长按复制 */
._select {
user-select: text;
}
/* #endif */
</style>
<template>
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
<slot v-if="!nodes[0]" />
<!-- #ifndef APP-PLUS-NVUE -->
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" />
<!-- #endif -->
<!-- #ifdef APP-PLUS-NVUE -->
<web-view ref="web" src="/uni_modules/mp-html/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
<!-- #endif -->
</view>
</template>
<script>
/**
* mp-html v2.4.1
* @description 富文本组件
* @tutorial https://github.com/jin-yufeng/mp-html
* @property {String} container-style 容器的样式
* @property {String} content 用于渲染的 html 字符串
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
* @property {String} domain 主域名用于拼接链接
* @property {String} error-img 图片出错时的占位图链接
* @property {Boolean} lazy-load 是否开启图片懒加载
* @property {string} loading-img 图片加载过程中的占位图链接
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
* @property {Boolean} preview-img 是否允许图片被点击时自动预览
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
* @property {Boolean | String} selectable 是否开启长按复制
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
* @property {Object} tag-style 标签的默认样式
* @property {Boolean | Number} use-anchor 是否使用锚点链接
* @event {Function} load dom 结构加载完毕时触发
* @event {Function} ready 所有图片加载完毕时触发
* @event {Function} imgtap 图片被点击时触发
* @event {Function} linktap 链接被点击时触发
* @event {Function} play 音视频播放时触发
* @event {Function} error 媒体加载出错时触发
*/
// #ifndef APP-PLUS-NVUE
import node from './node/node'
// #endif
import Parser from './parser'
const plugins=[]
// #ifdef APP-PLUS-NVUE
const dom = weex.requireModule('dom')
// #endif
export default {
name: 'mp-html',
data () {
return {
nodes: [],
// #ifdef APP-PLUS-NVUE
height: 3
// #endif
}
},
props: {
containerStyle: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
copyLink: {
type: [Boolean, String],
default: true
},
domain: String,
errorImg: {
type: String,
default: ''
},
lazyLoad: {
type: [Boolean, String],
default: false
},
loadingImg: {
type: String,
default: ''
},
pauseVideo: {
type: [Boolean, String],
default: true
},
previewImg: {
type: [Boolean, String],
default: true
},
scrollTable: [Boolean, String],
selectable: [Boolean, String],
setTitle: {
type: [Boolean, String],
default: true
},
showImgMenu: {
type: [Boolean, String],
default: true
},
tagStyle: Object,
useAnchor: [Boolean, Number]
},
// #ifdef VUE3
emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
// #endif
// #ifndef APP-PLUS-NVUE
components: {
node
},
// #endif
watch: {
content (content) {
this.setContent(content)
}
},
created () {
this.plugins = []
for (let i = plugins.length; i--;) {
this.plugins.push(new plugins[i](this))
}
},
mounted () {
if (this.content && !this.nodes.length) {
this.setContent(this.content)
}
},
beforeDestroy () {
this._hook('onDetached')
},
methods: {
/**
* @description 将锚点跳转的范围限定在一个 scroll-view
* @param {Object} page scroll-view 所在页面的示例
* @param {String} selector scroll-view 的选择器
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
*/
in (page, selector, scrollTop) {
// #ifndef APP-PLUS-NVUE
if (page && selector && scrollTop) {
this._in = {
page,
selector,
scrollTop
}
}
// #endif
},
/**
* @description 锚点跳转
* @param {String} id 要跳转的锚点 id
* @param {Number} offset 跳转位置的偏移量
* @returns {Promise}
*/
navigateTo (id, offset) {
return new Promise((resolve, reject) => {
if (!this.useAnchor) {
reject(Error('Anchor is disabled'))
return
}
offset = offset || parseInt(this.useAnchor) || 0
// #ifdef APP-PLUS-NVUE
if (!id) {
dom.scrollToElement(this.$refs.web, {
offset
})
resolve()
} else {
this._navigateTo = {
resolve,
reject,
offset
}
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
}
// #endif
// #ifndef APP-PLUS-NVUE
let deep = ' '
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
deep = '>>>'
// #endif
const selector = uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this._in ? this._in.page : this)
// #endif
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
if (this._in) {
selector.select(this._in.selector).scrollOffset()
.select(this._in.selector).boundingClientRect()
} else {
// scroll-view
selector.selectViewport().scrollOffset() //
}
selector.exec(res => {
if (!res[0]) {
reject(Error('Label not found'))
return
}
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
if (this._in) {
// scroll-view
this._in.page[this._in.scrollTop] = scrollTop
} else {
//
uni.pageScrollTo({
scrollTop,
duration: 300
})
}
resolve()
})
// #endif
})
},
/**
* @description 获取文本内容
* @return {String}
*/
getText (nodes) {
let text = '';
(function traversal (nodes) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === 'text') {
text += node.text.replace(/&amp;/g, '&')
} else if (node.name === 'br') {
text += '\n'
} else {
//
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
if (isBlock && text && text[text.length - 1] !== '\n') {
text += '\n'
}
//
if (node.children) {
traversal(node.children)
}
if (isBlock && text[text.length - 1] !== '\n') {
text += '\n'
} else if (node.name === 'td' || node.name === 'th') {
text += '\t'
}
}
}
})(nodes || this.nodes)
return text
},
/**
* @description 获取内容大小和位置
* @return {Promise}
*/
getRect () {
return new Promise((resolve, reject) => {
uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this)
// #endif
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
})
},
/**
* @description 暂停播放媒体
*/
pauseMedia () {
for (let i = (this._videos || []).length; i--;) {
this._videos[i].pause()
}
// #ifdef APP-PLUS
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
// #ifndef APP-PLUS-NVUE
let page = this.$parent
while (!page.$scope) page = page.$parent
page.$scope.$getAppWebview().evalJS(command)
// #endif
// #ifdef APP-PLUS-NVUE
this.$refs.web.evalJs(command)
// #endif
// #endif
},
/**
* @description 设置媒体播放速率
* @param {Number} rate 播放速率
*/
setPlaybackRate (rate) {
this.playbackRate = rate
for (let i = (this._videos || []).length; i--;) {
this._videos[i].playbackRate(rate)
}
// #ifdef APP-PLUS
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate
// #ifndef APP-PLUS-NVUE
let page = this.$parent
while (!page.$scope) page = page.$parent
page.$scope.$getAppWebview().evalJS(command)
// #endif
// #ifdef APP-PLUS-NVUE
this.$refs.web.evalJs(command)
// #endif
// #endif
},
/**
* @description 设置内容
* @param {String} content html 内容
* @param {Boolean} append 是否在尾部追加
*/
setContent (content, append) {
if (!append || !this.imgList) {
this.imgList = []
}
const nodes = new Parser(this).parse(content)
// #ifdef APP-PLUS-NVUE
if (this._ready) {
this._set(nodes, append)
}
// #endif
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
// #ifndef APP-PLUS-NVUE
this._videos = []
this.$nextTick(() => {
this._hook('onLoad')
this.$emit('load')
})
if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {
// 350ms
let height = 0
const callback = rect => {
if (!rect || !rect.height) rect = {}
// 350ms ready
if (rect.height === height) {
this.$emit('ready', rect)
} else {
height = rect.height
setTimeout(() => {
this.getRect().then(callback).catch(callback)
}, 350)
}
}
this.getRect().then(callback).catch(callback)
} else {
//
if (!this.imgList._unloadimgs) {
this.getRect().then(rect => {
this.$emit('ready', rect)
}).catch(() => {
this.$emit('ready', {})
})
}
}
// #endif
},
/**
* @description 调用插件钩子函数
*/
_hook (name) {
for (let i = plugins.length; i--;) {
if (this.plugins[i][name]) {
this.plugins[i][name]()
}
}
},
// #ifdef APP-PLUS-NVUE
/**
* @description 设置内容
*/
_set (nodes, append) {
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
},
/**
* @description 接收到 web-view 消息
*/
_onMessage (e) {
const message = e.detail.data[0]
switch (message.action) {
// web-view
case 'onJSBridgeReady':
this._ready = true
if (this.nodes) {
this._set(this.nodes)
}
break
// dom
case 'onLoad':
this.height = message.height
this._hook('onLoad')
this.$emit('load')
break
//
case 'onReady':
this.getRect().then(res => {
this.$emit('ready', res)
}).catch(() => {
this.$emit('ready', {})
})
break
//
case 'onHeightChange':
this.height = message.height
break
//
case 'onImgTap':
this.$emit('imgtap', message.attrs)
if (this.previewImg) {
uni.previewImage({
current: parseInt(message.attrs.i),
urls: this.imgList
})
}
break
//
case 'onLinkTap': {
const href = message.attrs.href
this.$emit('linktap', message.attrs)
if (href) {
//
if (href[0] === '#') {
if (this.useAnchor) {
dom.scrollToElement(this.$refs.web, {
offset: message.offset
})
}
} else if (href.includes('://')) {
//
if (this.copyLink) {
plus.runtime.openWeb(href)
}
} else {
uni.navigateTo({
url: href,
fail () {
uni.switchTab({
url: href
})
}
})
}
}
break
}
case 'onPlay':
this.$emit('play')
break
//
case 'getOffset':
if (typeof message.offset === 'number') {
dom.scrollToElement(this.$refs.web, {
offset: message.offset + this._navigateTo.offset
})
this._navigateTo.resolve()
} else {
this._navigateTo.reject(Error('Label not found'))
}
break
//
case 'onClick':
this.$emit('tap')
this.$emit('click')
break
//
case 'onError':
this.$emit('error', {
source: message.source,
attrs: message.attrs
})
}
}
// #endif
}
}
</script>
<style>
/* #ifndef APP-PLUS-NVUE */
/* 根节点样式 */
._root {
padding: 1px 0;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}
/* 长按复制 */
._select {
user-select: text;
}
/* #endif */
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,76 +1,76 @@
{
"id": "mp-html",
"displayName": "mp-html 富文本组件【全端支持支持编辑、latex等扩展】",
"version": "v2.4.1",
"description": "一个强大的富文本组件,高效轻量,功能丰富",
"keywords": [
"富文本",
"编辑器",
"html",
"rich-text",
"editor"
],
"repository": "https://github.com/jin-yufeng/mp-html",
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/mp-html",
"type": "component-vue"
},
"uni_modules": {
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
{
"id": "mp-html",
"displayName": "mp-html 富文本组件【全端支持支持编辑、latex等扩展】",
"version": "v2.4.1",
"description": "一个强大的富文本组件,高效轻量,功能丰富",
"keywords": [
"富文本",
"编辑器",
"html",
"rich-text",
"editor"
],
"repository": "https://github.com/jin-yufeng/mp-html",
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/mp-html",
"type": "component-vue"
},
"uni_modules": {
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -1,4 +1,6 @@
import { loadEnv } from 'vite';
import {
loadEnv
} from 'vite';
import uni from '@dcloudio/vite-plugin-uni';
import path from 'path';
// import viteCompression from 'vite-plugin-compression';
@ -31,4 +33,4 @@ export default (command, mode) => {
},
},
};
};
};