liujiaxin 1 month ago
parent
commit
4f9b26251f
43 changed files with 3154 additions and 652 deletions
  1. 8 2
      api/living.js
  2. 8 8
      api/order.js
  3. 549 151
      pages_course/living.vue
  4. 15 5
      pages_course/livingList.vue
  5. 96 96
      pages_shopping/live/confirmCreateOrder.vue
  6. 1 1
      pages_shopping/live/goods.vue
  7. 428 389
      pages_user/user/addAddress.vue
  8. BIN
      static/images/coupon.png
  9. BIN
      static/images/coupon_bg.png
  10. BIN
      static/images/coupon_top.png
  11. BIN
      static/images/no-prize.png
  12. 12 0
      uniCloud-tcb/database/JQL查询.jql
  13. 6 0
      uni_modules/uni-config-center/changelog.md
  14. 81 0
      uni_modules/uni-config-center/package.json
  15. 93 0
      uni_modules/uni-config-center/readme.md
  16. 0 0
      uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/index.js
  17. 13 0
      uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/package.json
  18. 36 0
      uni_modules/uni-id-common/changelog.md
  19. 84 0
      uni_modules/uni-id-common/package.json
  20. 3 0
      uni_modules/uni-id-common/readme.md
  21. 0 0
      uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common/index.js
  22. 20 0
      uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common/package.json
  23. 27 0
      uni_modules/uni-open-bridge-common/changelog.md
  24. 84 0
      uni_modules/uni-open-bridge-common/package.json
  25. 5 0
      uni_modules/uni-open-bridge-common/readme.md
  26. 26 0
      uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/bridge-error.js
  27. 124 0
      uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/config.js
  28. 30 0
      uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/consts.js
  29. 317 0
      uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/index.js
  30. 19 0
      uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/package.json
  31. 116 0
      uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/storage.js
  32. 324 0
      uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/uni-cloud-cache.js
  33. 31 0
      uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/validator.js
  34. 203 0
      uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/weixin-server.js
  35. 19 0
      uni_modules/uni-open-bridge-common/uniCloud/database/opendb-open-data.schema.json
  36. 8 0
      uni_modules/uni-subscribemsg/changelog.md
  37. 84 0
      uni_modules/uni-subscribemsg/package.json
  38. 7 0
      uni_modules/uni-subscribemsg/readme.md
  39. 58 0
      uni_modules/uni-subscribemsg/uniCloud/cloudfunctions/common/uni-subscribemsg/index.js
  40. 20 0
      uni_modules/uni-subscribemsg/uniCloud/cloudfunctions/common/uni-subscribemsg/package.json
  41. 4 0
      uni_modules/uni-subscribemsg/uniCloud/cloudfunctions/common/uni-subscribemsg/service/index.js
  42. 74 0
      uni_modules/uni-subscribemsg/uniCloud/cloudfunctions/common/uni-subscribemsg/service/weixin-h5.js
  43. 121 0
      uni_modules/uni-subscribemsg/uniCloud/cloudfunctions/common/uni-subscribemsg/service/weixin-mp.js

+ 8 - 2
api/living.js

@@ -29,8 +29,9 @@ const api = {
 	liveLottery: '/app/live/liveLottery/detail', // 抽奖查询
 	getlive: '/app/live/live',
 	liveList: '/app/live/liveList', //直播列表
-	liveShareList: (companyId, pageSize, pageNum) =>`/app/live/liveList/${companyId}?pageSize=${pageSize}&pageNum=${pageNum}` //直播列表
-
+	liveShareList: (companyId, pageSize, pageNum) =>
+		`/app/live/liveList/${companyId}?pageSize=${pageSize}&pageNum=${pageNum}`, //直播列表
+	subNotifyLive: '/app/live/subNotifyLive' //订阅消息
 }
 // 点赞
 export function liveDataLike(liveId, data = {}) {
@@ -146,4 +147,9 @@ export function liveList(data) {
 //销售端分享直播列表
 export function liveShareList(companyId, pageSize, pageNum, data = {}) {
 	return request(api.liveShareList(companyId, pageSize, pageNum), data, 'GET', 'application/json;charset=UTF-8');
+}
+
+//订阅消息
+export function subNotifyLive(data) {
+	return request(api.subNotifyLive, data, 'POST', 'application/json;charset=UTF-8');
 }

+ 8 - 8
api/order.js

@@ -35,6 +35,7 @@ const api = {
 
 	liveOrder: (orderId) => `/app/live/liveOrder/info/${orderId}`, // 订单详情
 	getMyStoreOrderById: (orderId) => `/app/live/liveOrder/info/${orderId}`,
+	computed: '/app/live/liveOrder/computed', // 查询创建订单信息
 	// finishOrder: '/app/live/liveOrder/finishOrder', 
 	// cancelOrder: '/app/live/liveOrder/cancelOrder',
 	
@@ -194,11 +195,10 @@ export function Dicts(data) {
 
 
 export function getMyStoreOrderById(orderId, data = {}) {
-	return request(api.getMyStoreOrderById(orderId), data, 'GET');
-}
-// export function cancelOrder(data) {
-// 	return request(api.cancelOrder(), data, 'POST', 'application/json;charset=UTF-8');
-// }
-// export function finishOrder(data) {
-// 	return request(api.finishOrder(), data, 'POST', 'application/json;charset=UTF-8');
-// }
+	return request(api.getMyStoreOrderById(orderId), data, 'GET', 'application/json;charset=UTF-8');
+}
+
+// 查询创建订单信息
+export function computed(data ) {
+	return request(api.computed, data, 'POST', 'application/json;charset=UTF-8');
+}

File diff suppressed because it is too large
+ 549 - 151
pages_course/living.vue


+ 15 - 5
pages_course/livingList.vue

@@ -99,7 +99,8 @@
 
 			goLive(item) {
 				uni.navigateTo({
-					url: `./living?liveId=${item.liveId}&immediate=true`
+					// &immediate=true
+					url: `./living?liveId=${item.liveId}`
 				});
 			}
 		}
@@ -126,21 +127,30 @@
 				overflow: hidden;
 				position: relative;
 
-				.img{
+				.img {
 					width: 100%;
 					height: 100%;
 				}
 
 				.info {
 					position: absolute;
-					left: 20rpx;
-					bottom: 14rpx;
-					right: 20rpx;
+					
+					width: 100%;
+					bottom: 0;
+					padding: 20rpx;
 					color: #ffffff;
 					display: flex;
+					background-color: rgba(0, 0, 0, 0.6);
 					align-items: center;
+					text{
+						white-space: nowrap;
+						overflow: hidden;
+						text-overflow: ellipsis;
+					}
 				}
+				
 			}
+
 			.list-item:nth-child(2n) {
 				margin-right: 0;
 			}

+ 96 - 96
pages_shopping/live/confirmCreateOrder.vue

@@ -26,56 +26,7 @@
 					<image src="/static/images/arrow_gray.png" mode=""></image>
 				</view>
 			</view>
-			<!-- 药品列表 -->
-			<view class="shopbox" v-for="(shop,idx) in carts" :key="idx">
-				<view class="shopbox-name" v-if="shop.storeName && shop.storeName != 'null'">
-					<text>{{shop.storeName}}</text>
-				</view>
-				<view class="goods-list">
-					<view v-for="(item,index) in shop.list" :key="index" class="item">
-						<view class="img-box">
-							<image :src="item.productAttrImage?item.productAttrImage:item.productImage"
-								mode="aspectFill"></image>
-						</view>
-						<view class="info-box">
-							<view>
-								<view class="name-box ellipsis2">
-									<view class="tag">{{utils.getDictLabelName("storeProductType",item.productType)}}
-									</view>{{item.productName}}
-								</view>
-								<view class="spec ellipsis2">{{item.productAttrName}}</view>
-							</view>
-							<view class="price-num">
-								<view class="price">
-									<text class="unit">¥</text>
-									<text class="num">{{(item.price||0).toFixed(2)}}</text>
-								</view>
-								<view class="num">x{{item.cartNum}}</view>
-							</view>
-						</view>
-					</view>
-					<!-- 运费 -->
-					<view class="points">
-						<view class="left">
-							<text class="text">运费</text>
-						</view>
-						<view class="right" v-if="price&&price.length > 0">
-							<text
-								class="text">{{price[idx].payPostage==null||price[idx].payPostage==0?'免运费':(price[idx].payPostage||0).toFixed(2)}}</text>
-						</view>
-					</view>
-					<!-- 备注 -->
-					<view class="points">
-						<view class="left">
-							<text class="text">备注</text>
-						</view>
-						<view class="remarks">
-							<input type="text" v-model="shop.markinfo" placeholder="备注留言(选填)"
-								placeholder-class="input" />
-						</view>
-					</view>
-				</view>
-			</view>
+
 			<!-- 积分 -->
 			<view class="price-info">
 				<view class="price-info-title">价格明细</view>
@@ -85,7 +36,16 @@
 					</view>
 					<view class="right" style="align-items: baseline;">
 						<text class="price-info-unit">¥</text>
-						<text class="price-info-num">{{(priceSum||0).toFixed(2)}}</text>
+						<text class="price-info-num">{{(orderData.payPrice||0).toFixed(2)}}</text>
+					</view>
+				</view>
+				<view class="points" @click="openCoupon()">
+					<view class="left">
+						<text class="text">优惠券</text>
+					</view>
+					<view class="right">
+						<text class="text">{{couponText}}</text>
+						<image src="/static/images/arrow4.png" mode=""></image>
 					</view>
 				</view>
 				<view class="points">
@@ -93,7 +53,8 @@
 						<text class="text">运费</text>
 					</view>
 					<view class="right">
-						<text class="text" v-if="address!=null">{{price.pay_delivery==null||price.pay_delivery==0?'免运费':price.pay_delivery.toFixed(2)}}</text>
+						<text class="text"
+							v-if="address!=null">{{orderData.payDelivery==null||orderData.payDelivery==0?'免运费':orderData.payDelivery.toFixed(2)}}</text>
 						<text class="text" v-if="address==null">--</text>
 					</view>
 				</view>
@@ -111,7 +72,7 @@
 					</view>
 					<view class="right" style="align-items: baseline;">
 						<text class="price-info-unit">¥</text>
-						<text class="price-info-num">{{(priceSum||0).toFixed(2)}}</text>
+						<text class="price-info-num">{{(orderData.totalPrice||0).toFixed(2)}}</text>
 					</view>
 				</view>
 			</view>
@@ -123,12 +84,45 @@
 					<text class="label">合计:</text>
 					<view class="price">
 						<text class="unit">¥</text>
-						<text class="num">{{(priceSum||0).toFixed(2)}}</text>
+						<text class="num">{{(orderData.totalPrice||0).toFixed(2)}}</text>
 					</view>
 				</view>
 				<view class="btn" @click="submitOrder">提交订单</view>
 			</view>
 		</view>
+
+		<popupBottom ref="popup" :visible.sync="couponVisible" title=" " bgColor="#f5f5f5" radius="30" maxHeight="60%">
+			<view class="coupon" style="height:650rpx;">
+				<div class="coupon-list" v-if="couponsList.length > 0">
+					<div class="item acea-row row-center-wrapper" v-for="(item, index) in couponsList" :key="index">
+						<div class="money">
+							<image v-if="item.status==0" class="img" src="../../static/images/coupon1.png"
+								mode="widthFix"></image>
+							<image v-if="item.status!=0" class="img" src="../../static/images/coupon2.png"
+								mode="widthFix"></image>
+							<div style="z-index: 999;">
+								¥<span class="num">{{ item.couponPrice }}</span>
+							</div>
+							<div class="pic-num">满{{ item.useMinPrice }}元可用</div>
+						</div>
+						<div class="text">
+							<div class="condition line1">
+								{{ item.couponTitle }}
+							</div>
+							<div class="data acea-row row-between-wrapper">
+								<div>{{ item.limitTime }}到期</div>
+								<div class="bnt bg-color-red" @click="couponSelect(item)">选择</div>
+							</div>
+						</div>
+					</div>
+				</div>
+				<view v-if="couponsList.length == 0" class="no-data-box">
+					<image src="/static/images/no_data.png" mode="aspectFit"></image>
+					<view class="empty-title">暂无数据</view>
+				</view>
+			</view>
+
+		</popupBottom>
 	</view>
 </template>
 
@@ -137,19 +131,25 @@
 		createliveOrder, // 创建订单
 		userAddr, // 获取用户收货地址
 		cartOrder, //购物车订单
-		checked //获取购物车选中商品
+		computed
+		// checked //获取购物车选中商品
 	} from "@/api/order.js"
+	import popupBottom from '@/components/px-popup-bottom/px-popup-bottom.vue'
 	export default {
+		components: {
+			popupBottom
+		},
 		data() {
 			return {
-				cityId:null,
-				priceSum: null,
+				orderData: {},
+				cityId: null,
+				// priceSum: null,
 				cartsSelect: [],
 				address: null,
 				addressId: null,
 				totalNum: null,
 				orderKey: null,
-				price: null,
+				// price: null,
 				liveId: null,
 				orderList: [],
 				userInfo: null,
@@ -163,24 +163,22 @@
 				// type: null,
 				cartIds: null,
 				confirmParam: [],
-				
+
 			}
 		},
 
 		onLoad(options) {
-			uni.$on('updateAddress', this.handleAddressUpdate);
+			// uni.$on('updateAddress', this.handleAddressUpdate);
 			console.log("确认订单", options)
-
 			this.orderKey = options.orderKey;
 			this.liveId = options.liveId
 			this.productId = options.productId
 			this.totalNum = Number(options.totalNum) || 0
-			this.price = Number(options.price) || 0
-			this.priceSum = this.price * this.totalNum
 		},
 
 		mounted() {
 			this.getUserAddr()
+
 			uni.$on('updateAddress', (e) => {
 				this.address = e;
 				this.addressId = e.addressId;
@@ -199,6 +197,9 @@
 				console.log('接收到地址数据:', item);
 				// 在这里处理地址数据
 				this.cityId = item.cityId;
+				this.address = item;
+				this.addressId = item.addressId;
+				this.computedOrder()
 				// 更新页面显示或其他操作
 			},
 			// 获取用户收货地址
@@ -207,6 +208,33 @@
 						if (res.code == 200) {
 							console.log("用户收货地址>>>>", res.data)
 							this.address = res.data;
+							if (res.data && res.data.cityId) {
+								this.cityId = res.data.cityId;
+							}
+							this.computedOrder();
+						} else {
+							uni.showToast({
+								title: res.msg,
+								icon: 'none'
+							});
+						}
+					},
+					rej => {}
+				);
+			},
+
+			// 查询创建订单信息
+			computedOrder() {
+				let data = {
+					cityId: this.cityId || '',
+					totalNum: this.totalNum,
+					productId: this.productId,
+					orderKey: this.orderKey,
+				}
+				computed(data).then(res => {
+						if (res.code == 200) {
+							console.log("查询创建订单信息>>>>", res.data)
+							this.orderData = res.data
 						} else {
 							uni.showToast({
 								title: res.msg,
@@ -223,12 +251,13 @@
 			createLiveOrder() {
 
 				let data = {
-					cityId:this.cityId,
+					cityId: this.cityId,
 					liveId: this.liveId,
 					orderKey: this.orderKey,
 					userName: this.address.realName,
 					userPhone: this.address.phone,
-					userAddress: this.address.province+' ' + this.address.city+' ' + this.address.district+' ' + this.address
+					userAddress: this.address.province + ' ' + this.address.city + ' ' + this.address.district + ' ' +
+						this.address
 						.detail,
 					cartId: "5",
 					productId: this.productId,
@@ -271,7 +300,7 @@
 				let that = this;
 				var data = {
 					couponType: 2,
-					useMinPrice: this.price.payPrice
+					useMinPrice: this.orderData.payPrice
 				};
 				getMyEnableCouponList(data).then(res => {
 					this.couponVisible = true;
@@ -318,36 +347,7 @@
 					});
 				}
 			},
-			// 购物车支付订单
-			async creatCartOrder() {
-				try {
-					if (this.orderKey == null) {
-						uni.showToast({
-							icon: 'none',
-							title: '订单KEY不存在',
-						});
-						return;
-					}
-					if (this.address == null) {
-						uni.showToast({
-							icon: 'none',
-							title: '收货地址不能为空',
-						});
-						return;
-					}
-					const orderList = await this.cartOrder(); // 等待订单数据返回
-					const orderListStr = encodeURIComponent(JSON.stringify(orderList));
-					uni.navigateTo({
-						url: `/pages_shopping/live/paymentOrder?orderList=${orderListStr}`
-					});
-				} catch (error) {
-					console.error('订单创建失败:', error);
-					uni.showToast({
-						title: '提交失败',
-						icon: 'none'
-					});
-				}
-			},
+
 		}
 	}
 </script>

+ 1 - 1
pages_shopping/live/goods.vue

@@ -323,7 +323,7 @@
 								url: '/pages_shopping/live/confirmCreateOrder?&orderKey=' + this.orderKey +
 									'&liveId=' +
 									this.liveId + '&productId=' + this.productId + '&totalNum=' + this
-									.totalNum + '&price=' + (this.goosDetail.price || 0)
+									.totalNum 
 							})
 						} else {
 							uni.showToast({

+ 428 - 389
pages_user/user/addAddress.vue

@@ -1,407 +1,446 @@
 <template>
 	<view class="content">
-		<view class="inner">
-			<view class="address-box">
-				<textarea class="textarea" v-model="content"  placeholder="请粘贴或输入文本,点击'识别'自动识别姓名、电话、地址,格式:深圳市龙岗区坂田街道长坑路西2巷2号202 黄大大 18888888888"   />
-				<view  class="btns" >
-					<view  class="btn parse" @click="parseAddress()">识别</view>
-				</view>
-			</view>
-			<view class="form-box">
-				
-				<view class="form-item">
-					<text class="label">联系人</text>
-					<input type="text" v-model="form.realName" maxlength="10" placeholder="姓名" class="form-input" />
-				</view>
-				<view class="form-item">
-					<text class="label">手机号</text>
-					<input type="number" v-model="form.phone" maxlength="11"  placeholder="手机号" class="form-input" />
-				</view>
-				<view class="form-item">
-					<text class="label">所在地区</text>
-					<picker :value="multiIndex"  class="birth-picker"  mode="multiSelector" range-key="n" :range="addressList" @change="pickerChange"  @columnchange="pickerColumnchange">
-						<view class="right-box">
-							<view class="input-box">
-								<input type="text" v-model="form.address" placeholder="请选择" class="form-input" disabled="disabled" />
-							</view>
-							<image class="arrow" src="../../static/images/arrow_gray.png" mode=""></image>
-						</view>
-					</picker>
-				</view>
-				<view class="form-item">
-					<text class="label">详细地址</text>
-					<textarea class="form-textarea" v-model="form.detail" placeholder="请输入详细地址"   />
-				</view>
-			</view>
-			<!-- 设为默认地址 -->
-			<view class="setting-box">
-				<text class="label">设为默认地址</text>
-				<evan-switch v-model="isDefault" activeColor="#2BC7B9" inactiveColor="rgba(0, 0, 0, 0.1)"></evan-switch>
+		<view class="inner">
+			<view class="address-box">
+				<textarea class="textarea" v-model="content"
+					placeholder="请粘贴或输入文本,点击'识别'自动识别姓名、电话、地址,格式:深圳市龙岗区坂田街道长坑路西2巷2号202 黄大大 18888888888" />
+				<view class="btns">
+					<view class="btn parse" @click="parseAddress()">识别</view>
+				</view>
 			</view>
-		</view>
-		<view class="btn-box">
-			<view class="sub-btn" @click="submit()">保存地址</view>
+			<view class="form-box">
+
+				<view class="form-item">
+					<text class="label">联系人</text>
+					<input type="text" v-model="form.realName" maxlength="10" placeholder="姓名" class="form-input" />
+				</view>
+				<view class="form-item">
+					<text class="label">手机号</text>
+					<input type="number" v-model="form.phone" maxlength="11" placeholder="手机号" class="form-input" />
+				</view>
+				<view class="form-item">
+					<text class="label">所在地区</text>
+					<picker :value="multiIndex" class="birth-picker" mode="multiSelector" range-key="n"
+						:range="addressList" @change="pickerChange" @columnchange="pickerColumnchange">
+						<view class="right-box">
+							<view class="input-box">
+								<input type="text" v-model="form.address" placeholder="请选择" class="form-input"
+									disabled="disabled" />
+							</view>
+							<image class="arrow" src="../../static/images/arrow_gray.png" mode=""></image>
+						</view>
+					</picker>
+				</view>
+				<view class="form-item">
+					<text class="label">详细地址</text>
+					<textarea class="form-textarea" v-model="form.detail" placeholder="请输入详细地址" />
+				</view>
+			</view>
+			<!-- 设为默认地址 -->
+			<view class="setting-box">
+				<text class="label">设为默认地址</text>
+				<evan-switch v-model="isDefault" activeColor="#2BC7B9" inactiveColor="rgba(0, 0, 0, 0.1)"></evan-switch>
+			</view>
+		</view>
+		<view class="btn-box">
+			<view class="sub-btn" @click="submit()">保存地址</view>
 		</view>
 	</view>
 </template>
 
-<script>
-	import {parseAddress,getCity,getCitys,getAddressById,addAddress,editAddress} from '@/api/address'
+<script>
+	import {
+		parseAddress,
+		getCity,
+		getCitys,
+		getAddressById,
+		addAddress,
+		editAddress
+	} from '@/api/address'
 	import EvanSwitch from '@/components/evan-switch/evan-switch.vue'
-	export default {
-		components: {
-			EvanSwitch
+	export default {
+		components: {
+			EvanSwitch
 		},
 		data() {
-			return {
-				content:null,
-				type:null,
-				id:null,
-				isDefault: false,
-				addressList:[[],[],[]],
-				multiIndex:[0,0,0],
-				address:[],
-				form: {
-					realName:null,
-					phone:null,
-					detail:null,
-					address: null,
-					isDefault: 0,
+			return {
+				content: null,
+				type: null,
+				id: null,
+				isDefault: false,
+				addressList: [
+					[],
+					[],
+					[]
+				],
+				multiIndex: [0, 0, 0],
+				address: [],
+				form: {
+					realName: null,
+					phone: null,
+					detail: null,
+					address: null,
+					isDefault: 0,
 				}
 			}
-		},
-		onLoad(option) {
-			this.type=option.type;
-			if(this.type=='edit'){
-				uni.setNavigationBarTitle({
-				  title:"修改收货地址"
-				})
-				this.id=option.id;
-				this.getAddressById();
-			}
-			else{
-				uni.setNavigationBarTitle({
-				  title:"新增收货地址"
-				})
-			}
-			this.getCitys()
-		},
-		methods: {
-			parseAddress(){
-				if(this.content==null||this.content==""){
-					uni.showToast({
						icon:'none',
						title: '请输入地址信息',
-					});
-					return;
-				}
-				var data={content:this.content};
-				parseAddress(data).then(
-					res => {
-						if(res.code==200){
-							this.form.realName=res.data.name
-							this.form.phone=res.data.mobile
-							this.form.address=res.data.provinceName+res.data.cityName+res.data.expAreaName
-							this.form.province=res.data.provinceName
-							this.form.city=res.data.cityName
-							this.form.district=res.data.expAreaName
-							this.form.detail=res.data.streetName+res.data.address
-							this.form.detail=this.form.detail.replace(/\s+/g,"");
-						}else{
-							uni.showToast({
-								icon:'none',
-								title: res.msg,
-							});
-						}
-					},
-					rej => {}
-				);
-			},
-			getAddressById(){
-				var data={id:this.id};
-				getAddressById(data).then(
-					res => {
-						if(res.code==200){
-							  this.form=res.data;
-							  this.isDefault=this.form.isDefault==1?true:false;
-							  this.form.address=this.form.province+this.form.city+this.form.district
-						}else{
-							uni.showToast({
-								icon:'none',
-								title: res.msg,
-							});
-						}
-					},
-					rej => {}
-				);
-			},
-			submit(){
-				if(this.type=="add"){
-					this.addAddress()
-				}
-				else if(this.type=="edit"){
-					this.editAddress()
-				}
-				
-			},
-			editAddress(){
-				this.form.isDefault=this.isDefault?1:0
-				editAddress(this.form).then(
-					res => {
-						if(res.code==200){
-							 uni.showToast({
-							 	icon:'success',
-							 	title: "操作成功",
-							 });
-							 setTimeout(function() {
-								 uni.$emit('refreshAddress');
-								 uni.navigateBack({
-									 delta: 1
-								 })
-							 }, 500);
-						}else{
-							uni.showToast({
-								icon:'none',
-								title: res.msg,
-							});
-						}
-					},
-					rej => {}
-				);
-			},
-			addAddress(){
-				this.form.isDefault=this.isDefault?1:0
-				addAddress(this.form).then(
-					res => {
-						if(res.code==200){
-							 uni.showToast({
-							 	icon:'success',
-							 	title: "操作成功",
-							 });
-							 setTimeout(function() {
-								 uni.$emit('refreshAddress');
-								 uni.navigateBack({
-									 delta: 1
-								 })
-							 }, 500);
-						}else{
-							uni.showToast({
-								icon:'none',
-								title: res.msg,
-							});
-						}
-					},
-					rej => {}
-				);
-			},
-			// 地区选择
-			pickerChange(e) {
-				this.multiIndex = e.detail.value;
-				// 数组内的下标
-				// 获取一级类目
-				// 获取二级类目
-				// 获取三级类目
-				this.form.address=this.addressList[0][this.multiIndex[0]].n+this.addressList[1][this.multiIndex[1]].n+this.addressList[2][this.multiIndex[2]].n
-				this.form.province=this.addressList[0][this.multiIndex[0]].n
-				this.form.city=this.addressList[1][this.multiIndex[1]].n
-				this.form.district=this.addressList[2][this.multiIndex[2]].n
-				this.form.cityId=this.addressList[1][this.multiIndex[1]].v;
-				
-				 
-			},
-			pickerColumnchange(e){
-				// 第一列滑动
-				if(e.detail.column === 0){
-					this.multiIndex[0] =  e.detail.value
-					// console.log('第一列滑动');
-					this.addressList[1] = this.address[this.multiIndex[0]].c;
-					this.addressList[2] = this.address[this.multiIndex[0]].c[0].c
-					// 第一列滑动  第二列 和第三列 都变为第一个
-					this.multiIndex.splice(1, 1, 0)
-					this.multiIndex.splice(2, 1, 0)
-				}
-				// 第二列滑动
-				if(e.detail.column === 1){
-					this.multiIndex[1] =  e.detail.value
-					// console.log('第二列滑动');
-					// console.log(this.multiIndex)
-					this.addressList[2] = this.address[this.multiIndex[0]].c[this.multiIndex[1]].c
-					// 第二列 滑动 第三列 变成第一个
-					this.multiIndex.splice(2, 1, 0)
-				}
-				// 第三列滑动
-				if(e.detail.column === 2){
-					this.multiIndex[2] =  e.detail.value
-				}
-			},
-			getCitys(){
-				getCitys().then(
-					res => {
-						if(res.code==200){
-							this.address=res.data
-							for(var i=0; i<this.address.length; i++){
-								this.addressList[0].push(this.address[i])
-							}
-							for(var i=0; i<this.address[0].c.length; i++){
-								this.addressList[1].push(this.address[0].c[i])
-							}
-							for(var i=0; i<this.address[0].c[0].c.length; i++){
-								this.addressList[2].push(this.address[0].c[0].c[i])
-							}
-						}else{
-							uni.showToast({
-								icon:'none',
-								title: "请求失败",
-							});
-						}
-					},
-					rej => {}
-				);
-			}
+		},
+		onLoad(option) {
+			this.getCitys()
+			this.type = option.type;
+			if (this.type == 'edit') {
+				uni.setNavigationBarTitle({
+					title: "修改收货地址"
+				})
+				this.id = option.id;
+				this.getAddressById();
+			} else {
+				uni.setNavigationBarTitle({
+					title: "新增收货地址"
+				})
+			}
+
+		},
+		methods: {
+			parseAddress() {
+				if (this.content == null || this.content == "") {
+					uni.showToast({
+						icon: 'none',
+						title: '请输入地址信息',
+					});
+					return;
+				}
+				var data = {
+					content: this.content
+				};
+				parseAddress(data).then(
+					res => {
+						if (res.code == 200) {
+							this.form.realName = res.data.name
+							this.form.phone = res.data.mobile
+							this.form.address = res.data.provinceName + res.data.cityName + res.data.expAreaName
+							this.form.province = res.data.provinceName
+							this.form.city = res.data.cityName
+							this.form.district = res.data.expAreaName
+							this.form.detail = res.data.streetName + res.data.address
+							this.form.detail = this.form.detail.replace(/\s+/g, "");
+						} else {
+							uni.showToast({
+								icon: 'none',
+								title: res.msg,
+							});
+						}
+					},
+					rej => {}
+				);
+			},
+			getAddressById() {
+				var data = {
+					id: this.id
+				};
+				getAddressById(data).then(
+					res => {
+						if (res.code == 200) {
+							this.form = res.data;
+							this.isDefault = this.form.isDefault == 1 ? true : false;
+							this.form.address = this.form.province + this.form.city + this.form.district
+						} else {
+							uni.showToast({
+								icon: 'none',
+								title: res.msg,
+							});
+						}
+					},
+					rej => {}
+				);
+			},
+			submit() {
+				if (this.type == "add") {
+					this.addAddress()
+				} else if (this.type == "edit") {
+					this.editAddress()
+				}
+
+			},
+			editAddress() {
+				this.form.isDefault = this.isDefault ? 1 : 0
+				editAddress(this.form).then(
+					res => {
+						if (res.code == 200) {
+							uni.showToast({
+								icon: 'success',
+								title: "操作成功",
+							});
+							setTimeout(function() {
+								uni.$emit('refreshAddress');
+								uni.navigateBack({
+									delta: 1
+								})
+							}, 500);
+						} else {
+							uni.showToast({
+								icon: 'none',
+								title: res.msg,
+							});
+						}
+					},
+					rej => {}
+				);
+			},
+			addAddress() {
+				this.form.isDefault = this.isDefault ? 1 : 0
+				addAddress(this.form).then(
+					res => {
+						if (res.code == 200) {
+							uni.showToast({
+								icon: 'success',
+								title: "操作成功",
+							});
+							setTimeout(function() {
+								uni.$emit('refreshAddress');
+								uni.navigateBack({
+									delta: 1
+								})
+							}, 500);
+						} else {
+							uni.showToast({
+								icon: 'none',
+								title: res.msg,
+							});
+						}
+					},
+					rej => {}
+				);
+			},
+			// 地区选择
+			pickerChange(e) {
+				this.multiIndex = e.detail.value;
+				// 数组内的下标
+				// 获取一级类目
+				// 获取二级类目
+				// 获取三级类目
+				this.form.address = this.addressList[0][this.multiIndex[0]].n + this.addressList[1][this.multiIndex[1]].n +
+					this.addressList[2][this.multiIndex[2]].n
+				this.form.province = this.addressList[0][this.multiIndex[0]].n
+				this.form.city = this.addressList[1][this.multiIndex[1]].n
+				this.form.district = this.addressList[2][this.multiIndex[2]].n
+				this.form.cityId = this.addressList[1][this.multiIndex[1]].v;
+
+
+			},
+			pickerColumnchange(e) {
+				// 第一列滑动
+				if (e.detail.column === 0) {
+					this.multiIndex[0] = e.detail.value
+					// console.log('第一列滑动');
+					this.addressList[1] = this.address[this.multiIndex[0]].c;
+					this.addressList[2] = this.address[this.multiIndex[0]].c[0].c
+					// 第一列滑动  第二列 和第三列 都变为第一个
+					this.multiIndex.splice(1, 1, 0)
+					this.multiIndex.splice(2, 1, 0)
+				}
+				// 第二列滑动
+				if (e.detail.column === 1) {
+					this.multiIndex[1] = e.detail.value
+					// console.log('第二列滑动');
+					// console.log(this.multiIndex)
+					this.addressList[2] = this.address[this.multiIndex[0]].c[this.multiIndex[1]].c
+					// 第二列 滑动 第三列 变成第一个
+					this.multiIndex.splice(2, 1, 0)
+				}
+				// 第三列滑动
+				if (e.detail.column === 2) {
+					this.multiIndex[2] = e.detail.value
+				}
+			},
+			getCitys() {
+				getCitys().then(
+					res => {
+						if (res.code == 200) {
+							this.address = res.data
+							for (var i = 0; i < this.address.length; i++) {
+								this.addressList[0].push(this.address[i])
+							}
+							for (var i = 0; i < this.address[0].c.length; i++) {
+								this.addressList[1].push(this.address[0].c[i])
+							}
+							for (var i = 0; i < this.address[0].c[0].c.length; i++) {
+								this.addressList[2].push(this.address[0].c[0].c[i])
+							}
+						} else {
+							uni.showToast({
+								icon: 'none',
+								title: "请求失败",
+							});
+						}
+					},
+					rej => {}
+				);
+			}
 		}
 	}
 </script>
 
 <style lang="scss">
-	page{
-		height: 100%;
-	}
-	.content{
-		height: 100%;
-		display: flex;
-		flex-direction: column;
-		justify-content: space-between;
-		
-		.inner{
-			height: calc(100% - 120upx);
-			padding: 20upx;
-			.address-box{
-				padding: 30upx;
-				background: #FFFFFF;
-				border-radius: 16upx;
-				margin-bottom: 20upx;
-				position: relative;
-				.textarea{
-					width: 100%;
-					height: 200upx;
-					font-size: 30upx;
-					color: #999999;
-					padding-bottom:100rpx;
-				}
-				.btns{
-					right:10rpx;
-					bottom:10rpx;
-					position: absolute;
-					.btn{
-						width: 155upx;
-						height: 64upx;
-						line-height: 64upx;
-						font-size: 26upx;
-						font-family: PingFang SC;
-						font-weight: 500;
-						text-align: center;
-						border-radius: 32upx;
-						&.parse{
-							background: #2BC7B9;
-							color: #FFFFFF;
-						}
-					}
-				}
-			}
-			.form-box{
-				padding: 0 30upx;
-				background: #FFFFFF;
-				border-radius: 16upx;
-				.form-item{
-					padding: 30upx 0;
-					display: flex;
-					align-items: flex-start;
-					border-bottom: 1px solid #F1F1F1;
-					&:last-child{
-						border-bottom: none;
-					}
-					.label{
-						width: 180upx;
-						text-align: left;
-						font-size: 30upx;
-						line-height: 44upx;
-						font-family: PingFang SC;
-						font-weight: 500;
-						color: #222222;
-						flex-shrink: 0;
-					}
-					input{
-						text-align: left;
-					}
-					.form-input{
-						font-size: 30upx;
-						font-family: PingFang SC;
-						font-weight: 500;
-						color: #999999;
-						text-align: left;
-					}
-					.form-textarea{
-						font-size: 30upx;
-						color: #999999;
-						height: 100upx;
-						padding: 4upx 0;
-					}
-					 
-					.birth-picker {
-						flex: 1;
-						display: flex;
-						align-items: center;
-						
-						.right-box{
-							width: 100%;
-							display: flex;
-							align-items: center;
-							.input-box{
-								width: 470upx;
-							}
-							.arrow{
-								width: 13upx;
-								height: 23upx;
-								margin-left: 20upx;
-							}
-						}
-					}
-				}
-			}
-			.setting-box{
-				height: 88upx;
-				background: #FFFFFF;
-				border-radius: 16upx;
-				margin-top: 20upx;
-				padding: 0 30upx;
-				display: flex;
-				align-items: center;
-				justify-content: space-between;
-				.label{
-					font-size: 28upx;
-					font-family: PingFang SC;
-					font-weight: 500;
-					color: #111111;
-				}
-			}
-		}
-		.btn-box{
-			height: 120upx;
-			padding: 0 30upx;
-			display: flex;
-			align-items: center;
-			justify-content: center;
-			background: #FFFFFF;
-			.sub-btn{
-				width: 100%;
-				height: 88upx;
-				line-height: 88upx;
-				text-align: center;
-				font-size: 30upx;
-				font-family: PingFang SC;
-				font-weight: bold;
-				color: #FFFFFF;
-				background: #2BC7B9;
-				border-radius: 44upx;
-			}
-		}
+	page {
+		height: 100%;
+	}
+
+	.content {
+		height: 100%;
+		display: flex;
+		flex-direction: column;
+		justify-content: space-between;
+
+		.inner {
+			height: calc(100% - 120upx);
+			padding: 20upx;
+
+			.address-box {
+				padding: 30upx;
+				background: #FFFFFF;
+				border-radius: 16upx;
+				margin-bottom: 20upx;
+				position: relative;
+
+				.textarea {
+					width: 100%;
+					height: 200upx;
+					font-size: 30upx;
+					color: #999999;
+					padding-bottom: 100rpx;
+				}
+
+				.btns {
+					right: 10rpx;
+					bottom: 10rpx;
+					position: absolute;
+
+					.btn {
+						width: 155upx;
+						height: 64upx;
+						line-height: 64upx;
+						font-size: 26upx;
+						font-family: PingFang SC;
+						font-weight: 500;
+						text-align: center;
+						border-radius: 32upx;
+
+						&.parse {
+							background: #2BC7B9;
+							color: #FFFFFF;
+						}
+					}
+				}
+			}
+
+			.form-box {
+				padding: 0 30upx;
+				background: #FFFFFF;
+				border-radius: 16upx;
+
+				.form-item {
+					padding: 30upx 0;
+					display: flex;
+					align-items: flex-start;
+					border-bottom: 1px solid #F1F1F1;
+
+					&:last-child {
+						border-bottom: none;
+					}
+
+					.label {
+						width: 180upx;
+						text-align: left;
+						font-size: 30upx;
+						line-height: 44upx;
+						font-family: PingFang SC;
+						font-weight: 500;
+						color: #222222;
+						flex-shrink: 0;
+					}
+
+					input {
+						text-align: left;
+					}
+
+					.form-input {
+						font-size: 30upx;
+						font-family: PingFang SC;
+						font-weight: 500;
+						color: #999999;
+						text-align: left;
+					}
+
+					.form-textarea {
+						font-size: 30upx;
+						color: #999999;
+						height: 100upx;
+						padding: 4upx 0;
+					}
+
+					.birth-picker {
+						flex: 1;
+						display: flex;
+						align-items: center;
+
+						.right-box {
+							width: 100%;
+							display: flex;
+							align-items: center;
+
+							.input-box {
+								width: 470upx;
+							}
+
+							.arrow {
+								width: 13upx;
+								height: 23upx;
+								margin-left: 20upx;
+							}
+						}
+					}
+				}
+			}
+
+			.setting-box {
+				height: 88upx;
+				background: #FFFFFF;
+				border-radius: 16upx;
+				margin-top: 20upx;
+				padding: 0 30upx;
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+
+				.label {
+					font-size: 28upx;
+					font-family: PingFang SC;
+					font-weight: 500;
+					color: #111111;
+				}
+			}
+		}
+
+		.btn-box {
+			height: 120upx;
+			padding: 0 30upx;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			background: #FFFFFF;
+
+			.sub-btn {
+				width: 100%;
+				height: 88upx;
+				line-height: 88upx;
+				text-align: center;
+				font-size: 30upx;
+				font-family: PingFang SC;
+				font-weight: bold;
+				color: #FFFFFF;
+				background: #2BC7B9;
+				border-radius: 44upx;
+			}
+		}
 	}
-</style>
+</style>

BIN
static/images/coupon.png


BIN
static/images/coupon_bg.png


BIN
static/images/coupon_top.png


BIN
static/images/no-prize.png


+ 12 - 0
uniCloud-tcb/database/JQL查询.jql

@@ -0,0 +1,12 @@
+// 本文件用于,使用JQL语法操作项目关联的uniCloud空间的数据库,方便开发调试和远程数据库管理
+// 编写clientDB的js API(也支持常规js语法,比如var),可以对云数据库进行增删改查操作。不支持uniCloud-db组件写法
+// 可以全部运行,也可以选中部分代码运行。点击工具栏上的运行按钮或者按下【F5】键运行代码
+// 如果文档中存在多条JQL语句,只有最后一条语句生效
+// 如果混写了普通js,最后一条语句需是数据库操作语句
+// 此处代码运行不受DB Schema的权限控制,移植代码到实际业务中注意在schema中配好permission
+// 不支持clientDB的action
+// 数据库查询有最大返回条数限制,详见:https://uniapp.dcloud.net.cn/uniCloud/cf-database.html#limit
+// 详细JQL语法,请参考:https://uniapp.dcloud.net.cn/uniCloud/jql.html
+
+// 下面示例查询uni-id-users表的所有数据
+db.collection('uni-id-users').get();

+ 6 - 0
uni_modules/uni-config-center/changelog.md

@@ -0,0 +1,6 @@
+## 0.0.3(2022-11-11)
+- 修复 config 方法获取根节点为数组格式配置时错误的转化为了对象的Bug
+## 0.0.2(2021-04-16)
+- 修改插件package信息
+## 0.0.1(2021-03-15)
+- 初始化项目

+ 81 - 0
uni_modules/uni-config-center/package.json

@@ -0,0 +1,81 @@
+{
+  "id": "uni-config-center",
+  "displayName": "uni-config-center",
+  "version": "0.0.3",
+  "description": "uniCloud 配置中心",
+  "keywords": [
+    "配置",
+    "配置中心"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "",
+    "type": "unicloud-template-function"
+  },
+  "directories": {
+    "example": "../../../scripts/dist"
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "u",
+          "app-nvue": "u"
+        },
+        "H5-mobile": {
+          "Safari": "u",
+          "Android Browser": "u",
+          "微信浏览器(Android)": "u",
+          "QQ浏览器(Android)": "u"
+        },
+        "H5-pc": {
+          "Chrome": "u",
+          "IE": "u",
+          "Edge": "u",
+          "Firefox": "u",
+          "Safari": "u"
+        },
+        "小程序": {
+          "微信": "u",
+          "阿里": "u",
+          "百度": "u",
+          "字节跳动": "u",
+          "QQ": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "u"
+        }
+      }
+    }
+  }
+}

+ 93 - 0
uni_modules/uni-config-center/readme.md

@@ -0,0 +1,93 @@
+# 为什么使用uni-config-center
+
+实际开发中很多插件需要配置文件才可以正常运行,如果每个插件都单独进行配置的话就会产生下面这样的目录结构
+
+```bash
+cloudfunctions
+└─────common 公共模块
+        ├─plugin-a // 插件A对应的目录
+        │  ├─index.js
+        │  ├─config.json // plugin-a对应的配置文件
+        │  └─other-file.cert  // plugin-a依赖的其他文件
+        └─plugin-b // plugin-b对应的目录
+           ├─index.js
+           └─config.json // plugin-b对应的配置文件
+```
+
+假设插件作者要发布一个项目模板,里面使用了很多需要配置的插件,无论是作者发布还是用户使用都是一个大麻烦。
+
+uni-config-center就是用了统一管理这些配置文件的,使用uni-config-center后的目录结构如下
+
+```bash
+cloudfunctions
+└─────common 公共模块
+        ├─plugin-a // 插件A对应的目录
+        │  └─index.js
+        ├─plugin-b // plugin-b对应的目录
+        │  └─index.js
+        └─uni-config-center
+           ├─index.js // config-center入口文件
+           ├─plugin-a
+           │  ├─config.json  // plugin-a对应的配置文件
+           │  └─other-file.cert  // plugin-a依赖的其他文件
+           └─plugin-b
+              └─config.json  // plugin-b对应的配置文件
+```
+
+使用uni-config-center后的优势
+
+- 配置文件统一管理,分离插件主体和配置信息,更新插件更方便
+- 支持对config.json设置schema,插件使用者在HBuilderX内编写config.json文件时会有更好的提示(后续HBuilderX会提供支持)
+
+# 用法
+
+在要使用uni-config-center的公共模块或云函数内引入uni-config-center依赖,请参考:[使用公共模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
+
+```js
+const createConfig = require('uni-config-center')
+
+const uniIdConfig = createConfig({
+    pluginId: 'uni-id', // 插件id
+    defaultConfig: { // 默认配置
+        tokenExpiresIn: 7200,
+        tokenExpiresThreshold: 600,
+    },
+    customMerge: function(defaultConfig, userConfig) { // 自定义默认配置和用户配置的合并规则,不设置的情况侠会对默认配置和用户配置进行深度合并
+        // defaudltConfig 默认配置
+        // userConfig 用户配置
+        return Object.assign(defaultConfig, userConfig)
+    }
+})
+
+
+// 以如下配置为例
+// {
+//   "tokenExpiresIn": 7200,
+//   "passwordErrorLimit": 6,
+//   "bindTokenToDevice": false,
+//   "passwordErrorRetryTime": 3600,
+//   "app-plus": {
+//     "tokenExpiresIn": 2592000
+//   },
+//   "service": {
+//     "sms": {
+//       "codeExpiresIn": 300
+//     }
+//   }
+// }
+
+// 获取配置
+uniIdConfig.config() // 获取全部配置,注意:uni-config-center内不存在对应插件目录时会返回空对象
+uniIdConfig.config('tokenExpiresIn') // 指定键值获取配置,返回:7200
+uniIdConfig.config('service.sms.codeExpiresIn') // 指定键值获取配置,返回:300
+uniIdConfig.config('tokenExpiresThreshold', 600) // 指定键值获取配置,如果不存在则取传入的默认值,返回:600
+
+// 获取文件绝对路径
+uniIdConfig.resolve('custom-token.js') // 获取uni-config-center/uni-id/custom-token.js文件的路径
+
+// 引用文件(require)
+uniIDConfig.requireFile('custom-token.js') // 使用require方式引用uni-config-center/uni-id/custom-token.js文件。文件不存在时返回undefined,文件内有其他错误导致require失败时会抛出错误。
+
+// 判断是否包含某文件
+uniIDConfig.hasFile('custom-token.js') // 配置目录是否包含某文件,true: 文件存在,false: 文件不存在
+```

File diff suppressed because it is too large
+ 0 - 0
uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/index.js


+ 13 - 0
uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/package.json

@@ -0,0 +1,13 @@
+{
+    "name": "uni-config-center",
+    "version": "0.0.3",
+    "description": "配置中心",
+    "main": "index.js",
+    "keywords": [],
+    "author": "DCloud",
+    "license": "Apache-2.0",
+    "origin-plugin-dev-name": "uni-config-center",
+    "origin-plugin-version": "0.0.3",
+    "plugin-dev-name": "uni-config-center",
+    "plugin-version": "0.0.3"
+}

+ 36 - 0
uni_modules/uni-id-common/changelog.md

@@ -0,0 +1,36 @@
+## 1.0.18(2024-07-08)
+- checkToken时如果传入的token为空则返回uni-id-check-token-failed错误码以便uniIdRouter能正常跳转
+## 1.0.17(2024-04-26)
+- 兼容uni-app-x对客户端uniPlatform的调整(uni-app-x内uniPlatform区分app-android、app-ios)
+## 1.0.16(2023-04-25)
+- 新增maxTokenLength配置,用于限制数据库用户记录token数组的最大长度
+## 1.0.15(2023-04-06)
+- 修复部分语言国际化出错的Bug
+## 1.0.14(2023-03-07)
+- 修复 admin用户包含其他角色时未包含在token的Bug
+## 1.0.13(2022-07-21)
+- 修复 创建token时未传角色权限信息生成的token不正确的bug
+## 1.0.12(2022-07-15)
+- 提升与旧版本uni-id的兼容性(补充读取配置文件时回退平台app-plus、h5),但是仍推荐使用新平台名进行配置(app、web)
+## 1.0.11(2022-07-14)
+- 修复 部分情况下报`read property 'reduce' of undefined`的错误
+## 1.0.10(2022-07-11)
+- 将token存储在用户表的token字段内,与旧版本uni-id保持一致
+## 1.0.9(2022-07-01)
+- checkToken兼容token内未缓存角色权限的情况,此时将查库获取角色权限
+## 1.0.8(2022-07-01)
+- 修复clientDB默认依赖时部分情况下获取不到uni-id配置的Bug
+## 1.0.7(2022-06-30)
+- 修复config文件不合法时未抛出具体错误的Bug
+## 1.0.6(2022-06-28)
+- 移除插件内的数据表schema
+## 1.0.5(2022-06-27)
+- 修复使用多应用配置时报`Cannot read property 'appId' of undefined`的Bug
+## 1.0.4(2022-06-27)
+- 修复使用自定义token内容功能报错的Bug [详情](https://ask.dcloud.net.cn/question/147945)
+## 1.0.2(2022-06-23)
+- 对齐旧版本uni-id默认配置
+## 1.0.1(2022-06-22)
+- 补充对uni-config-center的依赖
+## 1.0.0(2022-06-21)
+- 提供uni-id token创建、校验、刷新接口,简化旧版uni-id公共模块

+ 84 - 0
uni_modules/uni-id-common/package.json

@@ -0,0 +1,84 @@
+{
+	"id": "uni-id-common",
+	"displayName": "uni-id-common",
+	"version": "1.0.18",
+	"description": "包含uni-id token生成、校验、刷新功能的云函数公共模块",
+	"keywords": [
+        "uni-id-common",
+        "uniCloud",
+        "token",
+        "权限"
+    ],
+	"repository": "https://gitcode.net/dcloud/uni-id-common",
+    "engines": {
+	},
+    "dcloudext": {
+        "sale": {
+			"regular": {
+				"price": 0
+			},
+			"sourcecode": {
+				"price": 0
+			}
+		},
+		"contact": {
+			"qq": ""
+		},
+		"declaration": {
+			"ads": "无",
+			"data": "无",
+			"permissions": "无"
+		},
+        "npmurl": "",
+        "type": "unicloud-template-function"
+	},
+	"uni_modules": {
+		"dependencies": ["uni-config-center"],
+		"encrypt": [],
+		"platforms": {
+			"cloud": {
+				"tcb": "y",
+                "aliyun": "y",
+                "alipay": "n"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "u",
+					"vue3": "u"
+				},
+				"App": {
+					"app-vue": "u",
+					"app-nvue": "u"
+				},
+				"H5-mobile": {
+					"Safari": "u",
+					"Android Browser": "u",
+					"微信浏览器(Android)": "u",
+					"QQ浏览器(Android)": "u"
+				},
+				"H5-pc": {
+					"Chrome": "u",
+					"IE": "u",
+					"Edge": "u",
+					"Firefox": "u",
+					"Safari": "u"
+				},
+				"小程序": {
+					"微信": "u",
+					"阿里": "u",
+					"百度": "u",
+					"字节跳动": "u",
+					"QQ": "u",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+                    "京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+	}
+}

+ 3 - 0
uni_modules/uni-id-common/readme.md

@@ -0,0 +1,3 @@
+# uni-id-common
+
+文档请参考:[uni-id-common](https://uniapp.dcloud.net.cn/uniCloud/uni-id-common.html)

File diff suppressed because it is too large
+ 0 - 0
uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common/index.js


+ 20 - 0
uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common/package.json

@@ -0,0 +1,20 @@
+{
+    "name": "uni-id-common",
+    "version": "1.0.18",
+    "description": "uni-id token生成、校验、刷新",
+    "main": "index.js",
+    "homepage": "https:\/\/uniapp.dcloud.io\/uniCloud\/uni-id-common.html",
+    "repository": {
+        "type": "git",
+        "url": "git+https:\/\/gitee.com\/dcloud\/uni-id-common.git"
+    },
+    "author": "DCloud",
+    "license": "Apache-2.0",
+    "dependencies": {
+        "uni-config-center": "file:..\/..\/..\/..\/..\/uni-config-center\/uniCloud\/cloudfunctions\/common\/uni-config-center"
+    },
+    "origin-plugin-dev-name": "uni-id-common",
+    "origin-plugin-version": "1.0.18",
+    "plugin-dev-name": "uni-id-common",
+    "plugin-version": "1.0.18"
+}

+ 27 - 0
uni_modules/uni-open-bridge-common/changelog.md

@@ -0,0 +1,27 @@
+## 1.2.1(2024-09-13)
+- 修复 微信小程序平台 当微信的服务器返回`encrypt_key`的过期时间`expire_in`为`0`时特殊处理为`1`天,否者可能无法验证客户端请求携带的版本
+## 1.2.0(2023-04-27)
+- 重要 微信小程序平台 使用微信新增 API getStableAccessToken 获取 access_token, access_token 有效期内重复调用该接口不会更新 access_token, [详情](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getStableAccessToken.html)
+## 1.1.5(2023-03-27)
+- 修复 微信小程序平台 某些情况下 encrypt_key 插入错误的问题
+## 1.1.4(2023-03-13)
+- 修复 平台 weixin-web
+## 1.1.3(2023-03-13)
+- 新增 支持旧版本 uni-id 配置
+- 新增 支持平台 weixin-app|qq-mp|qq-app
+## 1.1.2(2023-02-28)
+- 新增 config 配置错误提示语
+## 1.1.1(2023-02-28)
+- 新增 支持 provider 参数,和 platform 保持一致
+## 1.1.0(2023-02-27)
+- 重要更新 调整数据库key格式,兼容旧版本API,如果开发者通过手动拼接key查询数据库需要修改现有逻辑
+  + 原格式: uni-id:[dcloudAppid]:[platform]:[openid]:[access-token|user-access-token|session-key|encrypt-key-version|ticket]
+  + 新格式: uni-id:[provider]:[appid]:[openid]:[access-token|user-access-token|session-key|encrypt-key-version|ticket]
+## 1.0.4(2022-09-21)
+- 新增 支持使用阿里云固定IP获取微信公众号H5凭据 access_token、ticket,开发者需要在微信公众平台配置阿里云固定IP,[固定IP详情](https://uniapp.dcloud.net.cn/uniCloud/cf-functions.html#aliyun-eip)
+## 1.0.3(2022-09-06)
+- 修复 过期时间问题,容错 AccessToken 默认 fallback 逻辑,当微信服务器没有返回过期时间时设置为2小时后过期
+## 1.0.2(2022-09-02)
+- 新增 依赖数据表schema opendb-open-data
+## 1.0.0(2022-08-22)
+- 首次发布

+ 84 - 0
uni_modules/uni-open-bridge-common/package.json

@@ -0,0 +1,84 @@
+{
+  "id": "uni-open-bridge-common",
+  "displayName": "uni-open-bridge-common",
+  "version": "1.2.1",
+  "description": "统一接管微信等三方平台认证凭据",
+  "keywords": [
+    "access_token",
+    "session_key",
+    "ticket"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.5.2"
+  },
+  "dcloudext": {
+    "type": "unicloud-template-function",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y",
+        "alipay": "n"
+      },
+      "client": {
+        "Vue": {
+          "vue2": "u",
+          "vue3": "u"
+        },
+        "App": {
+          "app-vue": "u",
+          "app-nvue": "u"
+        },
+        "H5-mobile": {
+          "Safari": "u",
+          "Android Browser": "u",
+          "微信浏览器(Android)": "u",
+          "QQ浏览器(Android)": "u"
+        },
+        "H5-pc": {
+          "Chrome": "u",
+          "IE": "u",
+          "Edge": "u",
+          "Firefox": "u",
+          "Safari": "u"
+        },
+        "小程序": {
+          "微信": "u",
+          "阿里": "u",
+          "百度": "u",
+          "字节跳动": "u",
+          "QQ": "u",
+          "钉钉": "u",
+          "快手": "u",
+          "飞书": "u",
+          "京东": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 5 - 0
uni_modules/uni-open-bridge-common/readme.md

@@ -0,0 +1,5 @@
+# uni-open-bridge-common
+
+`uni-open-bridge-common` 是统一接管微信等三方平台认证凭据(包括但不限于`access_token`、`session_key`、`encrypt_key`、`ticket`)的开源库。
+
+文档链接 [https://uniapp.dcloud.net.cn/uniCloud/uni-open-bridge#common](https://uniapp.dcloud.net.cn/uniCloud/uni-open-bridge#common)

+ 26 - 0
uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/bridge-error.js

@@ -0,0 +1,26 @@
+'use strict';
+
+class BridgeError extends Error {
+
+  constructor(code, message) {
+    super(message)
+
+    this._code = code
+  }
+
+  get code() {
+    return this._code
+  }
+
+  get errCode() {
+    return this._code
+  }
+
+  get errMsg() {
+    return this.message
+  }
+}
+
+module.exports = {
+  BridgeError
+}

+ 124 - 0
uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/config.js

@@ -0,0 +1,124 @@
+'use strict';
+
+const {
+  ProviderType
+} = require('./consts.js')
+
+const configCenter = require('uni-config-center')
+
+// 多维数据为兼容uni-id以前版本配置
+const OauthConfig = {
+  'weixin-app': [
+    ['app', 'oauth', 'weixin'],
+    ['app-plus', 'oauth', 'weixin']
+  ],
+  'weixin-mp': [
+    ['mp-weixin', 'oauth', 'weixin']
+  ],
+  'weixin-h5': [
+    ['web', 'oauth', 'weixin-h5'],
+    ['h5-weixin', 'oauth', 'weixin'],
+    ['h5', 'oauth', 'weixin']
+  ],
+  'weixin-web': [
+    ['web', 'oauth', 'weixin-web']
+  ],
+  'qq-app': [
+    ['app', 'oauth', 'qq'],
+    ['app-plus', 'oauth', 'qq']
+  ],
+  'qq-mp': [
+    ['mp-qq', 'oauth', 'qq']
+  ]
+}
+
+const Support_Platforms = [
+  ProviderType.WEIXIN_MP,
+  ProviderType.WEIXIN_H5,
+  ProviderType.WEIXIN_APP,
+  ProviderType.WEIXIN_WEB,
+  ProviderType.QQ_MP,
+  ProviderType.QQ_APP
+]
+
+class ConfigBase {
+
+  constructor() {
+    const uniIdConfigCenter = configCenter({
+      pluginId: 'uni-id'
+    })
+
+    this._uniIdConfig = uniIdConfigCenter.config()
+  }
+
+  getAppConfig(appid) {
+    if (Array.isArray(this._uniIdConfig)) {
+      return this._uniIdConfig.find((item) => {
+        return (item.dcloudAppid === appid)
+      })
+    }
+    return this._uniIdConfig
+  }
+}
+
+class AppConfig extends ConfigBase {
+
+  constructor() {
+    super()
+  }
+
+  get(appid, platform) {
+    if (!this.isSupport(platform)) {
+      return null
+    }
+
+    let appConfig = this.getAppConfig(appid)
+    if (!appConfig) {
+      return null
+    }
+
+    return this.getOauthConfig(appConfig, platform)
+  }
+
+  isSupport(platformName) {
+    return (Support_Platforms.indexOf(platformName) >= 0)
+  }
+
+  getOauthConfig(appConfig, platformName) {
+    let treePath = OauthConfig[platformName]
+    let node = this.findNode(appConfig, treePath)
+    if (node && node.appid && node.appsecret) {
+      return {
+        appid: node.appid,
+        secret: node.appsecret
+      }
+    }
+    return null
+  }
+
+  findNode(treeNode, arrayPath) {
+    let node = treeNode
+    for (let treePath of arrayPath) {
+      for (let name of treePath) {
+        const currentNode = node[name]
+        if (currentNode) {
+          node = currentNode
+        } else {
+          node = null
+          break
+        }
+      }
+      if (node === null) {
+        node = treeNode
+      } else {
+        break
+      }
+    }
+    return node
+  }
+}
+
+
+module.exports = {
+  AppConfig
+};

+ 30 - 0
uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/consts.js

@@ -0,0 +1,30 @@
+'use strict';
+
+const TAG = "UNI_OPEN_BRIDGE"
+
+const HTTP_STATUS = {
+  SUCCESS: 200
+}
+
+const ProviderType = {
+  WEIXIN_MP: 'weixin-mp',
+  WEIXIN_H5: 'weixin-h5',
+  WEIXIN_APP: 'weixin-app',
+  WEIXIN_WEB: 'weixin-web',
+  QQ_MP: 'qq-mp',
+  QQ_APP: 'qq-app'
+}
+
+// old
+const PlatformType = ProviderType
+
+const ErrorCodeType = {
+  SYSTEM_ERROR: TAG + "_SYSTEM_ERROR"
+}
+
+module.exports = {
+  HTTP_STATUS,
+  ProviderType,
+  PlatformType,
+  ErrorCodeType
+}

+ 317 - 0
uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/index.js

@@ -0,0 +1,317 @@
+'use strict';
+
+const {
+  PlatformType,
+  ProviderType,
+  ErrorCodeType
+} = require('./consts.js')
+
+const {
+  AppConfig
+} = require('./config.js')
+
+const {
+  Storage
+} = require('./storage.js')
+
+const {
+  BridgeError
+} = require('./bridge-error.js')
+
+const {
+  WeixinServer
+} = require('./weixin-server.js')
+
+const appConfig = new AppConfig()
+
+class AccessToken extends Storage {
+
+  constructor() {
+    super('access-token', ['provider', 'appid'])
+  }
+
+  async update(key) {
+    super.update(key)
+
+    const result = await this.getByWeixinServer(key)
+
+    return this.set(key, result.value, result.duration)
+  }
+
+  async fallback(key) {
+    return this.getByWeixinServer(key)
+  }
+
+  async getByWeixinServer(key) {
+    const oauthConfig = appConfig.get(key.dcloudAppid, key.provider)
+    let methodName
+    if (key.provider === ProviderType.WEIXIN_MP) {
+      methodName = 'GetMPAccessTokenData'
+    } else if (key.provider === ProviderType.WEIXIN_H5) {
+      methodName = 'GetH5AccessTokenData'
+    } else {
+      throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, "provider invalid")
+    }
+
+    const responseData = await WeixinServer[methodName](oauthConfig)
+
+    const duration = responseData.expires_in || (60 * 60 * 2)
+    delete responseData.expires_in
+
+    return {
+      value: responseData,
+      duration
+    }
+  }
+}
+
+class UserAccessToken extends Storage {
+
+  constructor() {
+    super('user-access-token', ['provider', 'appid', 'openid'])
+  }
+}
+
+class SessionKey extends Storage {
+
+  constructor() {
+    super('session-key', ['provider', 'appid', 'openid'])
+  }
+}
+
+class Encryptkey extends Storage {
+
+  constructor() {
+    super('encrypt-key', ['provider', 'appid', 'openid'])
+  }
+
+  async update(key) {
+    super.update(key)
+
+    const result = await this.getByWeixinServer(key)
+
+    return this.set(key, result.value, result.duration)
+  }
+
+  getKeyString(key) {
+    return `${super.getKeyString(key)}-${key.version}`
+  }
+
+  getExpiresIn(value) {
+    if (value <= 0) {
+      return 60
+    }
+    return value
+  }
+
+  async fallback(key) {
+    return this.getByWeixinServer(key)
+  }
+
+  async getByWeixinServer(key) {
+    const accessToken = await Factory.Get(AccessToken, key)
+    const userSession = await Factory.Get(SessionKey, key)
+
+    const responseData = await WeixinServer.GetUserEncryptKeyData({
+      openid: key.openid,
+      access_token: accessToken.access_token,
+      session_key: userSession.session_key
+    })
+
+    const keyInfo = responseData.key_info_list.find((item) => {
+      return item.version === key.version
+    })
+
+    if (!keyInfo) {
+      throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, 'key version invalid')
+    }
+
+    const value = {
+      encrypt_key: keyInfo.encrypt_key,
+      iv: keyInfo.iv
+    }
+
+    return {
+      value,
+      duration: keyInfo.expire_in || (24 * 60 * 60)
+    }
+  }
+}
+
+class Ticket extends Storage {
+
+  constructor() {
+    super('ticket', ['provider', 'appid'])
+  }
+
+  async update(key) {
+    super.update(key)
+
+    const result = await this.getByWeixinServer(key)
+
+    return this.set(key, result.value, result.duration)
+  }
+
+  async fallback(key) {
+    return this.getByWeixinServer(key)
+  }
+
+  async getByWeixinServer(key) {
+    const accessToken = await Factory.Get(AccessToken, {
+      dcloudAppid: key.dcloudAppid,
+      provider: ProviderType.WEIXIN_H5
+    })
+
+    const responseData = await WeixinServer.GetH5TicketData(accessToken)
+
+    const duration = responseData.expires_in || (60 * 60 * 2)
+    delete responseData.expires_in
+    delete responseData.errcode
+    delete responseData.errmsg
+
+    return {
+      value: responseData,
+      duration
+    }
+  }
+}
+
+
+const Factory = {
+
+  async Get(T, key, fallback) {
+    Factory.FixOldKey(key)
+    return Factory.MakeUnique(T).get(key, fallback)
+  },
+
+  async Set(T, key, value, expiresIn) {
+    Factory.FixOldKey(key)
+    return Factory.MakeUnique(T).set(key, value, expiresIn)
+  },
+
+  async Remove(T, key) {
+    Factory.FixOldKey(key)
+    return Factory.MakeUnique(T).remove(key)
+  },
+
+  async Update(T, key) {
+    Factory.FixOldKey(key)
+    return Factory.MakeUnique(T).update(key)
+  },
+
+  FixOldKey(key) {
+    if (!key.provider) {
+      key.provider = key.platform
+    }
+
+    const configData = appConfig.get(key.dcloudAppid, key.provider)
+    if (!configData) {
+      throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, 'appid or provider invalid')
+    }
+    key.appid = configData.appid
+  },
+
+  MakeUnique(T) {
+    return new T()
+  }
+}
+
+
+// exports
+
+async function getAccessToken(key, fallback) {
+  return Factory.Get(AccessToken, key, fallback)
+}
+
+async function setAccessToken(key, value, expiresIn) {
+  return Factory.Set(AccessToken, key, value, expiresIn)
+}
+
+async function removeAccessToken(key) {
+  return Factory.Remove(AccessToken, key)
+}
+
+async function updateAccessToken(key) {
+  return Factory.Update(AccessToken, key)
+}
+
+async function getUserAccessToken(key, fallback) {
+  return Factory.Get(UserAccessToken, key, fallback)
+}
+
+async function setUserAccessToken(key, value, expiresIn) {
+  return Factory.Set(UserAccessToken, key, value, expiresIn)
+}
+
+async function removeUserAccessToken(key) {
+  return Factory.Remove(UserAccessToken, key)
+}
+
+async function getSessionKey(key, fallback) {
+  return Factory.Get(SessionKey, key, fallback)
+}
+
+async function setSessionKey(key, value, expiresIn) {
+  return Factory.Set(SessionKey, key, value, expiresIn)
+}
+
+async function removeSessionKey(key) {
+  return Factory.Remove(SessionKey, key)
+}
+
+async function getEncryptKey(key, fallback) {
+  return Factory.Get(Encryptkey, key, fallback)
+}
+
+async function setEncryptKey(key, value, expiresIn) {
+  return Factory.Set(Encryptkey, key, value, expiresIn)
+}
+
+async function removeEncryptKey(key) {
+  return Factory.Remove(Encryptkey, key)
+}
+
+async function updateEncryptKey(key) {
+  return Factory.Update(Encryptkey, key)
+}
+
+async function getTicket(key, fallback) {
+  return Factory.Get(Ticket, key, fallback)
+}
+
+async function setTicket(key, value, expiresIn) {
+  return Factory.Set(Ticket, key, value, expiresIn)
+}
+
+async function removeTicket(key) {
+  return Factory.Remove(Ticket, key)
+}
+
+async function updateTicket(key) {
+  return Factory.Update(Ticket, key)
+}
+
+module.exports = {
+  getAccessToken,
+  setAccessToken,
+  removeAccessToken,
+  updateAccessToken,
+  getUserAccessToken,
+  setUserAccessToken,
+  removeUserAccessToken,
+  getSessionKey,
+  setSessionKey,
+  removeSessionKey,
+  getEncryptKey,
+  setEncryptKey,
+  removeEncryptKey,
+  updateEncryptKey,
+  getTicket,
+  setTicket,
+  removeTicket,
+  updateTicket,
+  ProviderType,
+  PlatformType,
+  WeixinServer,
+  ErrorCodeType
+}

+ 19 - 0
uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/package.json

@@ -0,0 +1,19 @@
+{
+    "name": "uni-open-bridge-common",
+    "version": "1.0.0",
+    "description": "",
+    "main": "index.js",
+    "scripts": {
+        "test": "echo \"Error: no test specified\" && exit 1"
+    },
+    "keywords": [],
+    "author": "",
+    "license": "ISC",
+    "dependencies": {
+        "uni-config-center": "file:..\/..\/..\/..\/..\/uni-config-center\/uniCloud\/cloudfunctions\/common\/uni-config-center"
+    },
+    "origin-plugin-dev-name": "uni-open-bridge-common",
+    "origin-plugin-version": "1.2.1",
+    "plugin-dev-name": "uni-open-bridge-common",
+    "plugin-version": "1.2.1"
+}

+ 116 - 0
uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/storage.js

@@ -0,0 +1,116 @@
+'use strict';
+
+const {
+  Validator
+} = require('./validator.js')
+
+const {
+  CacheKeyCascade
+} = require('./uni-cloud-cache.js')
+
+const {
+  BridgeError
+} = require('./bridge-error.js')
+
+class Storage {
+
+  constructor(type, keys) {
+    this._type = type || null
+    this._keys = keys || []
+  }
+
+  async get(key, fallback) {
+    this.validateKey(key)
+    const result = await this.create(key, fallback).get()
+    return result.value
+  }
+
+  async set(key, value, expiresIn) {
+    this.validateKey(key)
+    this.validateValue(value)
+    const expires_in = this.getExpiresIn(expiresIn)
+    if (expires_in !== 0) {
+      await this.create(key).set(this.getValue(value), expires_in)
+    }
+  }
+
+  async remove(key) {
+    this.validateKey(key)
+    await this.create(key).remove()
+  }
+
+  // virtual
+  async update(key) {
+    this.validateKey(key)
+  }
+
+  async ttl(key) {
+    this.validateKey(key)
+    // 后续考虑支持
+  }
+
+  async fallback(key) {
+    return {
+      value: null,
+      duration: -1
+    }
+  }
+
+  getKeyString(key) {
+    const keyArray = [Storage.Prefix]
+    this._keys.forEach((name) => {
+      keyArray.push(key[name])
+    })
+    keyArray.push(this._type)
+    return keyArray.join(':')
+  }
+
+  getValue(value) {
+    return value
+  }
+
+  getExpiresIn(value) {
+    if (value !== undefined) {
+      return value
+    }
+    return -1
+  }
+
+  validateKey(key) {
+    Validator.Key(this._keys, key)
+  }
+
+  validateValue(value) {
+    Validator.Value(value)
+  }
+
+  create(key, fallback) {
+    const keyString = this.getKeyString(key)
+    const options = {
+      layers: [{
+        type: 'database',
+        key: keyString
+      }, {
+        type: 'redis',
+        key: keyString
+      }]
+    }
+
+    const _this = this
+    return new CacheKeyCascade({
+      ...options,
+      fallback: async function() {
+        if (fallback) {
+          return fallback(key)
+        } else if (_this.fallback) {
+          return _this.fallback(key)
+        }
+      }
+    })
+  }
+}
+Storage.Prefix = "uni-id"
+
+module.exports = {
+  Storage
+};

+ 324 - 0
uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/uni-cloud-cache.js

@@ -0,0 +1,324 @@
+const db = uniCloud.database()
+
+function getType(value) {
+  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
+}
+
+const validator = {
+  key: function(value) {
+    const err = new Error('Invalid key')
+    if (typeof value !== 'string') {
+      throw err
+    }
+    const valueTrim = value.trim()
+    if (!valueTrim || valueTrim !== value) {
+      throw err
+    }
+  },
+  value: function(value) {
+    // 仅作简单校验
+    const type = getType(value)
+    const validValueType = ['null', 'number', 'string', 'array', 'object']
+    if (validValueType.indexOf(type) === -1) {
+      throw new Error('Invalid value type')
+    }
+  },
+  duration: function(value) {
+    const err = new Error('Invalid duration')
+    if (value === undefined) {
+      return
+    }
+    if (typeof value !== 'number' || value === 0) {
+      throw err
+    }
+  }
+}
+
+/**
+ * 入库时 expired 为过期时间对应的时间戳,永不过期用-1表示
+ * 返回结果时 与redis对齐,-1表示永不过期,-2表示已过期或不存在
+ */
+class DatabaseCache {
+  constructor({
+    collection = 'opendb-open-data'
+  } = {}) {
+    this.type = 'db'
+    this.collection = db.collection(collection)
+  }
+
+  _serializeValue(value) {
+    return value === undefined ? null : JSON.stringify(value)
+  }
+
+  _deserializeValue(value) {
+    return value ? JSON.parse(value) : value
+  }
+
+  async set(key, value, duration) {
+    validator.key(key)
+    validator.value(value)
+    validator.duration(duration)
+    value = this._serializeValue(value)
+    await this.collection.doc(key).set({
+      value,
+      expired: duration && duration !== -1 ? Date.now() + (duration * 1000) : -1
+    })
+  }
+
+  async _getWithDuration(key) {
+    const getKeyRes = await this.collection.doc(key).get()
+    const record = getKeyRes.data[0]
+    if (!record) {
+      return {
+        value: null,
+        duration: -2
+      }
+    }
+    const value = this._deserializeValue(record.value)
+    const expired = record.expired
+    if (expired === -1) {
+      return {
+        value,
+        duration: -1
+      }
+    }
+    const duration = expired - Date.now()
+    if (duration <= 0) {
+      await this.remove(key)
+      return {
+        value: null,
+        duration: -2
+      }
+    }
+    return {
+      value,
+      duration: Math.floor(duration / 1000)
+    }
+  }
+
+  async get(key, {
+    withDuration = true
+  } = {}) {
+    const result = await this._getWithDuration(key)
+    if (!withDuration) {
+      delete result.duration
+    }
+    return result
+  }
+
+  async remove(key) {
+    await this.collection.doc(key).remove()
+  }
+}
+
+class RedisCache {
+  constructor() {
+    this.type = 'redis'
+    this.redis = uniCloud.redis()
+  }
+
+  _serializeValue(value) {
+    return value === undefined ? null : JSON.stringify(value)
+  }
+
+  _deserializeValue(value) {
+    return value ? JSON.parse(value) : value
+  }
+
+  async set(key, value, duration) {
+    validator.key(key)
+    validator.value(value)
+    validator.duration(duration)
+    value = this._serializeValue(value)
+    if (!duration || duration === -1) {
+      await this.redis.set(key, value)
+    } else {
+      await this.redis.set(key, value, 'EX', duration)
+    }
+  }
+
+  async get(key, {
+    withDuration = false
+  } = {}) {
+    let value = await this.redis.get(key)
+    value = this._deserializeValue(value)
+    if (!withDuration) {
+      return {
+        value
+      }
+    }
+    const durationSecond = await this.redis.ttl(key)
+    let duration
+    switch (durationSecond) {
+      case -1:
+        duration = -1
+        break
+      case -2:
+        duration = -2
+        break
+      default:
+        duration = durationSecond
+        break
+    }
+    return {
+      value,
+      duration
+    }
+  }
+
+  async remove(key) {
+    await this.redis.del(key)
+  }
+}
+
+class Cache {
+  constructor({
+    type,
+    collection
+  } = {}) {
+    if (type === 'database') {
+      return new DatabaseCache({
+        collection
+      })
+    } else if (type === 'redis') {
+      return new RedisCache()
+    } else {
+      throw new Error('Invalid cache type')
+    }
+  }
+}
+
+class CacheKey {
+  constructor({
+    type,
+    collection,
+    cache,
+    key,
+    fallback
+  } = {}) {
+    this.cache = cache || new Cache({
+      type,
+      collection
+    })
+    this.key = key
+    this.fallback = fallback
+  }
+
+  async set(value, duration) {
+    await this.cache.set(this.key, value, duration)
+  }
+
+  async setWithSync(value, duration, syncMethod) {
+    await Promise.all([
+      this.set(this.key, value, duration),
+      syncMethod(value, duration)
+    ])
+  }
+
+  async get() {
+    let {
+      value,
+      duration
+    } = await this.cache.get(this.key)
+    if (value !== null && value !== undefined) {
+      return {
+        value,
+        duration
+      }
+    }
+    if (!this.fallback) {
+      return {
+        value: null,
+        duration: -2
+      }
+    }
+    const fallbackResult = await this.fallback()
+    value = fallbackResult.value
+    duration = fallbackResult.duration
+    if (value !== null && duration !== undefined) {
+      await this.cache.set(this.key, value, duration)
+    }
+    return {
+      value,
+      duration
+    }
+  }
+
+  async remove() {
+    await this.cache.remove(this.key)
+  }
+}
+
+class CacheKeyCascade {
+  constructor({
+    layers, // [{cache, type, collection, key}] 从低级到高级排序,[DbCacheKey, RedisCacheKey]
+    fallback
+  } = {}) {
+    this.layers = layers
+    this.cacheLayers = []
+    let lastCacheKey
+    for (let i = 0; i < layers.length; i++) {
+      const {
+        type,
+        cache,
+        collection,
+        key
+      } = layers[i]
+      const lastCacheKeyTemp = lastCacheKey
+      try {
+        const currentCacheKey = new CacheKey({
+          type,
+          collection,
+          cache,
+          key,
+          fallback: i === 0 ? fallback : function() {
+            return lastCacheKeyTemp.get()
+          }
+        })
+        this.cacheLayers.push(currentCacheKey)
+        lastCacheKey = currentCacheKey
+      } catch (e) {}
+    }
+    this.highLevelCache = lastCacheKey
+  }
+
+  async set(value, duration) {
+    return Promise.all(
+      this.cacheLayers.map(item => {
+        return item.set(value, duration)
+      })
+    )
+  }
+
+  async setWithSync(value, duration, syncMethod) {
+    const setPromise = this.cacheLayers.map(item => {
+      return item.set(value, duration)
+    })
+    return Promise.all(
+      [
+        ...setPromise,
+        syncMethod(value, duration)
+      ]
+    )
+  }
+
+  async get() {
+    return this.highLevelCache.get()
+  }
+
+  async remove() {
+    await Promise.all(
+      this.cacheLayers.map(cacheKeyItem => {
+        return cacheKeyItem.remove()
+      })
+    )
+  }
+}
+
+module.exports = {
+  Cache,
+  DatabaseCache,
+  RedisCache,
+  CacheKey,
+  CacheKeyCascade
+}

+ 31 - 0
uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/validator.js

@@ -0,0 +1,31 @@
+const Validator = {
+
+  Key(keyArray, parameters) {
+    for (let i = 0; i < keyArray.length; i++) {
+      const keyName = keyArray[i]
+      if (typeof parameters[keyName] !== 'string') {
+        Validator.ThrowNewError(`Invalid ${keyName}`)
+      }
+      if (parameters[keyName].length < 1) {
+        Validator.ThrowNewError(`Invalid ${keyName}`)
+      }
+    }
+  },
+
+  Value(value) {
+    if (value === undefined) {
+      Validator.ThrowNewError('Invalid Value')
+    }
+    if (typeof value !== 'object') {
+      Validator.ThrowNewError('Invalid Value Type')
+    }
+  },
+
+  ThrowNewError(message) {
+    throw new Error(message)
+  }
+}
+
+module.exports = {
+  Validator
+}

+ 203 - 0
uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/weixin-server.js

@@ -0,0 +1,203 @@
+'use strict';
+
+const crypto = require('crypto')
+
+const {
+  HTTP_STATUS
+} = require('./consts.js')
+
+const {
+  BridgeError
+} = require('./bridge-error.js')
+
+class WeixinServer {
+
+  constructor(options = {}) {
+    this._appid = options.appid
+    this._secret = options.secret
+  }
+
+  getAccessToken() {
+    return uniCloud.httpclient.request(WeixinServer.AccessToken_Url, {
+      dataType: 'json',
+      method: 'POST',
+      contentType: 'json',
+      data: {
+        appid: this._appid,
+        secret: this._secret,
+        grant_type: "client_credential"
+      }
+    })
+  }
+
+  // 使用客户端获取的 code 从微信服务器换取 openid,code 仅可使用一次
+  codeToSession(code) {
+    return uniCloud.httpclient.request(WeixinServer.Code2Session_Url, {
+      dataType: 'json',
+      data: {
+        appid: this._appid,
+        secret: this._secret,
+        js_code: code,
+        grant_type: 'authorization_code'
+      }
+    })
+  }
+
+  getUserEncryptKey({
+    access_token,
+    openid,
+    session_key
+  }) {
+    console.log(access_token, openid, session_key);
+    const signature = crypto.createHmac('sha256', session_key).update('').digest('hex')
+    return uniCloud.httpclient.request(WeixinServer.User_Encrypt_Key_Url, {
+      dataType: 'json',
+      method: 'POST',
+      dataAsQueryString: true,
+      data: {
+        access_token,
+        openid: openid,
+        signature: signature,
+        sig_method: 'hmac_sha256'
+      }
+    })
+  }
+
+  getH5AccessToken() {
+    return uniCloud.httpclient.request(WeixinServer.AccessToken_H5_Url, {
+      dataType: 'json',
+      method: 'GET',
+      data: {
+        appid: this._appid,
+        secret: this._secret,
+        grant_type: "client_credential"
+      }
+    })
+  }
+
+  getH5Ticket(access_token) {
+    return uniCloud.httpclient.request(WeixinServer.Ticket_Url, {
+      dataType: 'json',
+      dataAsQueryString: true,
+      method: 'POST',
+      data: {
+        access_token
+      }
+    })
+  }
+
+  getH5AccessTokenForEip() {
+    return uniCloud.httpProxyForEip.postForm(WeixinServer.AccessToken_H5_Url, {
+      appid: this._appid,
+      secret: this._secret,
+      grant_type: "client_credential"
+    }, {
+      dataType: 'json'
+    })
+  }
+
+  getH5TicketForEip(access_token) {
+    return uniCloud.httpProxyForEip.postForm(WeixinServer.Ticket_Url, {
+      access_token
+    }, {
+      dataType: 'json',
+      dataAsQueryString: true
+    })
+  }
+}
+
+WeixinServer.AccessToken_Url = 'https://api.weixin.qq.com/cgi-bin/stable_token'
+WeixinServer.Code2Session_Url = 'https://api.weixin.qq.com/sns/jscode2session'
+WeixinServer.User_Encrypt_Key_Url = 'https://api.weixin.qq.com/wxa/business/getuserencryptkey'
+WeixinServer.AccessToken_H5_Url = 'https://api.weixin.qq.com/cgi-bin/token'
+WeixinServer.Ticket_Url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi'
+
+WeixinServer.GetMPAccessToken = function(options) {
+  return new WeixinServer(options).getAccessToken()
+}
+
+WeixinServer.GetCodeToSession = function(options) {
+  return new WeixinServer(options).codeToSession(options.code)
+}
+
+WeixinServer.GetUserEncryptKey = function(options) {
+  return new WeixinServer(options).getUserEncryptKey(options)
+}
+
+WeixinServer.GetH5AccessToken = function(options) {
+  return new WeixinServer(options).getH5AccessToken()
+}
+
+WeixinServer.GetH5Ticket = function(options) {
+  return new WeixinServer(options).getH5Ticket(options.access_token)
+}
+
+////////////////////////////////////////////////////////////////
+
+function isAliyun() {
+  return (uniCloud.getCloudInfos()[0].provider === 'aliyun')
+}
+
+WeixinServer.GetResponseData = function(response) {
+  console.log("WeixinServer::response", response)
+
+  if (!(response.status === HTTP_STATUS.SUCCESS || response.statusCodeValue === HTTP_STATUS.SUCCESS)) {
+    throw new BridgeError(response.status || response.statusCodeValue, response.status || response.statusCodeValue)
+  }
+
+  const responseData = response.data || response.body
+
+  if (responseData.errcode !== undefined && responseData.errcode !== 0) {
+    throw new BridgeError(responseData.errcode, responseData.errmsg)
+  }
+
+  return responseData
+}
+
+WeixinServer.GetMPAccessTokenData = async function(options) {
+  const response = await new WeixinServer(options).getAccessToken()
+  return WeixinServer.GetResponseData(response)
+}
+
+WeixinServer.GetCodeToSessionData = async function(options) {
+  const response = await new WeixinServer(options).codeToSession(options.code)
+  return WeixinServer.GetResponseData(response)
+}
+
+WeixinServer.GetUserEncryptKeyData = async function(options) {
+  const response = await new WeixinServer(options).getUserEncryptKey(options)
+  return WeixinServer.GetResponseData(response)
+}
+
+WeixinServer.GetH5AccessTokenData = async function(options) {
+  const ws = new WeixinServer(options)
+  let response
+  if (isAliyun()) {
+    response = await ws.getH5AccessTokenForEip()
+    if (typeof response === 'string') {
+      response = JSON.parse(response)
+    }
+  } else {
+    response = await ws.getH5AccessToken()
+  }
+  return WeixinServer.GetResponseData(response)
+}
+
+WeixinServer.GetH5TicketData = async function(options) {
+  const ws = new WeixinServer(options)
+  let response
+  if (isAliyun()) {
+    response = await ws.getH5TicketForEip(options.access_token)
+    if (typeof response === 'string') {
+      response = JSON.parse(response)
+    }
+  } else {
+    response = await ws.getH5Ticket(options.access_token)
+  }
+  return WeixinServer.GetResponseData(response)
+}
+
+
+module.exports = {
+  WeixinServer
+}

+ 19 - 0
uni_modules/uni-open-bridge-common/uniCloud/database/opendb-open-data.schema.json

@@ -0,0 +1,19 @@
+// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema
+{
+  "bsonType": "object",
+  "required": ["_id", "value"],
+  "properties": {
+    "_id": {
+      "bsonType": "string",
+      "description": "key,格式:uni-id:[provider]:[appid]:[openid]:[access-token|user-access-token|session-key|encrypt-key-version|ticket]"
+    },
+    "value": {
+      "bsonType": "object",
+      "description": "字段_id对应的值"
+    },
+    "expired": {
+      "bsonType": "date",
+      "description": "过期时间"
+    }
+  }
+}

+ 8 - 0
uni_modules/uni-subscribemsg/changelog.md

@@ -0,0 +1,8 @@
+## 1.1.0(2024-03-25)
+- 【重要】支持支付宝小程序云
+## 1.0.2(2023-03-20)
+- 【新增】支持微信小程序转发微信公众号的模板消息 [文档](https://uniapp.dcloud.net.cn/uniCloud/uni-subscribemsg.html#sendtemplatemessageformp)
+## 1.0.1(2023-03-16)
+- 【新增】`uniSubscribemsg.getSubscribeUserInfo` 可用于获取用户是否关注了公众号 [文档](https://uniapp.dcloud.net.cn/uniCloud/uni-subscribemsg.html#getsubscribeuserinfo)
+## 1.0.0(2023-03-03)
+初版

+ 84 - 0
uni_modules/uni-subscribemsg/package.json

@@ -0,0 +1,84 @@
+{
+  "id": "uni-subscribemsg",
+  "displayName": "uni-subscribemsg公共模块",
+  "version": "1.1.0",
+  "description": "可以方便开发者快速接入小程序订阅消息和微信公众号模板消息",
+  "keywords": [
+    "uni-subscribemsg",
+    "订阅消息",
+    "模板消息"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "unicloud-template-function",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "插件不采集任何数据",
+      "permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+		"dependencies": ["uni-open-bridge-common"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y",
+        "alipay": "y"
+      },
+      "client": {
+        "Vue": {
+          "vue2": "u",
+          "vue3": "u"
+        },
+        "App": {
+          "app-vue": "u",
+          "app-nvue": "u"
+        },
+        "H5-mobile": {
+          "Safari": "u",
+          "Android Browser": "u",
+          "微信浏览器(Android)": "u",
+          "QQ浏览器(Android)": "u"
+        },
+        "H5-pc": {
+          "Chrome": "u",
+          "IE": "u",
+          "Edge": "u",
+          "Firefox": "u",
+          "Safari": "u"
+        },
+        "小程序": {
+          "微信": "u",
+          "阿里": "u",
+          "百度": "u",
+          "字节跳动": "u",
+          "QQ": "u",
+          "钉钉": "u",
+          "快手": "u",
+          "飞书": "u",
+          "京东": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 7 - 0
uni_modules/uni-subscribemsg/readme.md

@@ -0,0 +1,7 @@
+# uni-subscribemsg
+
+开发业务时时常遇到需要向用户发送一些通知,如欠费通知、会员到期通知等等。
+
+uni-subcribe-msg模块可以方便开发者快速接入小程序订阅消息和微信公众号模板消息。
+
+文档链接 [https://uniapp.dcloud.net.cn/uniCloud/uni-subscribemsg](https://uniapp.dcloud.net.cn/uniCloud/uni-subscribemsg)

+ 58 - 0
uni_modules/uni-subscribemsg/uniCloud/cloudfunctions/common/uni-subscribemsg/index.js

@@ -0,0 +1,58 @@
+const service = require('./service/index.js');
+
+class UniSubscribemsg {
+
+	// 构造函数
+	constructor(data = {}) {
+		let {
+			dcloudAppid,
+			provider, // 平台 weixin-mp 微信小程序 weixin-h5 微信公众号
+		} = data;
+
+		this.config = {
+			dcloudAppid,
+			provider
+		}
+	}
+
+	// API - 发送订阅消息
+	async sendSubscribeMessage(data = {}) {
+		let res = await this._call("sendSubscribeMessage", data);
+		return res;
+	}
+
+	// API - 发送公众号模板消息
+	async sendTemplateMessage(data = {}) {
+		let res = await this._call("sendTemplateMessage", data);
+		return res;
+	}
+	
+	// API - 检测用户是否关注了公众号
+	async getSubscribeUserInfo(data = {}) {
+		let res = await this._call("getSubscribeUserInfo", data);
+		return res;
+	}
+
+	// 测试API - 获取当前登录用户的openid(此接口仅测试时使用)
+	async getOpenid(data = {}) {
+		let res = await this._call("getOpenid", data);
+		return res;
+	}
+
+	// 私有函数
+	async _call(name, data = {}) {
+		let runService = service[this.config.provider];
+		if (!runService) {
+			throw new Error(`不支持平台:${this.config.provider}`);
+		}
+		let runFunction = runService[name];
+		if (!runFunction) {
+			throw new Error(`平台:${this.config.provider}不支持:${name}`);
+		}
+		let res = await runFunction.call(this, data);
+		return res;
+	}
+
+}
+
+module.exports = UniSubscribemsg;

+ 20 - 0
uni_modules/uni-subscribemsg/uniCloud/cloudfunctions/common/uni-subscribemsg/package.json

@@ -0,0 +1,20 @@
+{
+    "name": "uni-subscribemsg",
+    "version": "1.0.0",
+    "description": "",
+    "main": "index.js",
+    "scripts": {
+        "test": "echo \"Error: no test specified\" && exit 1"
+    },
+    "keywords": [],
+    "author": "",
+    "license": "ISC",
+    "dependencies": {
+        "uni-open-bridge-common": "file:..\/..\/..\/..\/..\/uni-open-bridge-common\/uniCloud\/cloudfunctions\/common\/uni-open-bridge-common",
+        "uni-config-center": "file:..\/..\/..\/..\/..\/uni-config-center\/uniCloud\/cloudfunctions\/common\/uni-config-center"
+    },
+    "origin-plugin-dev-name": "uni-subscribemsg",
+    "origin-plugin-version": "1.1.0",
+    "plugin-dev-name": "uni-subscribemsg",
+    "plugin-version": "1.1.0"
+}

+ 4 - 0
uni_modules/uni-subscribemsg/uniCloud/cloudfunctions/common/uni-subscribemsg/service/index.js

@@ -0,0 +1,4 @@
+module.exports = {
+	"weixin-h5": require('./weixin-h5.js'),
+	"weixin-mp": require('./weixin-mp.js')
+}

+ 74 - 0
uni_modules/uni-subscribemsg/uniCloud/cloudfunctions/common/uni-subscribemsg/service/weixin-h5.js

@@ -0,0 +1,74 @@
+const {
+	getAccessToken
+} = require('uni-open-bridge-common')
+
+module.exports = {
+	// 发送模板消息
+	sendTemplateMessage: async function(obj = {}) {
+		let {
+			touser, // 接收者(用户)的 openid
+			template_id, // 所需下发的订阅模板id
+			url, // 点击模板卡片后的跳转的链接地址。该字段不填则模板无跳转。
+			miniprogram, // 跳小程序所需数据,不需跳小程序可不用传该数据
+			data, // 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
+			client_msg_id, // 防重入id。对于同一个openid + client_msg_id, 只发送一条消息,10分钟有效,超过10分钟不保证效果。若无防重入需求,可不填
+		} = obj;
+		let getAccessTokenRes = await getAccessToken({
+			dcloudAppid: this.config.dcloudAppid,
+			provider: 'weixin-h5'
+		});
+		let access_token = getAccessTokenRes.access_token;
+		let requestUrl = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${access_token}`;
+		let requestRes = await uniCloud.httpclient.request(requestUrl, {
+			method: "POST",
+			dataType: "json",
+			headers: {
+				"content-type": "application/json"
+			},
+			data: {
+				touser,
+				template_id,
+				url,
+				miniprogram,
+				data,
+				client_msg_id
+			},
+			useContent: true
+		})
+		let result = requestRes.data;
+		let res = {
+			errCode: result.errcode || 0,
+			errMsg: result.errmsg,
+			result: result
+		};
+		return res;
+	},
+	// 检测用户是否关注了公众号
+	getSubscribeUserInfo: async function(obj = {}) {
+		let {
+			openid, // 用户openid
+		} = obj;
+		let getAccessTokenRes = await getAccessToken({
+			dcloudAppid: this.config.dcloudAppid,
+			provider: 'weixin-h5'
+		});
+		let access_token = getAccessTokenRes.access_token;
+		let requestUrl = `https://api.weixin.qq.com/cgi-bin/user/info?access_token=${access_token}&openid=${openid}&lang=zh_CN`;
+		let requestRes = await uniCloud.httpclient.request(requestUrl, {
+			method: "GET",
+			dataType: "json",
+			headers: {
+				"content-type": "application/json"
+			}
+		})
+		let result = requestRes.data;
+		let res = {
+			errCode: result.errcode || 0,
+			errMsg: result.errmsg,
+			subscribe: result.subscribe === 1 ? true : false,
+			result: result
+		};
+		return res;
+	},
+	
+}

+ 121 - 0
uni_modules/uni-subscribemsg/uniCloud/cloudfunctions/common/uni-subscribemsg/service/weixin-mp.js

@@ -0,0 +1,121 @@
+const {
+	getAccessToken
+} = require('uni-open-bridge-common')
+
+const configCenter = require('uni-config-center')
+const uniIdConfig = configCenter({
+	pluginId: 'uni-id'
+});
+
+module.exports = {
+	// 发送订阅消息
+	sendSubscribeMessage: async function(obj = {}) {
+		let {
+			touser, // 接收者(用户)的 openid
+			template_id, // 所需下发的订阅模板id
+			page, // 点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。
+			data, // 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
+			miniprogram_state = "formal", // 跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
+			lang = "zh_CN", // 进入小程序查看”的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN
+		} = obj;
+		let getAccessTokenRes = await getAccessToken({
+			dcloudAppid: this.config.dcloudAppid,
+			provider: 'weixin-mp'
+		});
+		let access_token = getAccessTokenRes.access_token;
+		let url = `https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=${access_token}`;
+		let requestRes = await uniCloud.httpclient.request(url, {
+			method: "POST",
+			dataType: "json",
+			headers: {
+				"content-type": "application/json"
+			},
+			data: {
+				touser,
+				template_id,
+				page,
+				data,
+				miniprogram_state,
+				lang
+			},
+			useContent: true
+		})
+		let result = requestRes.data;
+		let res = {
+			errCode: result.errcode || 0,
+			errMsg: result.errmsg,
+			result: result
+		};
+		return res;
+	},
+	// 小程序发公众号模板消息
+	sendTemplateMessage: async function(obj = {}) {
+		let {
+			appid,
+			touser, // 接收者(用户)的 openid
+			template_id, // 所需下发的订阅模板id
+			url, // 点击模板卡片后的跳转的链接地址。该字段不填则模板无跳转。
+			miniprogram, // 跳小程序所需数据,不需跳小程序可不用传该数据
+			data, // 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
+		} = obj;
+		let getAccessTokenRes = await getAccessToken({
+			dcloudAppid: this.config.dcloudAppid,
+			provider: 'weixin-mp'
+		});
+		if (!appid) {
+			try {
+				appid = uniIdConfig.config()["web"].oauth["weixin-h5"].appid;
+			} catch (err) {}
+		}
+		let access_token = getAccessTokenRes.access_token;
+		let requestUrl = `https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=${access_token}`;
+		let requestRes = await uniCloud.httpclient.request(requestUrl, {
+			method: "POST",
+			dataType: "json",
+			headers: {
+				"content-type": "application/json"
+			},
+			data: {
+				touser,
+				mp_template_msg: {
+					appid,
+					template_id,
+					url,
+					miniprogram,
+					data
+				}
+			},
+			useContent: true
+		})
+		let result = requestRes.data;
+		let res = {
+			errCode: result.errcode || 0,
+			errMsg: result.errmsg,
+			result: result
+		};
+		return res;
+	},
+	// 根据code获取openid(此接口仅为演示时使用,实际应该通过uniId的微信登录时就已经拿到了openid)
+	getOpenid: async function(obj = {}) {
+		let {
+			code
+		} = obj;
+		let { appid, appsecret } = uniIdConfig.config()["mp-weixin"].oauth.weixin;
+		let url =
+			`https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${appsecret}&js_code=${code}&grant_type=authorization_code`;
+		let requestRes = await uniCloud.httpclient.request(url, {
+			method: "get",
+			dataType: "json",
+			headers: {
+				"content-type": "application/json"
+			}
+		})
+		let result = requestRes.data;
+		let res = {
+			errCode: result.errcode || 0,
+			errMsg: result.errmsg,
+			openid: result.openid
+		};
+		return res;
+	}
+}

Some files were not shown because too many files changed in this diff