Przeglądaj źródła

完成搜索页面 以及完善帖子详情页面

XSXS 8 miesięcy temu
rodzic
commit
eea90521d9

+ 209 - 190
App.vue

@@ -1,214 +1,233 @@
 <script>
-	// // #ifdef APP-PLUS
+// // #ifdef APP-PLUS
 
-	// import {
-	// 	registerRequestPermissionTipsListener,
-	// 	unregisterRequestPermissionTipsListener,
-	// 	setRequestPermissionTips
-	// } from "@/uni_modules/uni-registerRequestPermissionTips"
-	// var PermissionTips = {
-	// 	"android.permission.CAMERA": "<h4 style=\"font-size:40px;\">正在获取相机、存储权限</h4><font color=#cccccc>该权限用于获取设备拍摄或获取本地应用相册,进行头像或图片上传。</font>",
-	// 	"android.permission.WRITE_EXTERNAL_STORAGE": "<h4 style=\"font-size:40px;\">正在获取相机、存储权限</h4><font color=#cccccc>该权限用于获取设备拍摄或获取本地应用相册,进行头像或图片上传。</font>",
-	// 	"android.permission.READ_EXTERNAL_STORAGE": "<h4 style=\"font-size:40px;\">正在获取相机、存储权限</h4><font color=#cccccc>该权限用于获取设备拍摄或获取本地应用相册,进行头像或图片上传。</font>",
-	// 	"android.permission.RECORD_AUDIO": "<h4 style=\"font-size:40px;\">正在获取麦克风权限</h4><font color=#cccccc>该权限用于获取设备语音通话、语音等录音或通讯场景所需。</font>"
-	// }
-	// // #endif
+// import {
+// 	registerRequestPermissionTipsListener,
+// 	unregisterRequestPermissionTipsListener,
+// 	setRequestPermissionTips
+// } from "@/uni_modules/uni-registerRequestPermissionTips"
+// var PermissionTips = {
+// 	"android.permission.CAMERA": "<h4 style=\"font-size:40px;\">正在获取相机、存储权限</h4><font color=#cccccc>该权限用于获取设备拍摄或获取本地应用相册,进行头像或图片上传。</font>",
+// 	"android.permission.WRITE_EXTERNAL_STORAGE": "<h4 style=\"font-size:40px;\">正在获取相机、存储权限</h4><font color=#cccccc>该权限用于获取设备拍摄或获取本地应用相册,进行头像或图片上传。</font>",
+// 	"android.permission.READ_EXTERNAL_STORAGE": "<h4 style=\"font-size:40px;\">正在获取相机、存储权限</h4><font color=#cccccc>该权限用于获取设备拍摄或获取本地应用相册,进行头像或图片上传。</font>",
+// 	"android.permission.RECORD_AUDIO": "<h4 style=\"font-size:40px;\">正在获取麦克风权限</h4><font color=#cccccc>该权限用于获取设备语音通话、语音等录音或通讯场景所需。</font>"
+// }
+// // #endif
 
-	const tabbars = ['/pages/my/my', '/pages/make/index', '/pages/message/mailMessage', '/pages/index/index']
-	export default {
-		globalData: {
-			postHeader: null,
-			headerSign: '',
-			uuid: '',
-			skey: '',
-			isLogin: 0,
-			userId: 0,
-			socket: null,
-			postHeader: null,
-			systemInfo: null
-		},
-		beforeDestroy() {},
-		onLaunch: function(options) {
-			// #ifdef APP-PLUS
-			// var brand = uni.getSystemInfoSync().deviceBrand
-			// setRequestPermissionTips(PermissionTips)
-			// registerRequestPermissionTipsListener({
-			// 	onRequest: (e) => {
-			// 		console.log(e)
-			// 	},
-			// 	onConfirm: (e) => {
-			// 		console.log(e)
-			// 	},
-			// 	onComplete: (e) => {
-			// 		// 华为手机在权限禁止之后,再次申请权限不会出现权限申请框。此时应该引导用户去系统设置开启此权限,不应该频繁申请。
-			// 		if (brand.toLowerCase() == "huawei" || true) {
-			// 			var tips = {}
-			// 			var hasDeniedPermission = false
-			// 			for (var k in PermissionTips) {
-			// 				if (e[k] != "denied") {
-			// 					tips[k] = PermissionTips[k]
-			// 				} else {
-			// 					hasDeniedPermission = true
-			// 				}
-			// 			}
-			// 			setRequestPermissionTips(tips) // 更新弹框提醒,防止华为手机不出现权限申请框时权限提醒框闪烁的情况
-			// 			if (hasDeniedPermission)
-			// 				uni.showModal({
-			// 					content: "权限已经被拒绝,请前往设置中开启"
-			// 				})
-			// 		}
-			// 	}
-			// });
-			// #endif
-			let deviceId = "";
-			let uuid2 = uni.getStorageSync("app_uuid");
-			if (uuid2 && uuid2.length >= 6) {
-				deviceId = uuid2;
+const tabbars = ['/pages/my/my', '/pages/make/index', '/pages/message/mailMessage', '/pages/index/index']
+export default {
+	globalData: {
+		postHeader: null,
+		headerSign: '',
+		uuid: '',
+		skey: '',
+		isLogin: 0,
+		userId: 0,
+		socket: null,
+		postHeader: null,
+		systemInfo: null,
+		nickname: '',
+		avator: '',
+		mobile:0,
+		user_id: 0,
+	},
+	beforeDestroy() { },
+	onLaunch: function (options) {
+		// #ifdef APP-PLUS
+		// var brand = uni.getSystemInfoSync().deviceBrand
+		// setRequestPermissionTips(PermissionTips)
+		// registerRequestPermissionTipsListener({
+		// 	onRequest: (e) => {
+		// 		console.log(e)
+		// 	},
+		// 	onConfirm: (e) => {
+		// 		console.log(e)
+		// 	},
+		// 	onComplete: (e) => {
+		// 		// 华为手机在权限禁止之后,再次申请权限不会出现权限申请框。此时应该引导用户去系统设置开启此权限,不应该频繁申请。
+		// 		if (brand.toLowerCase() == "huawei" || true) {
+		// 			var tips = {}
+		// 			var hasDeniedPermission = false
+		// 			for (var k in PermissionTips) {
+		// 				if (e[k] != "denied") {
+		// 					tips[k] = PermissionTips[k]
+		// 				} else {
+		// 					hasDeniedPermission = true
+		// 				}
+		// 			}
+		// 			setRequestPermissionTips(tips) // 更新弹框提醒,防止华为手机不出现权限申请框时权限提醒框闪烁的情况
+		// 			if (hasDeniedPermission)
+		// 				uni.showModal({
+		// 					content: "权限已经被拒绝,请前往设置中开启"
+		// 				})
+		// 		}
+		// 	}
+		// });
+		// #endif
+		let deviceId = "";
+		let uuid2 = uni.getStorageSync("app_uuid");
+		if (uuid2 && uuid2.length >= 6) {
+			deviceId = uuid2;
+		} else {
+			deviceId = uni.getSystemInfoSync().deviceId || uni.getDeviceInfo().deviceId;
+			if (!deviceId || deviceId.length < 6) {
+				deviceId = this.generateUUID();
 			} else {
-				deviceId = uni.getSystemInfoSync().deviceId || uni.getDeviceInfo().deviceId;
-				if (!deviceId || deviceId.length < 6) {
-					deviceId = this.generateUUID();
-				} else {
-					// uni.setStorageSync("app_uuid", deviceId);
-				}
-			}
-			if (deviceId && deviceId.length >= 6) {} else {
-				deviceId = new Date().getTime()
+				// uni.setStorageSync("app_uuid", deviceId);
 			}
-			this.globalData.uuid = deviceId;
-			uni.setStorageSync("app_uuid", deviceId);
-			// this.globalData.uuid = "";
+		}
+		if (deviceId && deviceId.length >= 6) { } else {
+			deviceId = new Date().getTime()
+		}
+		this.globalData.uuid = deviceId;
+		uni.setStorageSync("app_uuid", deviceId);
+		// this.globalData.uuid = "";
 
-			let skey = uni.getStorageSync("wapptoken");
-			if (skey != null && skey != "" && skey != undefined) {
-				this.globalData.skey = skey;
-			}
-			if (this.globalData.headerSign == "") {
-				let headerSignKKK = uni.getStorageSync("headerSign");
-				if (headerSignKKK != null && headerSignKKK != "" && headerSignKKK != undefined) {
-					this.globalData.headerSign = headerSignKKK;
-				}
+		let skey = uni.getStorageSync("wapptoken");
+		if (skey != null && skey != "" && skey != undefined) {
+			this.globalData.skey = skey;
+		}
+		if (this.globalData.headerSign == "") {
+			let headerSignKKK = uni.getStorageSync("headerSign");
+			if (headerSignKKK != null && headerSignKKK != "" && headerSignKKK != undefined) {
+				this.globalData.headerSign = headerSignKKK;
 			}
-			// uni.getPushClientId({
-			// 	success: (res) => {
-			// 		console.log(res.cid);
-			// 		// this.push_token = res.cid;
-			// 	},
-			// 	fail(err) {
-			// 		console.log(err)
-			// 	}
-			// })
-			// this.$nextTick(async () => {
-			// 	for (var i = 0; i < tabbars.length; i++) {
-			// 		await this.go(tabbars[i])
-			// 		console.log('page:', i);
-			// 	}
-			// 	// uni.$store.commit('user/setPageState', 'loadmore')
-			// })
-			// #ifdef APP
-			plus.navigator.closeSplashscreen()
-			// #endif
+		}
+		let nickname = uni.getStorageSync("nickname");
+		if (nickname != null && nickname != "" && nickname != undefined) {
+			this.globalData.nickname = nickname;
+		}
+		let avator = uni.getStorageSync("avator");
+		if (avator != null && avator != "" && avator != undefined) {
+			this.globalData.avator = avator;
+		}
+		let mobile = uni.getStorageSync("mobile");
+		if (mobile != null && mobile != "" && mobile != undefined) {
+			this.globalData.mobile = mobile;
+		}
+		let user_id = uni.getStorageSync("user_id");
+		if (user_id != null && user_id != "" && user_id != undefined) {
+			this.globalData.user_id = user_id;
+		}
+		// uni.getPushClientId({
+		// 	success: (res) => {
+		// 		console.log(res.cid);
+		// 		// this.push_token = res.cid;
+		// 	},
+		// 	fail(err) {
+		// 		console.log(err)
+		// 	}
+		// })
+		// this.$nextTick(async () => {
+		// 	for (var i = 0; i < tabbars.length; i++) {
+		// 		await this.go(tabbars[i])
+		// 		console.log('page:', i);
+		// 	}
+		// 	// uni.$store.commit('user/setPageState', 'loadmore')
+		// })
+		// #ifdef APP
+		plus.navigator.closeSplashscreen()
+		// #endif
 
-			let that = this;
-			uni.$on('check_login', function(callback) {
-				let skeyLogin = uni.getStorageSync("is_login");
-				console.log("skeylogin", skeyLogin);
-				// callback(); 
-				if (skeyLogin == null || skeyLogin == undefined || skeyLogin == "") {
-					uni.removeStorageSync("wapptoken");
-					uni.redirectTo({
-						url: '/pages/login/login'
-					});
-				} else if (typeof callback === 'function') {
-					callback(); // 登录状态有效时执行回调
-				}
-			});
+		let that = this;
+		uni.$on('check_login', function (callback) {
+			let skeyLogin = uni.getStorageSync("is_login");
+			console.log("skeylogin", skeyLogin);
+			// callback(); 
+			if (skeyLogin == null || skeyLogin == undefined || skeyLogin == "") {
+				uni.removeStorageSync("wapptoken");
+				uni.redirectTo({
+					url: '/pages/login/login'
+				});
+			} else if (typeof callback === 'function') {
+				callback(); // 登录状态有效时执行回调
+			}
+		});
 
-		},
+	},
 
-		onShow: function(options) {
-			console.log('App Show');
-			// setTimeout(()=>{
-			//   uni.hideTabBar()  
-			// },100)
-		},
-		onHide: function() {
-			console.log('App Hide');
-		},
-		onExit: function() {
-			unregisterRequestPermissionTipsListener(null)
+	onShow: function (options) {
+		console.log('App Show');
+		// setTimeout(()=>{
+		//   uni.hideTabBar()  
+		// },100)
+	},
+	onHide: function () {
+		console.log('App Hide');
+	},
+	onExit: function () {
+		unregisterRequestPermissionTipsListener(null)
+	},
+	onAppBackground() { },
+	methods: {
+		generateUUID() {
+			return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+				const r = Math.random() * 16 | 0;
+				const v = c === 'x' ? r : (r & 0x3 | 0x8);
+				return v.toString(16);
+			});
 		},
-		onAppBackground() {},
-		methods: {
-			generateUUID() {
-				return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
-					const r = Math.random() * 16 | 0;
-					const v = c === 'x' ? r : (r & 0x3 | 0x8);
-					return v.toString(16);
-				});
-			},
-			go(url) {
-				return new Promise((resolve) => {
-					uni.switchTab({
-						url,
-						complete: () => {
-							setTimeout(() => {
-								resolve()
-							}, 100)
-						}
-					})
+		go(url) {
+			return new Promise((resolve) => {
+				uni.switchTab({
+					url,
+					complete: () => {
+						setTimeout(() => {
+							resolve()
+						}, 100)
+					}
 				})
-			},
-		}
-	};
+			})
+		},
+	}
+};
 </script>
 
 <style lang="scss">
-	/*每个页面公共css */
-	@import 'colorui/main.css';
-	@import 'colorui/icon.css'; 
-	@import 'style/FontStyle.css';
+/*每个页面公共css */
+@import 'colorui/main.css';
+@import 'colorui/icon.css';
+@import 'style/FontStyle.css';
 
 
 
-	body {
-		-webkit-text-size-adjust: none;
-		overscroll-behavior-y: contain;
-		-webkit-overflow-scrolling: touch;
-		background-color: #fff;
-		// background: url('./http://c.yujianmate.com/images/v1/bg.png');
-		// background-size: 750rpx 1624rpx;
-		background-repeat: repeat-y;
-	}
+body {
+	-webkit-text-size-adjust: none;
+	overscroll-behavior-y: contain;
+	-webkit-overflow-scrolling: touch;
+	background-color: #fff;
+	// background: url('./http://c.yujianmate.com/images/v1/bg.png');
+	// background-size: 750rpx 1624rpx;
+	background-repeat: repeat-y;
+}
 
-	/* 超出一行省略号 */
-	.one-omit {
-		white-space: nowrap;
-		/*规定段落中的文本不进行换行*/
-		overflow: hidden;
-		/*内容会被修剪,并且其余内容是不可见的。*/
-		text-overflow: ellipsis;
-		/*显示省略号来代表被修剪的文本*/
-	}
+/* 超出一行省略号 */
+.one-omit {
+	white-space: nowrap;
+	/*规定段落中的文本不进行换行*/
+	overflow: hidden;
+	/*内容会被修剪,并且其余内容是不可见的。*/
+	text-overflow: ellipsis;
+	/*显示省略号来代表被修剪的文本*/
+}
 
-	.two-omit {
-		display: -webkit-box;
-		-webkit-line-clamp: 2;
-		overflow: hidden;
-		text-overflow: ellipsis;
-		-webkit-box-orient: vertical;
-	}
+.two-omit {
+	display: -webkit-box;
+	-webkit-line-clamp: 2;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	-webkit-box-orient: vertical;
+}
 
-	img {
-		width: 100%;
-	}
+img {
+	width: 100%;
+}
 
-	uni-modal {
-		z-index: 998 !important;
-	}
+uni-modal {
+	z-index: 998 !important;
+}
 
-	.cu-modal {
-		z-index: 997 !important;
-	}
- 
+.cu-modal {
+	z-index: 997 !important;
+}
 </style>

+ 14 - 2
components/CommentSection/CommentSection.vue

@@ -51,7 +51,7 @@ export default {
 			required: true
 		},
 		articleInfo: {
-			type: Object, 
+			// type 
 			default: () => ({is_like:0})
 		},
 		articleId: {
@@ -73,6 +73,10 @@ export default {
 	},
 	created() {
 		this.loadCommentData();
+	 
+	},
+	onMounted(){
+
 	},
 	methods: {
 		giveTheThumbsUp(){
@@ -231,9 +235,17 @@ export default {
 				success: (res) => {
 					console.log("删除结果:", res.data);
 					if (res.data.success === "yes") {
-						callback(res);
 						this.loadCommentData(); // 重新加载评论列表
 					}
+					callback(res);
+					if (res.data.success === "no" && res.data.str ) {
+						console.log(1);
+						
+					uni.showToast({
+						title: res.data.str,
+						icon: 'none'
+					});
+					}
 				},
 				fail: (e) => {
 					console.log("删除失败:", e);

+ 162 - 142
components/card/card.vue

@@ -1,22 +1,23 @@
 <template>
-	<view class="waterfall-item" :style="{width,...customStyle}" @click="goWork(item)">
-		<view class="waterfall-item__image" :style="{width,backgroundColor:item.backgroundColor}">
+	<view class="waterfall-item" :style="{ width, ...customStyle }" @click="goWork(item)">
+		<view class="waterfall-item__image" :style="{ width, backgroundColor: item.backgroundColor }">
 			<image :src="item.image" mode="widthFix" style="border-radius: 20rpx;"></image>
 		</view>
 		<view class="waterfall-item__ft">
 			<view class="waterfall-item__ft__title">
-				<text class="value" :style="{color:textColor}">{{item.title}}</text>
+				<text class="value" :style="{ color: textColor }">{{ item.title }}</text>
 			</view>
 			<view class="waterfall-item__ft__desc">
 				<view class="user">
 					<image :src="item.avator" mode="aspectFill" class="avater">
 					</image>
-					<text class="name" :style="{color:textColor}">{{item.nickname}}</text>
+					<text class="name" :style="{ color: textColor }">{{ item.nickname }}</text>
 				</view>
 				<view class="like" @click="changeLike">
-					<image class="like-icon" :src="isLike ?'/static/icon/icon-18.png':'/static/icon/icon-19.png'" mode="">
+					<image class="like-icon" :src="isLike ? '/static/icon/icon-18.png' : '/static/icon/icon-19.png'"
+						mode="">
 					</image>
-					<text class="value" :style="{color:textColor}">{{item.num_like}}</text>
+					<text class="value" :style="{ color: textColor }">{{ item.num_like }}</text>
 				</view>
 			</view>
 			<view class="waterfall-item__ft__btn" v-if="false">
@@ -24,10 +25,10 @@
 				</image>
 			</view>
 		</view>
-		<view class="waterfall-item__tips">
+		<view class="waterfall-item__tips" v-if="findType != 'search'">
 			<view class="item">
 				<image class="look-icon" src="@/static/icon/icon-17.png" mode=""></image>
-				<text class="value" style="color: #fff;">{{item.num_view}}</text>
+				<text class="value" style="color: #fff;">{{ item.num_view }}</text>
 			</view>
 			<view class="item" style="background-color: #1F1F1F;">
 				<text class="value" style="color: #ACF934;">精选</text>
@@ -37,184 +38,203 @@
 </template>
 
 <script>
-	export default {
-		name: "card",
-		props: {
-			width: {
-				type: String,
-				default: ''
-			},
-			item: {
-				type: Object,
-				default: () => ({})
-			},
-			textColor: {
-				type: String,
-				default: '#ff0000'
-			},
-			customStyle: {
-				type: Object,
-				default: () => ({
-					background: '#3F4141',
-				})
-			}
+export default {
+	name: "card",
+	props: {
+		width: {
+			type: String,
+			default: ''
 		},
-		data() {
-			return {
-				isLike: false,
-			};
+		item: {
+			type: Object,
+			default: () => ({})
 		},
-		methods: {
-			changeLike() {
-				this.isLike = !this.isLike
-			},
-			goWork(item) {
-				uni.$emit('check_login', () => {
-					uni.navigateTo({
-						url: '/pages/index/workDetail?id=' + item.id
-					})
-				})
+		textColor: {
+			type: String,
+			default: '#ff0000'
+		},
+		customStyle: {
+			type: Object,
+			default: () => ({
+				background: '#3F4141',
+			})
+		},
+		goLink: {
+			type: String,
+			default: ''
+		},
+		findType: {
+			type: String,
+			default: ''
+		}
+	},
+	data() {
+		return {
+			isLike: false,
+		};
+	},
+	mounted() {
+		console.log('card', this.findType);
+		
+	},
+	methods: {
+		changeLike() {
+			this.isLike = !this.isLike
+		},
+		goWork(item) {
+			var url = '/pages/index/workDetail?id='
+			if (this.goLink) {
+				url = this.goLink
 			}
+			console.log('goWork', this.goLink);
+
+			uni.$emit('check_login', () => {
+				uni.navigateTo({
+					url: url + item.id
+				})
+			})
 		}
 	}
+}
 </script>
 
 <style lang="scss">
-	.waterfall-item {
-		margin-bottom: 20rpx;
-		border-radius: 30rpx;
-		position: relative;
-
-		// #ifndef APP-NVUE
-		.waterfall-item__image {
+.waterfall-item {
+	margin-bottom: 20rpx;
+	border-radius: 30rpx;
+	position: relative;
+
+	// #ifndef APP-NVUE
+	.waterfall-item__image {
+		width: 100%;
+		height: auto;
+		box-sizing: border-box;
+		padding: 28rpx 20rpx;
+
+		image {
 			width: 100%;
-			height: auto;
-			box-sizing: border-box;
-			padding: 28rpx 20rpx;
-			image {
-				width: 100%;
-				height: 100%;
-			}
+			height: 100%;
 		}
+	}
 
+	// #endif
+	.waterfall-item__tips {
+		position: absolute;
+		top: 10rpx;
+		padding: 0rpx 10rpx;
+		// #ifndef APP-NVUE
+		box-sizing: border-box;
 		// #endif
-		.waterfall-item__tips {
-			position: absolute;
-			top: 10rpx;
-			padding: 0rpx 10rpx;
+		width: 100%;
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		justify-content: space-between;
+
+		.item {
 			// #ifndef APP-NVUE
-			box-sizing: border-box;
 			// #endif
+			display: flex;
+			flex-direction: row;
+			align-items: center;
+			background: rgba(0, 0, 0, 0.2);
+			padding: 4rpx 14rpx;
+			border-radius: 12rpx;
+
+			.look-icon {
+				width: 26rpx;
+				height: 26rpx;
+				margin-right: 10rpx;
+			}
+
+			.value {
+				font-size: 20rpx;
+			}
+		}
+	}
+
+	.waterfall-item__ft {
+		padding: 10rpx 20rpx;
+
+		&__title {
+			line-height: 48rpx;
+			font-weight: 700;
+
+			.value {
+				font-size: 30rpx;
+			}
+		}
+
+		&__desc {
 			width: 100%;
+			// #ifndef APP-NVUE
 			display: flex;
+			// #endif
+			// #ifdef APP-NVUE
+			// #endif
 			flex-direction: row;
 			align-items: center;
 			justify-content: space-between;
+			margin: 20rpx 0;
 
-			.item {
+			.user {
 				// #ifndef APP-NVUE
-				// #endif
 				display: flex;
+				// #endif
+				// #ifdef APP-NVUE
 				flex-direction: row;
+				// #endif
 				align-items: center;
-				background: rgba(0, 0, 0, 0.2);
-				padding: 4rpx 14rpx;
-				border-radius: 12rpx;
 
-				.look-icon {
-					width: 26rpx;
-					height: 26rpx;
+				.avater {
+					width: 36rpx;
+					height: 36rpx;
+					border-radius: 18rpx;
 					margin-right: 10rpx;
 				}
 
-				.value {
-					font-size: 20rpx;
-				}
-			}
-		}
-
-		.waterfall-item__ft {
-			padding: 10rpx 20rpx;
-
-			&__title {
-				line-height: 48rpx;
-				font-weight: 700;
-
-				.value {
-					font-size: 30rpx;
+				.name {
+					font-size: 24rpx;
 				}
 			}
 
-			&__desc {
-				width: 100%;
+			.like {
 				// #ifndef APP-NVUE
 				display: flex;
 				// #endif
 				// #ifdef APP-NVUE
-				// #endif
 				flex-direction: row;
+				// #endif
 				align-items: center;
-				justify-content: space-between;
-				margin: 20rpx 0;
-
-				.user {
-					// #ifndef APP-NVUE
-					display: flex;
-					// #endif
-					// #ifdef APP-NVUE
-					flex-direction: row;
-					// #endif
-					align-items: center;
-
-					.avater {
-						width: 36rpx;
-						height: 36rpx;
-						border-radius: 18rpx;
-						margin-right: 10rpx;
-					}
-
-					.name {
-						font-size: 24rpx;
-					}
+
+				.like-icon {
+					width: 30rpx;
+					height: 30rpx;
+					margin-right: 10rpx;
 				}
 
-				.like {
-					// #ifndef APP-NVUE
-					display: flex;
-					// #endif
-					// #ifdef APP-NVUE
-					flex-direction: row;
-					// #endif
-					align-items: center;
-
-					.like-icon {
-						width: 30rpx;
-						height: 30rpx;
-						margin-right: 10rpx;
-					}
-
-					.value {
-						font-size: 24rpx;
-					}
+				.value {
+					font-size: 24rpx;
 				}
 			}
+		}
 
-			&__btn {
-				height: 65rpx;
-				border-radius: 18px;
-				background-image: linear-gradient(to bottom, #BFFE9C, #AEE75D);
-				// #ifndef APP-NVUE
-				background: linear-gradient(180deg, #BFFE9C 0%, #AEE75D 62%, #AEE75D 100%);
-				display: flex;
-				// #endif
-				justify-content: center;
-				align-items: center;
+		&__btn {
+			height: 65rpx;
+			border-radius: 18px;
+			background-image: linear-gradient(to bottom, #BFFE9C, #AEE75D);
+			// #ifndef APP-NVUE
+			background: linear-gradient(180deg, #BFFE9C 0%, #AEE75D 62%, #AEE75D 100%);
+			display: flex;
+			// #endif
+			justify-content: center;
+			align-items: center;
 
-				&__icon {
-					width: 155rpx;
-					height: 32rpx;
-				}
+			&__icon {
+				width: 155rpx;
+				height: 32rpx;
 			}
 		}
 	}
+}
 </style>

+ 428 - 415
components/cc-comment/cc-comment.vue

@@ -1,5 +1,5 @@
 <template>
-	<view> 
+	<view>
 		<view class="c_total">评论 {{ tableTotal }}</view>
 		<template v-if="dataList && dataList.length">
 			<view class="c_comment" v-for="(item1, index1) in dataList" :key="item1.id">
@@ -7,6 +7,7 @@
 				<CommonComp :data="item1" @likeClick="() => likeClick({ item1, index1 })"
 					@replyClick="() => replyClick({ item1, index1 })"
 					@deleteClick="() => deleteClick({ item1, index1 })" />
+			 
 				<view class="children_item" v-if="item1.children && item1.children.length">
 					<!-- 二级评论 -->
 					<CommonComp v-for="(item2, index2) in item1.childrenShow" :key="item2.id" :data="item2"
@@ -31,7 +32,7 @@
 		<!-- 空盒子 -->
 		<view class="empty_box" v-else>
 			<!-- <uni-icons type="chatboxes" size="36" color="#c0c0c0"></uni-icons> -->
-			 <image src="@/static/icon/quexing_01.png" style="width: 380rpx; height: 308rpx;"></image>
+			<image src="@/static/icon/quexing_01.png" style="width: 380rpx; height: 308rpx;"></image>
 			<view>
 				<span class="txt"> 来鼓励一下作者吧~ </span>
 				<span class="txt click" @click="() => newCommentFun()">说点什么...</span>
@@ -45,23 +46,16 @@
 						<span class="text_aid">回复给</span>
 						<img class="user_avatar"
 							:src="replyTemp.item2 ? replyTemp.item2.user_avatar : replyTemp.item1.user_avatar" />
-						<span
-							class="text_main">{{ replyTemp.item2 ? replyTemp.item2.user_name : replyTemp.item1.user_name }}</span>
+						<span class="text_main">{{ replyTemp.item2 ? replyTemp.item2.user_name :
+							replyTemp.item1.user_name }}</span>
 					</template>
 					<span v-else class="text_main">发表新评论</span>
 				</view>
 				<view class="content">
 					<view class="text_area">
-						<textarea 
-							class="textarea" 
-							v-model="commentValue"
-							:placeholder="commentPlaceholder"
-							:focus="focus"
-							maxlength="300"
-							auto-height
-							@focus="onTextareaFocus"
-							@keydown="handleKeydown"  
-						></textarea>
+						<textarea class="textarea" v-model="commentValue" :placeholder="commentPlaceholder"
+							:focus="focus" maxlength="300" auto-height @focus="onTextareaFocus"
+							@keydown="handleKeydown"></textarea>
 						<view class="emoji-trigger" @tap="toggleEmojiPanel">
 							<text class="fa fa-smile-o"></text>
 						</view>
@@ -71,12 +65,8 @@
 				<!-- 表情面板 -->
 				<view class="emoji-panel" v-if="showEmojiPanel">
 					<view class="emoji-grid">
-						<view 
-							class="emoji-item" 
-							v-for="(emoji, index) in emojiList" 
-							:key="index"
-							@tap="selectEmoji(emoji)"
-						>
+						<view class="emoji-item" v-for="(emoji, index) in emojiList" :key="index"
+							@tap="selectEmoji(emoji)">
 							{{ emoji }}
 						</view>
 					</view>
@@ -84,69 +74,72 @@
 			</view>
 		</uni-popup>
 		<!-- 删除弹窗 -->
-		<uni-popup ref="delPopupRef" type="dialog">
+		<!-- <uni-popup ref="delPopupRef" type="dialog">
 			<uni-popup-dialog mode="base" title="" content="确定删除这条评论吗?" :before-close="true" @close="delCloseFun"
 				@confirm="delConfirmFun"></uni-popup-dialog>
-		</uni-popup>
+		</uni-popup> -->
+
+		<DialogBox ref="DialogBox"></DialogBox>
+		
 	</view>
 </template>
 
 <script>
-	import CommonComp from "./componets/common";
-	export default {
-		components: {
-			CommonComp
+import CommonComp from "./componets/common";
+export default {
+	components: {
+		CommonComp
+	},
+	props: {
+		/** 登陆用户信息
+		 *    id: number // 登陆用户id
+		 *    user_name: number // 登陆用户名
+		 *    user_avatar: string // 登陆用户头像地址
+		 */
+		myInfo: {
+			type: Object,
+			default: () => { },
 		},
-		props: {
-			/** 登陆用户信息
-			 *    id: number // 登陆用户id
-			 *    user_name: number // 登陆用户名
-			 *    user_avatar: string // 登陆用户头像地址
-			 */
-			myInfo: {
-				type: Object,
-				default: () => {},
-			},
-			/** 文章作者信息
-			 *    id: number // 文章作者id
-			 *    user_name: number // 文章作者名
-			 *    user_avatar: string // 文章作者头像地址
-			 */
-			userInfo: {
-				type: Object,
-				default: () => {},
-			},
-			/** 评论列表
-			 *    id: number // 评论id
-			 *    parent_id: number // 父级评论id
-			 *    reply_id: number // 被回复人评论id
-			 *    reply_name: string // 被回复人名称
-			 *    user_name: string // 用户名
-			 *    user_avatar: string // 评论者头像地址
-			 *    user_content: string // 评论内容
-			 *    is_like: boolean // 是否点赞
-			 *    like_count: number // 点赞数统计
-			 *    create_time: string // 创建时间
-			 */
-			tableData: {
-				type: Array,
-				default: () => [],
-			},
-			// 评论总数
-			tableTotal: {
-				type: Number,
-				default: 0,
-			},
-			// 评论删除模式
-			// bind - 当被删除的一级评论存在回复评论, 那么该评论内容变更显示为[当前评论内容已被移除]
-			// only - 仅删除当前评论(后端删除相关联的回复评论, 否则总数显示不对)
-			// all - 删除所有评论包括回复评论
-			deleteMode: {
-				type: String,
-				default: "all",
-			},
+		/** 文章作者信息
+		 *    id: number // 文章作者id
+		 *    user_name: number // 文章作者名
+		 *    user_avatar: string // 文章作者头像地址
+		 */
+		userInfo: {
+			type: Object,
+			default: () => { },
+		},
+		/** 评论列表
+		 *    id: number // 评论id
+		 *    parent_id: number // 父级评论id
+		 *    reply_id: number // 被回复人评论id
+		 *    reply_name: string // 被回复人名称
+		 *    user_name: string // 用户名
+		 *    user_avatar: string // 评论者头像地址
+		 *    user_content: string // 评论内容
+		 *    is_like: boolean // 是否点赞
+		 *    like_count: number // 点赞数统计
+		 *    create_time: string // 创建时间
+		 */
+		tableData: {
+			type: Array,
+			default: () => [],
 		},
-		data() {
+		// 评论总数
+		tableTotal: {
+			type: Number,
+			default: 0,
+		},
+		// 评论删除模式
+		// bind - 当被删除的一级评论存在回复评论, 那么该评论内容变更显示为[当前评论内容已被移除]
+		// only - 仅删除当前评论(后端删除相关联的回复评论, 否则总数显示不对)
+		// all - 删除所有评论包括回复评论
+		deleteMode: {
+			type: String,
+			default: "all",
+		},
+	},
+	data() {
 		return {
 			dataList: [], // 渲染数据(前端的格式)
 			replyTemp: {}, // 回复临时数据
@@ -166,327 +159,338 @@
 			]
 		};
 	},
-		watch: {
-			tableData: {
-				handler(newVal) {
-					if (newVal.length !== this.dataList.length) {
-						this.dataList = this.treeTransForm(newVal);
-					}
-				},
-				deep: true,
-				immediate: true,
+	watch: {
+		tableData: {
+			handler(newVal) {
+				if (newVal.length !== this.dataList.length) {
+					this.dataList = this.treeTransForm(newVal);
+				}
 			},
+			deep: true,
+			immediate: true,
 		},
-		mounted() {},
-		methods: {
-			// 数据转换
-			treeTransForm(data) {
-				let newData = JSON.parse(JSON.stringify(data));
-				let result = [];
-				let map = {};
-				newData.forEach((item, i) => {
-					item.owner = item.user_id === this.myInfo.user_id; // 是否为当前登陆用户
-					item.author = item.user_id === this.userInfo.user_id; // 是否为作者
-					map[item.id] = item;
-				});
-				newData.forEach((item) => {
-					let parent = map[item.parent_id];
-					if (parent) {
-						(parent.children || (parent.children = [])).push(item); // 所有回复
-						if (parent.children.length === 1) {
-							(parent.childrenShow = []).push(item); // 显示的回复
-						}
-					} else {
-						result.push(item);
-					}
-				});
-				return result;
-			},
-			// 点赞
-			setLike(item) {
-				item.is_like = !item.is_like;
-				item.like_count = item.is_like ? item.like_count + 1 : item.like_count - 1;
-			},
-			likeClick({
-				item1,
-				index1,
-				item2,
-				index2
-			}) {
-				let item = item2 || item1;
-				let that = this;
-				this.setLike(item);
-				this.$emit("likeFun", {
-					params: item
-				}, (res) => {
-					// 请求后端失败, 重置点赞
-					that.setLike(item);
-				});
-			},
-			// 回复
-			replyClick({
-				item1,
-				index1,
-				item2,
-				index2
-			}) {
-				this.replyTemp = JSON.parse(JSON.stringify({
-					item1,
-					index1,
-					item2,
-					index2
-				}));
-				this.$refs["cPopupRef"].open();
-			},
-			// 发起新评论
-			newCommentFun() {
-				this.isNewComment = true;
-				this.$refs["cPopupRef"].open();
-			},
-			// 评论弹窗
-	popChange(e) {
-		// 关闭弹窗
-		if (!e.show) {
-			this.commentValue = ""; // 清空输入框值
-			this.replyTemp = {}; // 清空被回复人信息
-			this.isNewComment = false; // 恢复是否为新评论默认值
-			this.showEmojiPanel = false; // 隐藏表情面板
-		}
-		this.focus = e.show;
-	},
-	// 切换表情面板显示状态
-	toggleEmojiPanel() {
-		this.showEmojiPanel = !this.showEmojiPanel;
 	},
-	// 选择表情
-	selectEmoji(emoji) {
-		this.commentValue += emoji;
-	},
-	// 输入框获取焦点时
-	onTextareaFocus() {
-		// 可以选择不关闭表情面板,让用户同时使用键盘和表情
-		// this.showEmojiPanel = false;
-	},
-			// 发送评论
-			sendClick({
+	mounted() { },
+	methods: {
+		// 数据转换
+		treeTransForm(data) {
+			let newData = JSON.parse(JSON.stringify(data));
+			let result = [];
+			let map = {};
+			
+			newData.forEach((item, i) => { 
+				item.owner = item.user_id === this.myInfo.user_id; // 是否为当前登陆用户
+				item.author = item.user_id === this.userInfo.user_id; // 是否为作者
+				map[item.id] = item;
+			});
+			newData.forEach((item) => {
+				let parent = map[item.parent_id];
+				if (parent) {
+					(parent.children || (parent.children = [])).push(item); // 所有回复
+					if (parent.children.length === 1) {
+						(parent.childrenShow = []).push(item); // 显示的回复
+					}
+				} else {
+					result.push(item);
+				}
+			});
+			return result;
+		},
+		// 点赞
+		setLike(item) {
+			item.is_like = !item.is_like;
+			item.like_count = item.is_like ? item.like_count + 1 : item.like_count - 1;
+		},
+		likeClick({
+			item1,
+			index1,
+			item2,
+			index2
+		}) {
+			let item = item2 || item1;
+			let that = this;
+			this.setLike(item);
+			this.$emit("likeFun", {
+				params: item
+			}, (res) => {
+				// 请求后端失败, 重置点赞
+				that.setLike(item);
+			});
+		},
+		// 回复
+		replyClick({
+			item1,
+			index1,
+			item2,
+			index2
+		}) {
+			this.replyTemp = JSON.parse(JSON.stringify({
 				item1,
 				index1,
 				item2,
 				index2
-			} = this.replyTemp) {
-				let item = item2 || item1;
-				let params = {};
-				// 新评论
-				if (this.isNewComment) {
-					params = {
-						id: Math.random(), // 评论id
-						parent_id: null, // 父级评论id
-						reply_id: null, // 被回复评论id
-						reply_name: null, // 被回复人名称
-					};
-				} else {
-					// 回复评论
-					params = {
-						id: Math.random(), // 评论id
-						parent_id: item?.parent_id ?? item.id, // 父级评论id
-						reply_id: item.id, // 被回复评论id
-						reply_name: item.user_name, // 被回复人名称
-					};
-				}
+			}));
+			this.$refs["cPopupRef"].open();
+		},
+		// 发起新评论
+		newCommentFun() {
+			this.isNewComment = true;
+			this.$refs["cPopupRef"].open();
+		},
+		// 评论弹窗
+		popChange(e) {
+			// 关闭弹窗
+			if (!e.show) {
+				this.commentValue = ""; // 清空输入框值
+				this.replyTemp = {}; // 清空被回复人信息
+				this.isNewComment = false; // 恢复是否为新评论默认值
+				this.showEmojiPanel = false; // 隐藏表情面板
+			}
+			this.focus = e.show;
+		},
+		// 切换表情面板显示状态
+		toggleEmojiPanel() {
+			this.showEmojiPanel = !this.showEmojiPanel;
+		},
+		// 选择表情
+		selectEmoji(emoji) {
+			this.commentValue += emoji;
+		},
+		// 输入框获取焦点时
+		onTextareaFocus() {
+			// 可以选择不关闭表情面板,让用户同时使用键盘和表情
+			// this.showEmojiPanel = false;
+		},
+		// 发送评论
+		sendClick({
+			item1,
+			index1,
+			item2,
+			index2
+		} = this.replyTemp) {
+			let item = item2 || item1;
+			let params = {};
+			// 新评论
+			if (this.isNewComment) {
+				params = {
+					id: Math.random(), // 评论id
+					parent_id: null, // 父级评论id
+					reply_id: null, // 被回复评论id
+					reply_name: null, // 被回复人名称
+				};
+			} else {
+				// 回复评论
+				params = {
+					id: Math.random(), // 评论id
+					parent_id: item && item.parent_id ? item.parent_id : item.id, // 父级评论id
+					reply_id: item.id, // 被回复评论id
+					reply_name: item.user_name, // 被回复人名称
+				};
+			}
+			params = {
+				...params,
+				user_id: this.myInfo.user_id, // 用户id
+				user_name: this.myInfo.user_name, // 用户名
+				user_avatar: this.myInfo.user_avatar, // 用户头像地址
+				user_content: this.commentValue, // 用户评论内容
+				is_like: false, // 是否点赞
+				like_count: 0, // 点赞数统计
+				create_time: "刚刚", // 创建时间
+				owner: true, // 是否为所有者 所有者可以进行删除 管理员默认true
+			}; 
+			uni.showLoading({
+				title: "正在发送",
+				mask: true,
+			});
+			this.$emit("replyFun", {
+				params
+			}, (res) => {
+				uni.hideLoading();
+				// 拿到后端返回的id赋值, 因为删除要用到id
 				params = {
 					...params,
-					user_id: this.myInfo.user_id, // 用户id
-					user_name: this.myInfo.user_name, // 用户名
-					user_avatar: this.myInfo.user_avatar, // 用户头像地址
-					user_content: this.commentValue, // 用户评论内容
-					is_like: false, // 是否点赞
-					like_count: 0, // 点赞数统计
-					create_time: "刚刚", // 创建时间
-					owner: true, // 是否为所有者 所有者可以进行删除 管理员默认true
+					id: res.id
 				};
-
-				uni.showLoading({
-					title: "正在发送",
-					mask: true,
-				});
-				this.$emit("replyFun", {
-					params
-				}, (res) => {
-					uni.hideLoading();
-					// 拿到后端返回的id赋值, 因为删除要用到id
-					params = {
-						...params,
-						id: res.id
-					};
-					// 新评论
-					if (this.isNewComment) {
-						this.dataList.push(params);
-					} else {
-						// 回复
-						let c_data = this.dataList[index1];
-						(c_data.children || (c_data.children = [])).push(params);
-						// 如果已展开所有回复, 那么此时插入children长度会大于childrenShow长度1, 所以就直接展开显示即可
-						if (c_data.children.length === (c_data.childrenShow || (c_data.childrenShow = [])).length +
-							1) {
-							c_data.childrenShow.push(params);
-						}
+				// 新评论
+				if (this.isNewComment) {
+					this.dataList.push(params);
+				} else {
+					// 回复
+					let c_data = this.dataList[index1];
+					(c_data.children || (c_data.children = [])).push(params);
+					// 如果已展开所有回复, 那么此时插入children长度会大于childrenShow长度1, 所以就直接展开显示即可
+					if (c_data.children.length === (c_data.childrenShow || (c_data.childrenShow = [])).length +
+						1) {
+						c_data.childrenShow.push(params);
 					}
-					this.$emit("update:tableTotal", this.tableTotal + 1);
-					this.$refs["cPopupRef"].close();
-				});
-			},
-			//删除
-			deleteClick({
-				item1,
-				index1,
-				item2,
-				index2
-			}) {
-				this.delTemp = JSON.parse(JSON.stringify({
-					item1,
-					index1,
-					item2,
-					index2
-				}));
-				this.$refs["delPopupRef"].open();
-			},
-			// 关闭删除弹窗
-			delCloseFun() {
-				this.delTemp = {};
-				this.$refs["delPopupRef"].close();
-			},
-			// 确定删除
-			delConfirmFun({
+				}
+				this.$emit("update:tableTotal", this.tableTotal + 1);
+				this.$refs["cPopupRef"].close();
+			});
+		},
+		//删除
+		deleteClick({
+			item1,
+			index1,
+			item2,
+			index2
+		}) {
+			this.delTemp = JSON.parse(JSON.stringify({
 				item1,
 				index1,
 				item2,
 				index2
-			} = this.delTemp) {
-				const deleteMode = this.deleteMode;
-				let c_data = this.dataList[index1];
-				uni.showLoading({
-					title: "正在删除",
-					mask: true,
+			}));
+			this.$refs['DialogBox'].confirm({
+				title: '提示',
+				content: '确定要删除该评论及其回复吗?',
+				DialogType: 'inquiry',
+				btn1: '取消',
+				btn2: '确定',
+				animation: 0
+			}).then(() => {
+				 this.delConfirmFun()
+			}).catch(() => {
+				this.delCloseFun()
+			})
+		},
+		// 关闭删除弹窗
+		delCloseFun() {
+			this.delTemp = {};
+			// this.$refs["delPopupRef"].close();
+		},
+		// 确定删除
+		delConfirmFun({
+			item1,
+			index1,
+			item2,
+			index2
+		} = this.delTemp) {
+			const deleteMode = this.deleteMode;
+			let c_data = this.dataList[index1];
+			uni.showLoading({
+				title: "正在删除",
+				mask: true,
+			});
+			// 删除二级评论
+			if (index2 >= 0) {
+				this.$emit("deleteFun", {
+					params: [c_data.children[index2].id],
+					mode: deleteMode
+				}, (res) => {
+					uni.hideLoading();
+					this.$emit("update:tableTotal", this.tableTotal - 1);
+					c_data.children.splice(index2, 1);
+					c_data.childrenShow.splice(index2, 1);
 				});
-				// 删除二级评论
-				if (index2 >= 0) {
+			} else {
+				// 删除一级评论
+				if (c_data?.children?.length) {
+					// 如果一级评论包含回复评论
+					switch (deleteMode) {
+						case "bind":
+							// 一级评论内容展示修改为: 当前评论内容已被移除
+							this.$emit(
+								"deleteFun", {
+								params: [c_data.id],
+								mode: deleteMode,
+							},
+								(res) => {
+									uni.hideLoading();
+									c_data.user_content = "当前评论内容已被移除";
+								}
+							);
+							break;
+						case "only":
+							// 后端自行根据删除的一级评论id, 查找关联的子评论进行删除
+							this.$emit(
+								"deleteFun", {
+								params: [c_data.id],
+								mode: deleteMode,
+							},
+								(res) => {
+									uni.hideLoading();
+									this.$emit("update:tableTotal", this.tableTotal - c_data.children.length + 1);
+									this.dataList.splice(index1, 1);
+								}
+							);
+							break;
+						default:
+							// all
+							// 收集子评论id, 提交给后端统一删除
+							let delIdArr = [c_data.id];
+							c_data.children.forEach((_, i) => {
+								delIdArr.push(_.id);
+							});
+							this.$emit("deleteFun", {
+								params: delIdArr,
+								mode: deleteMode
+							}, (res) => {
+								uni.hideLoading();
+								this.$emit("update:tableTotal", this.tableTotal - c_data.children.length + 1);
+								this.dataList.splice(index1, 1);
+							});
+							break;
+					}
+				} else {
+					// 一级评论无回复, 直接删除
 					this.$emit("deleteFun", {
-						params: [c_data.children[index2].id],
+						params: [c_data.id],
 						mode: deleteMode
 					}, (res) => {
 						uni.hideLoading();
 						this.$emit("update:tableTotal", this.tableTotal - 1);
-						c_data.children.splice(index2, 1);
-						c_data.childrenShow.splice(index2, 1);
+						this.dataList.splice(index1, 1);
 					});
-				} else {
-					// 删除一级评论
-					if (c_data?.children?.length) {
-						// 如果一级评论包含回复评论
-						switch (deleteMode) {
-							case "bind":
-								// 一级评论内容展示修改为: 当前评论内容已被移除
-								this.$emit(
-									"deleteFun", {
-										params: [c_data.id],
-										mode: deleteMode,
-									},
-									(res) => {
-										uni.hideLoading();
-										c_data.user_content = "当前评论内容已被移除";
-									}
-								);
-								break;
-							case "only":
-								// 后端自行根据删除的一级评论id, 查找关联的子评论进行删除
-								this.$emit(
-									"deleteFun", {
-										params: [c_data.id],
-										mode: deleteMode,
-									},
-									(res) => {
-										uni.hideLoading();
-										this.$emit("update:tableTotal", this.tableTotal - c_data.children.length + 1);
-										this.dataList.splice(index1, 1);
-									}
-								);
-								break;
-							default:
-								// all
-								// 收集子评论id, 提交给后端统一删除
-								let delIdArr = [c_data.id];
-								c_data.children.forEach((_, i) => {
-									delIdArr.push(_.id);
-								});
-								this.$emit("deleteFun", {
-									params: delIdArr,
-									mode: deleteMode
-								}, (res) => {
-									uni.hideLoading();
-									this.$emit("update:tableTotal", this.tableTotal - c_data.children.length + 1);
-									this.dataList.splice(index1, 1);
-								});
-								break;
-						}
-					} else {
-						// 一级评论无回复, 直接删除
-						this.$emit("deleteFun", {
-							params: [c_data.id],
-							mode: deleteMode
-						}, (res) => {
-							uni.hideLoading();
-							this.$emit("update:tableTotal", this.tableTotal - 1);
-							this.dataList.splice(index1, 1);
-						});
-					}
 				}
-				this.delCloseFun();
-			},
-			// 展开评论if
-			expandTxtShow({
-				item1,
-				index1
-			}) {
-				return item1.childrenShow?.length && item1.children.length - item1.childrenShow.length;
-			},
-			// 展开更多评论
-			expandReplyFun({
-				item1,
-				index1
-			}) {
-				let csLen = this.dataList[index1].childrenShow.length;
-				this.dataList[index1].childrenShow.push(
-					...this.dataList[index1].children.slice(csLen, csLen + 6) // 截取5条评论
-				);
-			},
+			}
+			this.delCloseFun();
+		},
+		// 展开评论if
+		expandTxtShow({
+			item1,
+			index1
+		}) {
+			return item1.childrenShow?.length && item1.children.length - item1.childrenShow.length;
+		},
+		// 展开更多评论
+		expandReplyFun({
+			item1,
+			index1
+		}) {
+			let csLen = this.dataList[index1].childrenShow.length;
+			this.dataList[index1].childrenShow.push(
+				...this.dataList[index1].children.slice(csLen, csLen + 6) // 截取5条评论
+			);
+		},
 
-			// 收起评论if
-			shrinkTxtShow({
-				item1,
-				index1
-			}) {
-				return item1.childrenShow?.length >= 2 && item1.children.length - item1.childrenShow.length === 0;
-			},
-			// 收起更多评论
-			shrinkReplyFun({
-				item1,
-				index1
-			}) {
-				this.dataList[index1].childrenShow = [];
-				this.dataList[index1].childrenShow.push(
-					...this.dataList[index1].children.slice(0, 1) // 截取1条评论
-				);
-			},
-			handleKeydown(event) { // 新增方法
-				console.log(event.key, event.shiftKey);
-				
-				if (event.key === 'Enter' && !event.shiftKey) {
-					event.preventDefault();
-					this.sendClick();
-				}
-			},
+		// 收起评论if
+		shrinkTxtShow({
+			item1,
+			index1
+		}) {
+			return item1.childrenShow?.length >= 2 && item1.children.length - item1.childrenShow.length === 0;
+		},
+		// 收起更多评论
+		shrinkReplyFun({
+			item1,
+			index1
+		}) {
+			this.dataList[index1].childrenShow = [];
+			this.dataList[index1].childrenShow.push(
+				...this.dataList[index1].children.slice(0, 1) // 截取1条评论
+			);
 		},
-	};
+		handleKeydown(event) { // 新增方法
+			console.log(event.key, event.shiftKey);
+
+			if (event.key === 'Enter' && !event.shiftKey) {
+				event.preventDefault();
+				this.sendClick();
+			}
+		},
+	},
+};
 </script>
 
 <style lang="scss" scoped>
@@ -496,6 +500,7 @@
 	font-weight: normal;
 	font-style: normal;
 }
+
 .fa {
 	display: inline-block;
 	font: normal normal normal 14px/1 FontAwesome;
@@ -503,62 +508,68 @@
 	text-rendering: auto;
 	-webkit-font-smoothing: antialiased;
 }
+
 .fa-smile-o:before {
 	content: "\f118";
 }
-	////////////////////////
-	.center {
-		display: flex;
-		align-items: center;
-	}
 
-	////////////////////////
-	.c_total {
-		padding: 20rpx 30rpx 0 30rpx;
-		font-size: 28rpx;
-	}
+////////////////////////
+.center {
+	display: flex;
+	align-items: center;
+}
 
-	.empty_box {
-		display: flex;
-		justify-content: center;
-		align-items: center;
-		flex-direction: column;
-		padding: 150rpx 10rpx;
-		font-size: 28rpx;
+////////////////////////
+.c_total {
+	padding: 20rpx 30rpx 0 30rpx;
+	font-size: 28rpx;
+}
 
-		.txt {
-			color: $uni-text-color-disable;
-		}
+.empty_box {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	flex-direction: column;
+	padding: 150rpx 10rpx;
+	font-size: 28rpx;
 
-		.click {
-			color: $uni-color-primary;
-		}
+	.txt {
+		color: $uni-text-color-disable;
 	}
 
-	.c_comment {
-		padding: 20rpx 30rpx;
-		font-size: 28rpx;
+	.click {
+		color: $uni-color-primary;
+	}
+}
+
+.c_comment {
+	padding: 20rpx 30rpx;
+	font-size: 28rpx;
 
-		.children_item {
-			padding: 10rpx 0rpx 10rpx 0rpx;
+	.children_item {
+		padding: 10rpx 0rpx 10rpx 0rpx;
+		margin-top: 10rpx;
+		margin-left: 80rpx;
+		background-color: $uni-bg-color-grey;
+
+		.expand_reply,
+		.shrink_reply {
 			margin-top: 10rpx;
 			margin-left: 80rpx;
-			background-color: $uni-bg-color-grey;
-
-			.expand_reply,
-			.shrink_reply {
-				margin-top: 10rpx;
-				margin-left: 80rpx;
 
-				.txt {
-					font-weight: 600;
-					color: $uni-color-primary;
-				}
+			.txt {
+				font-weight: 600;
+				color: $uni-color-primary;
+				font-size: 26rpx !important;
+			}
+			.uni-icons{
+				font-size: 30rpx !important;
 			}
 		}
 	}
+}
 
-	.c_popup_box {
+.c_popup_box {
 	background-color: #fff;
 	margin-bottom: 0rpx;
 
@@ -588,7 +599,7 @@
 			flex: 1;
 			padding: 20rpx;
 			position: relative;
-			
+
 			.textarea {
 				width: 100%;
 				min-height: 80rpx;
@@ -599,7 +610,7 @@
 				border-radius: 8rpx;
 				padding: 16rpx 50rpx 16rpx 16rpx;
 			}
-			
+
 			.emoji-trigger {
 				position: absolute;
 				right: 30rpx;
@@ -611,7 +622,7 @@
 				justify-content: center;
 				color: #666;
 				font-size: 40rpx;
-				
+
 				&:active {
 					opacity: 0.7;
 				}
@@ -631,16 +642,18 @@
 			margin-left: 5rpx;
 		}
 	}
-	
+
 	.emoji-panel {
 		padding: 20rpx;
 		background-color: #f8f8f8;
 		border-top: 2rpx solid #eee;
 		max-height: 40vh;
 		overflow-y: scroll;
+
 		.emoji-grid {
 			display: flex;
 			flex-wrap: wrap;
+
 			// overflow-y: scroll;
 			.emoji-item {
 				width: 12.5%;
@@ -649,7 +662,7 @@
 				align-items: center;
 				justify-content: center;
 				font-size: 40rpx;
-				
+
 				&:active {
 					background-color: rgba(0, 0, 0, 0.1);
 					border-radius: 8rpx;

+ 7 - 0
pages.json

@@ -22,6 +22,13 @@
 				"navigationStyle": "custom",
 				"navigationBarBackgroundColor": "#ffffff"
 			}
+		}, {
+			"path": "pages/index/Search",
+			"onReachBottomDistance":"50",
+			"style": {
+				"navigationBarTitleText": "搜索",
+				"navigationStyle": "custom"
+			}
 		}, {
 			"path": "pages/make/fabuArticle",
 			"style": {

+ 838 - 0
pages/index/Search.vue

@@ -0,0 +1,838 @@
+<template>
+  <view class="search-container">
+    <!-- 搜索框 -->
+    <view class="search-header">
+      <view class="cancel-btn" @click="goBack">
+        <uni-icons type="left" size="22" color="#1F1F1F"></uni-icons>
+      </view>
+      <view class="search-box">
+        <!-- <uni-icons type="search" size="16" color="#999"></uni-icons> -->
+        <input type="text" v-model="searchKeyword" placeholder="请输入关键词" confirm-type="search" @confirm="handleSearch"
+          @input="handleInput" />
+        <uni-icons v-if="searchKeyword" type="clear" size="16" color="#999" @click="clearKeyword"></uni-icons>
+        <view class="searchImgBox" @click="handleSearch">
+          <image class="image" src="@/static/home/sy_icon_sousuo.png"></image>
+        </view>
+      </view>
+    </view>
+    <view class="reserveASeat"></view>
+    <!-- 搜索历史 -->
+    <view class="search-history" v-if="searchStatus && historyList.length > 0">
+      <view class="history-header">
+        <text class="title">搜索历史</text>
+        <uni-icons type="trash" size="17" color="#999" @click="clearHistory"></uni-icons>
+      </view>
+      <view class="history-list">
+        <view class="history-item" v-for="(item, index) in displayedHistoryList" :key="index"
+          @click="useHistoryKeyword(item)">
+          <view>
+            <uni-icons type="clock" size="14" color="#999"></uni-icons>
+            <view class="history-text">
+              <image src="@/static/home/sy_icon_lishijilu.png"></image> {{ item }}
+            </view>
+          </view>
+          <image class="deleteBtn" @click.stop="deleteHistoryItem(item)" src="@/static/icon/close.png"></image>
+        </view>
+        <view class="expandBtn" @click="toggleHistory">
+          <view v-if="!isExpanded">
+            查看全部
+            <image src="@/static/home/sy_icon_chakanquanbu.png"></image>
+          </view>
+          <view v-else class="fold">
+            折叠历史记录
+            <image src="@/static/home/sy_icon_chakanquanbu.png"></image>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 搜索结果 -->
+    <view class="search-result" v-if="searchKeyword && !searchStatus">
+      <!-- <view class="search-result"> -->
+      <!-- 这里添加搜索结果的展示逻辑 -->
+      <view>
+        <view class="tab-nav">
+          <view v-for="(tab, index) in ['作品', '作者']" :key="index"
+            :class="['tab-item', currentTab === index ? 'active' : '']" @click="switchTab(index)">
+            {{ tab }}
+            <view class="indicator-triangle">
+            </view>
+          </view>
+        </view>
+
+        <!-- 关注列表 - 类似my.vue -->
+        <view v-show="currentTab === 0" class="follow-list">
+          <block v-if="followList.length > 0">
+            <w-waterfall :data="followList">
+              <template v-slot:content="{ item, width }">
+                <card :item="formatItem(item)" findType="search" :width="width" :custom-style="{ background: '#fff' }" textColor="#000">
+                </card>
+              </template>
+            </w-waterfall>
+          </block>
+          <view class="no-data" v-else-if="!isLoadingFollow">
+            <text>暂无作品数据</text>
+          </view>
+        </view>
+
+        <view v-show="currentTab === 1" class="follow-list">
+          <block v-if="recommendList.length > 0">
+            <view class="follow-item" v-for="(item, index) in recommendList" :key="index">
+              <CircleAvatar class="avator" :src="item.avator"></CircleAvatar>
+              <view class="info">
+                <view class="top-box">
+                  <view class="name one-omit">{{ item.nickname }}</view>
+                  <image src="../../static/icon/wd_icon_nan.png" mode="widthFix" v-if="item.sex_id == 1" />
+                  <image src="../../static/icon/wd_icon_nv.png" mode="widthFix" v-else-if="item.sex_id == 2" />
+                  <view class="level">Lv{{ item.level }}</view>
+                </view>
+                <!-- <view class="desc">{{ item.description }}</view> -->
+              </view>
+              <view :class="item.is_attention ? 'unfollow-btn active' : 'unfollow-btn'" @click="toggleFollow(item)">
+                <image src="../../static/me/wd_icon_guanzhu.png"></image>
+                {{ item.is_attention == 0 ? '取消关注' : '关注' }}
+              </view>
+            </view>
+          </block>
+          <view class="no-data" v-else-if="!isLoadingFollow">
+            <text>暂无作者数据</text>
+          </view>
+        </view>
+        <!-- avator: "http://e.yujianmate.com/images/avator/b1.jpg"
+      is_attention: 0
+      level: 0
+      nickname: "刘畅"
+      sex_id: 2 -->
+
+      </view>
+    </view>
+    <DialogBox ref="DialogBox"></DialogBox>
+  </view>
+</template>
+
+<script>
+const HISTORY_KEY = 'search_history';
+const MAX_HISTORY = 20;
+
+export default {
+  data() {
+    return {
+      searchKeyword: '', // 搜索关键词
+      historyList: [], // 搜索历史
+      searchResult: [], // 搜索结果
+      primaryHistoryList: [],
+      isExpanded: false, // 添加展开/折叠状态变量
+      currentTab: 0, // 当前激活的标签页
+      followList: [], // 关注列表数据
+      followOffset: 0, // 关注列表偏移量
+      hasMoreFollow: true, // 是否有更多关注列表数据
+      recommendList: [], // 推荐列表数据
+      recommendOffset: 0, // 推荐列表偏移量
+      hasMoreRecommend: true, // 是否有更多推荐列表数据
+      isLoadingFollow: false, // 是否正在加载关注列表
+      isLoadingRecommend: false, // 是否正在加载推荐列表 
+
+      searchStatus: true,//显示搜索历史  false显示搜索结果
+    }
+  },
+  onLoad() {
+    // 加载历史记录
+    this.loadHistory();
+  },
+  onReachBottom() {
+    // 触底加载更多数据
+    if (!(hasMoreFollow || hasMoreRecommend)) {
+      switch (this.currentTab) {
+        case 0:
+          this.loadFollowList();
+          break;
+        case 1:
+          this.loadRecommendList();
+          break;
+      }
+    } else {
+      uni.hideToast();
+      uni.showToast({
+        title:'没有更多数据了',
+        icon: 'none'
+      });
+      return;
+    }
+  },
+  computed: {
+    // 添加计算属性以控制显示的历史记录数量
+    displayedHistoryList() {
+      return this.isExpanded ? this.historyList : this.historyList.slice(0, 5);
+    }
+  },
+  methods: {
+    // 加载历史记录
+    loadHistory() {
+      try {
+        const history = uni.getStorageSync(HISTORY_KEY);
+        this.historyList = history ? JSON.parse(history) : [];
+      } catch (e) {
+        console.error('Failed to load search history:', e);
+        this.historyList = [];
+      }
+      // this.primaryHistoryList = JSON.parse(JSON.stringify(this.historyList));
+    },
+
+    // 保存历史记录
+    saveHistory() {
+      try {
+        // 将当前搜索词添加到历史记录开头
+        if (this.searchKeyword && !this.historyList.includes(this.searchKeyword)) {
+          console.log(this.historyList, 11111);
+          this.historyList.unshift(this.searchKeyword);
+          console.log(this.historyList, 11111);
+
+          // 限制历史记录数量
+          if (this.historyList.length > MAX_HISTORY) {
+            this.historyList = this.historyList.slice(0, MAX_HISTORY);
+          }
+          uni.setStorageSync(HISTORY_KEY, JSON.stringify(this.historyList));
+        }
+      } catch (e) {
+        console.error('Failed to save search history:', e);
+      }
+    },
+
+    // 清空历史记录
+    clearHistory() {
+      uni.showModal({
+        title: '提示',
+        content: '确定要清空搜索历史吗?',
+        success: (res) => {
+          if (res.confirm) {
+            this.historyList = [];
+            uni.setStorageSync(HISTORY_KEY, '[]');
+          }
+        }
+      });
+    },
+    // 清空单个历史记录
+    deleteHistoryItem(item) {
+      console.log(item, '删除历史记录');
+
+      this.$refs['DialogBox'].confirm({
+        title: '提示',
+        content: '确定要删除搜索历史吗?',
+        DialogType: 'inquiry',
+        btn1: '否',
+        btn2: '是',
+        animation: 0
+      }).then((res) => {
+        if (res.isConfirm) {
+          const index = this.historyList.indexOf(item);
+          if (index !== -1) {
+            this.historyList.splice(index, 1);
+          }
+          uni.setStorageSync(HISTORY_KEY, JSON.stringify(this.historyList));
+        }
+      })
+
+
+    },
+
+    // 使用历史记录中的关键词
+    useHistoryKeyword(keyword) {
+      this.searchKeyword = keyword;
+      this.handleSearch();
+    },
+
+    // 处理搜索
+    handleSearch() {
+      if (!this.searchKeyword.trim()) return;
+      // 保存到历史记录
+      this.saveHistory();
+      this.searchStatus = false;
+      // this.loadHistory()
+      // TODO: 实现搜索逻辑
+      console.log('Searching for:', this.searchKeyword);
+      this.queryList()
+    },
+
+    // 清空搜索框
+    clearKeyword() {
+      this.searchKeyword = '';
+    },
+
+    // 处理输入
+    handleInput(e) {
+      this.searchKeyword = e.detail.value;
+      // this.historyList = this.primaryHistoryList.filter(item => {
+      //   if (item.includes(e.detail.value)) {
+      //     return item;
+      //   }
+      // })
+      if (this.searchKeyword.trim() == '') {
+        this.searchStatus = true;
+      }
+      console.log(this.historyList, e.detail.value);
+
+    },
+
+    // 返回上一页
+    goBack() {
+      uni.navigateBack();
+    },
+
+    // 添加展开/折叠方法
+    toggleHistory() {
+      this.isExpanded = !this.isExpanded;
+    },
+    switchTab(index) {
+      this.currentTab = index;
+      this.searchRequest(index);
+      this.queryList()
+    },
+    formatItem(item) {
+
+      this.downloadAndProcessImage(item.images)
+        .then((color) => {
+          console.log(`平均颜色: R=${color.r}, G=${color.g}, B=${color.b}`);
+        })
+        .catch((error) => {
+          console.error("获取图像失败:", error);
+        });
+      // 处理接口返回的数据,使其适配card组件
+      return {
+        id: item.id,
+        allowEdit: false,
+        nickname: item.nickname,
+        avator: item.avator,
+        num_like: item.num_like,
+        num_view: item.num_view,
+        image: item.images || item.img_url || item.image, // 优先使用images字段
+        w: item.width,
+        h: item.height,
+        title: item.title || "",
+        desc: item.desc || "",
+        backgroundColor: "#f6f6f6",
+      };
+    },
+    downloadAndProcessImage(imageUrl, width = 10, height = 10) {
+      return new Promise((resolve, reject) => {
+        uni.downloadFile({
+          url: imageUrl,
+          success: (downloadResult) => {
+            if (downloadResult.statusCode === 200) {
+              const tempFilePath = downloadResult.tempFilePath;
+              const ctx = uni.createCanvasContext('myCanvas', this);
+              ctx.drawImage(tempFilePath, 0, 0, width, height);
+              ctx.draw(false, () => {
+                uni.canvasGetImageData({
+                  canvasId: 'myCanvas',
+                  x: 0,
+                  y: 0,
+                  width: width,
+                  height: height,
+                  success: (res) => {
+                    const data = res.data;
+                    let r = 0,
+                      g = 0,
+                      b = 0;
+                    for (let i = 0; i < data.length; i +=
+                      4) {
+                      r += data[i];
+                      g += data[i + 1];
+                      b += data[i + 2];
+                    }
+                    const count = width * height;
+                    r = Math.floor(r / count);
+                    g = Math.floor(g / count);
+                    b = Math.floor(b / count);
+                    resolve({
+                      r,
+                      g,
+                      b
+                    });
+                  },
+                  fail: (err) => {
+                    reject(err);
+                  }
+                });
+              });
+            } else {
+              reject(new Error('下载图像失败'));
+            }
+          },
+          fail: (err) => {
+            reject(err);
+          }
+        });
+      });
+    },
+    queryList() {
+      // 根据当前标签刷新数据
+      switch (this.currentTab) {
+        case 0:
+          // 重置作品列表
+          this.followList = [];
+          this.followOffset = 0;
+          this.hasMoreFollow = true;
+          // this.loadFollowList();
+          this.loadFollowList();
+          break;
+        case 1:
+          // 重置作者列表
+          this.recommendList = [];
+          this.recommendOffset = 0;
+          this.hasMoreRecommend = true;
+          // this.loadRecommendList();
+          this.loadRecommendList();
+          break;
+      }
+    },
+    loadFollowList() {
+      if (this.isLoadingFollow) return;
+      this.isLoadingFollow = true;
+
+      uni.request({
+        url: this.$apiHost + "/Work/getlist",
+        data: {
+          uuid: getApp().globalData.uuid,
+          skey: getApp().globalData.skey,
+          offset: this.followOffset,
+          search: this.searchKeyword,
+        },
+        header: {
+          "content-type": "application/json",
+          sign: getApp().globalData.headerSign,
+        },
+        success: (res) => {
+          console.log("作品列表数据:", res.data);
+          // 确保在任何情况下都完成加载
+          if (
+            res.data.success == "yes" &&
+            res.data.list &&
+            res.data.list.length > 0
+          ) {
+            // 只有当列表有数据时才追加
+            this.followList = [...this.followList, ...res.data.list];
+            this.followOffset += res.data.list.length;
+            console.log(this.followOffset, "作品列表数据");
+
+            if (res.data.list.length < 20) {
+              this.hasMoreFollow = false;
+            }
+          } else {
+            // 如果列表为空,确保标记没有更多数据
+            this.hasMoreFollow = false;
+          }
+
+          // 无论是否有数据,都需要通知z-paging组件完成刷新
+          if (this.$refs.paging) {
+            this.$refs.paging.complete(this.followList);
+          }
+        },
+        complete: () => {
+          this.isLoadingFollow = false;
+        },
+        fail: (e) => {
+          console.log("请求关注列表失败:", e);
+          this.isLoadingFollow = false;
+          // 加载失败时也要通知组件完成
+          if (this.$refs.paging) {
+            this.$refs.paging.complete(false);
+          }
+        },
+      });
+    },
+    loadRecommendList() {
+      console.log(this.recommendOffset, 666);
+
+      if (this.isLoadingRecommend) return;
+      this.isLoadingRecommend = true;
+      uni.request({
+        url: this.$apiHost + "/User/getlist",
+        data: {
+          uuid: getApp().globalData.uuid,
+          skey: getApp().globalData.skey,
+          offset: this.recommendOffset,
+          search: this.searchKeyword,
+        },
+        header: {
+          "content-type": "application/json",
+          sign: getApp().globalData.headerSign,
+        },
+        success: (res) => {
+          console.log("作者列表数据:", res.data);
+          if (
+            res.data.success == "yes" &&
+            res.data.list &&
+            res.data.list.length > 0
+          ) {
+            this.recommendList = [...this.recommendList, ...res.data.list];
+            this.recommendOffset += res.data.list.length;
+            console.log(this.recommendOffset, "作者列表数据");
+
+            if (res.data.list.length < 20) {
+              this.hasMoreRecommend = false;
+            }
+          } else {
+            this.hasMoreRecommend = false;
+          }
+
+          // 无论是否有数据,都需要通知z-paging组件完成刷新
+          if (this.$refs.paging) {
+            this.$refs.paging.complete(this.recommendList);
+          }
+        },
+        complete: () => {
+          this.isLoadingRecommend = false;
+        },
+        fail: (e) => {
+          console.log("请求推荐列表失败:", e);
+          this.isLoadingRecommend = false;
+          // 加载失败时也要通知组件完成
+          if (this.$refs.paging) {
+            this.$refs.paging.complete(false);
+          }
+        },
+      });
+    },
+    toggleFollow(item) {
+      uni.request({
+        url: this.$apiHost + '/Member/attention',
+        data: {
+          uuid: getApp().globalData.uuid,
+          id: item.user_id,
+        },
+        header: {
+          "content-type": "application/json",
+          'sign': getApp().globalData.headerSign
+        },
+        success: (res) => {
+          console.log("关注结果:", res.data);
+          uni.showToast({
+            title: res.data.str,
+            icon: 'none'
+          });
+          if (res.data.success === "yes") {
+            if (res.data.str === "关注成功") {
+              item.is_attention = false
+            }
+            if (res.data.str === "取消关注") {
+              item.is_attention = true
+            }
+          }
+        },
+        fail: (e) => {
+          console.log("关注失败:", e);
+          uni.showToast({
+            title: '网络请求失败',
+            icon: 'none'
+          });
+        }
+      });
+      // 这里可以添加调用后端API的逻辑
+    },
+    // 搜索请求
+    searchRequest() {
+    },
+  }
+}
+</script>
+
+<style lang="scss">
+.search-container {
+  background-color: #ffffff;
+  min-height: 100vh;
+
+  .search-header {
+    background-color: #ffffff;
+    padding: 16rpx 30rpx;
+    display: flex;
+    align-items: center;
+    padding-top: calc(16rpx + var(--status-bar-height));
+    padding-left: 40rpx;
+    padding-right: 28rpx;
+    padding-bottom: 16rpx;
+    position: fixed;
+    left: 0;
+    top: 0;
+    z-index: 9;
+
+    .search-box {
+      flex: 1;
+      height: 72rpx;
+      background-color: #f1f1f1;
+      border-radius: 36rpx;
+      display: flex;
+      align-items: center;
+      padding: 0 24rpx;
+      width: 622rpx;
+      height: 72rpx;
+      background: #FFFFFF;
+      border-radius: 36rpx;
+      border: 4rpx solid #000000;
+      position: relative;
+      left: 0;
+      top: 0;
+
+      input {
+        flex: 1;
+        height: 100%;
+        margin: 0 16rpx;
+        font-size: 24rpx;
+        font-family: 'PingFang SC-Medium';
+      }
+
+      .searchImgBox {
+        width: 88rpx;
+        height: 56rpx;
+        background: #1F1F1F;
+        border-radius: 32rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        position: absolute;
+        top: 4rpx;
+        right: 4rpx;
+
+        .image {
+          width: 36rpx;
+          height: 36rpx;
+        }
+      }
+    }
+
+    .cancel-btn {
+      color: #1f1f1f;
+      width: 36rpx;
+      height: 100%;
+      margin-right: 24rpx;
+      font-weight: 700;
+    }
+  }
+
+  .reserveASeat {
+    width: 100vh;
+    height: calc(108rpx + var(--status-bar-height));
+  }
+
+  .search-history {
+    background-color: #ffffff;
+    margin-top: 20rpx;
+    padding: 30rpx;
+
+    .history-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 15rpx;
+
+      .title {
+        font-size: 30rpx;
+        color: #333;
+        font-weight: bold;
+      }
+    }
+
+    .history-list {
+      transition: all 1s;
+      overflow: hidden;
+
+      .history-item {
+        display: flex;
+        align-items: center;
+        padding: 13rpx 0;
+        justify-content: space-between;
+
+        .history-text {
+          font-size: 28rpx;
+          color: #666;
+          display: flex;
+          align-items: center;
+          justify-content: flex-start;
+          font-weight: 400;
+          color: #1F1F1F;
+          margin-left: 6rpx;
+          font-family: 'PingFang SC-Bold';
+
+          image {
+            width: 32rpx;
+            height: 32rpx;
+            margin-right: 16rpx;
+          }
+        }
+
+        .deleteBtn {
+          width: 30rpx;
+          height: 30rpx;
+        }
+      }
+    }
+
+    .expandBtn {
+      view {
+        font-family: 'PingFang SC-Bold';
+        color: #999999;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        &.fold {
+          image {
+            transform: rotate(180deg) translateY(-2rpx);
+          }
+        }
+
+        image {
+          width: 28rpx;
+          height: 28rpx;
+        }
+      }
+    }
+  }
+
+  .search-result {
+    background-color: #ffffff;
+    margin-top: 20rpx;
+    min-height: 200rpx;
+
+    .tab-nav {
+      display: flex;
+      justify-content: flex-start;
+      padding: 20rpx 20rpx;
+      box-sizing: border-box;
+      background: #ffffff;
+
+      .tab-item {
+        padding: 10rpx 38rpx;
+        color: #1F1F1F;
+        font-size: 28rpx;
+        background: #F2F6F2;
+        margin-right: 20rpx;
+        border-radius: 30rpx;
+        position: relative;
+        left: 0;
+        top: 0;
+
+        .indicator-triangle {
+          position: absolute;
+          bottom: -10rpx;
+          left: 50%;
+          transform: translateX(-50%);
+          width: 0;
+          height: 0;
+          border-left: 10rpx solid transparent;
+          border-right: 10rpx solid transparent;
+          border-top: 10rpx solid #ACF934;
+          display: none;
+        }
+
+        &.active {
+          background: #ACF934;
+          font-family: "CustomFont" !important;
+
+          .indicator-triangle {
+            display: block;
+          }
+        }
+      }
+    }
+
+    .follow-list {
+      padding: 0 20px;
+
+      .follow-item {
+        display: flex;
+        align-items: center;
+        padding: 15px 0;
+
+        .avator {
+          width: 120rpx;
+          height: 120rpx;
+          margin-right: 24rpx;
+        }
+
+        .info {
+          flex: 1;
+
+          .top-box {
+            display: flex;
+            align-items: center;
+
+            .name {
+              font-size: 32rpx;
+              font-weight: 500;
+              margin-bottom: 8rpx;
+              max-width: 200rpx;
+            }
+
+            >image {
+              width: 36rpx;
+              margin-left: 8rpx;
+              margin-right: 10rpx;
+            }
+
+            .level {
+              font-weight: 400;
+              font-size: 20rpx;
+              font-family: "PingFang SC-Bold";
+              background: linear-gradient(360deg, #acf934 0%, #ffe439 100%);
+              border-radius: 8rpx;
+              padding: 2rpx 8rpx;
+              display: inline-block;
+            }
+          }
+
+          .desc {
+            font-size: 24rpx;
+            color: #999;
+          }
+        }
+
+        .unfollow-btn {
+          font-size: 24rpx;
+          width: 144rpx;
+          height: 52rpx;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          color: #666;
+          background: #fff;
+          border: 2rpx solid #000;
+          border-radius: 112rpx;
+          margin: 0;
+          font-family: 'PingFang SC-Bold';
+
+          image {
+            display: none;
+            width: 16rpx;
+            height: 16rpx;
+            margin-right: 5rpx;
+          }
+
+          &.active {
+            color: #ACF934;
+            background: #000;
+
+            image {
+              display: inline-block;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .no-data {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 60rpx 0;
+    background-color: #fff;
+
+    text {
+      color: #808080;
+      font-size: 28rpx;
+    }
+  }
+
+}
+</style>

+ 619 - 614
pages/index/articleDetail.vue

@@ -1,179 +1,135 @@
 <template>
-  <view class="page">
-    <!-- 引入FontAwesome -->
-    <view>
-      <link
-        rel="stylesheet"
-        href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
-      />
-    </view>
+	<view class="page">
+		<!-- 引入FontAwesome -->
+		<view>
+			<link rel="stylesheet"
+				href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
+		</view>
 
-    <!-- 顶部导航栏 -->
-    <view class="custom-navbar">
-      <view class="navbar-left" @click="goBack">
-        <text class="fa fa-angle-left"></text>
-      </view>
-      <view class="navbar-center">
-        <view class="navbar-title">
-          <image
-            class="navbar-avatar"
-            :src="author.avator"
-            mode="aspectFill"
-          ></image>
-          <text class="navbar-text">{{ author.nickname }}</text>
-          <!-- <text class="navbar-badge" v-if="author.is_vip > 0">VIP</text> -->
-        </view>
-      </view>
-      <view class="navbar-right">
-        <text
-          class="followTheAuthor followTheAuthor1"
-          v-if="author.is_attention == 0"
-          @click="followTheAuthor(1)"
-          >+关注</text
-        >
-        <text
-          class="followTheAuthor followTheAuthor0"
-          v-if="author.is_attention == 1"
-          @click="followTheAuthor(0)"
-          >已关注</text
-        >
-        <image
-          v-if="false"
-          @click="showActionSheet(0)"
-          src="@/static/icon/sy_icon_fenxiang.png"
-          mode="widthFix"
-        ></image>
-        <view v-else class="navbar-right" @click="showActionSheet(1)">
-          <text class="fa fa-ellipsis-h"></text>
-        </view>
-      </view>
-    </view>
-    <view class="topStatusBar inProgress" v-if="0"> 审核中</view>
-    <view class="topStatusBar fail" v-if="0" @click="openConfirmationBox">
-      审核未通过,点击查看原因</view
-    >
+		<!-- 顶部导航栏 -->
+		<view class="custom-navbar">
+			<view class="navbar-left" @click="goBack">
+				<text class="fa fa-angle-left"></text>
+			</view>
+			<view class="navbar-center">
+				<view class="navbar-title">
+					<image class="navbar-avatar" :src="author.avator" mode="aspectFill"></image>
+					<text class="navbar-text">{{ author.nickname }}</text>
+					<!-- <text class="navbar-badge" v-if="author.is_vip > 0">VIP</text> -->
+				</view>
+			</view>
+			<view class="navbar-right">
+				<template v-if="!isItMe">
+					<text class="followTheAuthor followTheAuthor1" v-if="author.is_attention == 0"
+						@click="followTheAuthor(1)">+关注</text>
+					<text class="followTheAuthor followTheAuthor0" v-if="author.is_attention == 1"
+						@click="followTheAuthor(0)">已关注</text>
+				</template>
 
-    <!--  轮播图部分 -->
-    <uv-swiper
-      v-if="articleInfo.type == 'user'"
-      class="swiper-box"
-      height="1032rpx"
-      :list="swperImages"
-      indicator
-      indicatorMode="dot"
-      bgColor="#fff"
-      :autoplay="false"
-    >
-    </uv-swiper>
+				<image v-if="false" @click="showActionSheet(0)" src="@/static/icon/sy_icon_fenxiang.png"
+					mode="widthFix"></image>
+				<view v-else class="navbar-right" @click="showActionSheet(1)">
+					<text class="fa fa-ellipsis-h"></text>
+				</view>
+			</view>
+		</view>
+		<view class="topStatusBar inProgress" v-if="articleInfo.status == 2"> 审核中</view>
+		<view class="topStatusBar fail" v-else-if="articleInfo.status == 3" @click="openConfirmationBox">
+			审核未通过,点击查看原因</view>
 
-    <!-- 文章内容区域 -->
-    <view class="body">
-      <!-- 文章标题与元信息 -->
-      <view class="article-header">
-        <view class="title">
-          {{ articleInfo.title || "暂无标题" }}
-        </view>
-        <view class="article-content">
-          <view class="content">
-            <rich-text :nodes="content" style="font-size: 14px"></rich-text>
-          </view>
-        </view>
-        <view class="cardLink" @click="goToDetails(workInfo.id)">
-          <image :src="workInfo.images" mode="heightFix"></image>
-          <view class="content">
-            <view class="tit">作品一号</view>
-            <view class="tex">{{
-              cutTime(workInfo.create_time) || "暂无时间"
-            }}</view>
-          </view>
-        </view>
-        <view v-if="false" class="meta-info">
-          <view class="meta-item">
-            <text class="fa fa-calendar"></text>
-            <text class="meta-text">{{
-              articleInfo.create_time || "暂无时间"
-            }}</text>
-          </view>
-          <view class="meta-item">
-            <text class="fa fa-eye"></text>
-            <text class="meta-text">{{ articleInfo.num_view || 0 }}次阅读</text>
-          </view>
-          <view class="meta-item" v-if="articleInfo.author">
-            <text class="fa fa-user"></text>
-            <text class="meta-text">{{ articleInfo.author }}</text>
-          </view>
-        </view>
-      </view>
+		<!--  轮播图部分 -->
+		<uv-swiper v-if="articleInfo.type == 'user'" class="swiper-box" height="1032rpx" :list="swperImages" indicator
+			indicatorMode="dot" bgColor="#fff" :autoplay="false">
+		</uv-swiper>
 
-      <!-- 文章底部区域 -->
-      <view class="article-footer" v-if="false">
-        <view class="action-bar">
-          <view class="action-item" @tap="likeArticle">
-            <text
-              class="fa"
-              :class="
-                articleInfo.is_like ? 'fa-thumbs-up liked' : 'fa-thumbs-o-up'
-              "
-            ></text>
-            <text class="action-text">{{ articleInfo.like_count || 0 }}</text>
-          </view>
-          <view class="action-item" @tap="openComment">
-            <text class="fa fa-comment-o"></text>
-            <text class="action-text">{{ tableTotal || 0 }}</text>
-          </view>
-          <view class="action-item" @tap="shareArticle">
-            <text class="fa fa-share-alt"></text>
-            <text class="action-text">分享</text>
-          </view>
-        </view>
-      </view>
-    </view>
+		<!-- 文章内容区域 -->
+		<view class="body">
+			<!-- 文章标题与元信息 -->
+			<view class="article-header">
+				<view class="title">
+					{{ articleInfo.title || "暂无标题" }}
+				</view>
+				<view class="article-content">
+					<view class="content">
+						<rich-text :nodes="content" style="font-size: 14px"></rich-text>
+					</view>
+				</view>
+				<view class="cardLink" @click="goToDetails(workInfo.id)">
+					<image :src="workInfo.images" mode="heightFix"></image>
+					<view class="content">
+						<view class="tit">作品一号</view>
+						<view class="tex">{{
+							cutTime(workInfo.create_time) || "暂无时间"
+						}}</view>
+					</view>
+				</view>
+				<view v-if="false" class="meta-info">
+					<view class="meta-item">
+						<text class="fa fa-calendar"></text>
+						<text class="meta-text">{{
+							articleInfo.create_time || "暂无时间"
+						}}</text>
+					</view>
+					<view class="meta-item">
+						<text class="fa fa-eye"></text>
+						<text class="meta-text">{{ articleInfo.num_view || 0 }}次阅读</text>
+					</view>
+					<view class="meta-item" v-if="articleInfo.author">
+						<text class="fa fa-user"></text>
+						<text class="meta-text">{{ articleInfo.author }}</text>
+					</view>
+				</view>
+			</view>
 
-    <!-- <view class="btn_submit" @click="chatTA()">
+			<!-- 文章底部区域 -->
+			<view class="article-footer" v-if="false">
+				<view class="action-bar">
+					<view class="action-item" @tap="likeArticle">
+						<text class="fa" :class="articleInfo.is_like ? 'fa-thumbs-up liked' : 'fa-thumbs-o-up'
+							"></text>
+						<text class="action-text">{{ articleInfo.like_count || 0 }}</text>
+					</view>
+					<view class="action-item" @tap="openComment">
+						<text class="fa fa-comment-o"></text>
+						<text class="action-text">{{ tableTotal || 0 }}</text>
+					</view>
+					<view class="action-item" @tap="shareArticle">
+						<text class="fa fa-share-alt"></text>
+						<text class="action-text">分享</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- <view class="btn_submit" @click="chatTA()">
 			<image class="icon" src="../../static/icon/icon_chat_white.png" mode="widthFix"></image>
 			私聊
 		</view> -->
 
-    <previewImage
-      ref="previewImage"
-      :opacity="0.8"
-      :circular="true"
-      :imgs="imgs"
-      :descs="descs"
-    ></previewImage>
+		<previewImage ref="previewImage" :opacity="0.8" :circular="true" :imgs="imgs" :descs="descs"></previewImage>
 
-    <!-- 评论区域 -->
-    <CommentSection
-      v-if="userInfo.id != 0"
-      ref="commentSection"
-      @totalNumberOfComments="totalNumberOfComments"
-      :articleInfo="articleInfo"
-      :myInfo="myInfo"
-      :userInfo="userInfo"
-      :articleId="arcID"
-      :type="'article'"
-    >
-    </CommentSection>
+		<!-- 评论区域 -->
+		<template v-if="articleInfo.status == 1">
+			<CommentSection v-if="userInfo.id != 0" ref="commentSection" @totalNumberOfComments="totalNumberOfComments"
+				:articleInfo="articleInfo" :myInfo="myInfo" :userInfo="userInfo" :articleId="arcID" :type="'article'">
+			</CommentSection>
 
-    <view class="thread2"></view>
-    <!-- 自定义 ActionSheet -->
-    <ActionSheet
-      ref="actionSheet"
-      :items="items"
-      @select="handleActionSelect"
-      @cancel="handleActionCancel"
-    />
-    <CustomPopup ref="confirmationBox">
-      <view class="failureReason">
-        <view class="title"> 审核未通关</view>
-        <view class="reviewContent">
-          <!-- {{ fileInformation.reason }} -->
-          不通过原因
-        </view>
-        <view class="btn-box" @click="closeConfirmationBox">知道了</view>
-      </view>
-    </CustomPopup>
-  </view>
+		</template>
+		<view class="thread2"></view>
+		<!-- 自定义 ActionSheet -->
+		<ActionSheet ref="actionSheet" :items="items" @select="handleActionSelect" @cancel="handleActionCancel" />
+		<CustomPopup ref="confirmationBox">
+			<view class="failureReason">
+				<view class="title"> 审核未通关</view>
+				<view class="reviewContent">
+					<!-- {{ fileInformation.reason }} -->
+					<uv-parse :content="articleInfo.reason"></uv-parse>
+				</view>
+				<view class="btn-box" @click="closeConfirmationBox">知道了</view>
+			</view>
+		</CustomPopup>
+		<DialogBox ref="DialogBox"></DialogBox>
+	</view>
 </template>
 
 <script>
@@ -182,467 +138,516 @@ import CommentSection from "@/components/CommentSection/CommentSection.vue";
 import previewImage from "@/components/kxj-previewImage/kxj-previewImage.vue"; //引用插件
 import CustomPopup from "@/components/CustomPopup/CustomPopup.vue";
 function parseImgs(nodes) {
-  nodes.forEach((node) => {
-    if (node.name === "img" && node.attrs && node.attrs["data-img-size-val"]) {
-      const sizes = node.attrs["data-img-size-val"].split(",");
-      const width = uni.upx2px(720 * 0.9);
-      const height = parseInt(width * (sizes[1] / sizes[0]));
-      node.attrs.style = `width:${width};height:${height};`;
-    }
-    if (Array.isArray(node.children)) {
-      parseImgs(node.children);
-    }
-  });
-  return nodes;
+	nodes.forEach((node) => {
+		if (node.name === "img" && node.attrs && node.attrs["data-img-size-val"]) {
+			const sizes = node.attrs["data-img-size-val"].split(",");
+			const width = uni.upx2px(720 * 0.9);
+			const height = parseInt(width * (sizes[1] / sizes[0]));
+			node.attrs.style = `width:${width};height:${height};`;
+		}
+		if (Array.isArray(node.children)) {
+			parseImgs(node.children);
+		}
+	});
+	return nodes;
 }
 export default {
-  components: {
-    previewImage,
-    CommentSection,
-    CustomPopup,
-  },
-  data() {
-    return {
-      swperImages: [],
-      items: [],
-      title: "",
-      arcID: 0,
-      selImg: 0,
-      home_image: "",
-      myinfo: {},
-      tag_list: [],
-      image_list: [],
-      imgs: [],
-      descs: [],
-      list_wish: [],
-      content: "",
-      tableTotal: 0,
-      author: {},
-      // 添加文章信息字段
-      articleInfo: {
-        title: "",
-        content: "",
-        create_time: "",
-        images: "",
-        view_count: 0,
-        author: "",
-        like_count: 0,
-        is_like: false,
-      },
-      workInfo: {},
-      myInfo: {
-        user_id: getApp().globalData.mobile, // 用户id
-        user_name: getApp().globalData.nickname, // 用户名
-        user_avatar: getApp().globalData.avator, // 用户头像地址
-      },
-      // 文章作者信息(提示: 一般来自localstorage, 如果是实时获取的话, 那么获取到数据后再v-if显示评论组件)
-      userInfo: {
-        user_id: 0, // 用户id
-        user_name: "", // 用户名
-        user_avatar: "", // 用户头像地址
-      },
-    };
-  },
-  onLoad(parms) {
-    let self = this;
-    this.arcID = parms.id || 396;
-  },
-  onShow() {
-    uni.$emit("check_update");
-    this.$nextTick(() => {
-      if (this.$refs.commentSection) {
-        this.$refs.commentSection.loadCommentData();
-      }
-    });
-    this.loadData();
-  },
-  methods: {
-    openConfirmationBox() {
-      this.$refs.confirmationBox.open();
-    },
-    closeConfirmationBox() {
-      this.$refs.confirmationBox.close();
-    },
-    // 返回上一页
-    goBack() {
-      uni.navigateBack({
-        delta: 1,
-      });
-    },
+	components: {
+		previewImage,
+		CommentSection,
+		CustomPopup,
+	},
+	data() {
+		return {
+			swperImages: [],
+			items: [],
+			title: "",
+			arcID: 0,
+			selImg: 0,
+			home_image: "",
+			myinfo: {},
+			tag_list: [],
+			image_list: [],
+			imgs: [],
+			descs: [],
+			list_wish: [],
+			content: "",
+			tableTotal: 0,
+			author: {},
+			// 添加文章信息字段
+			articleInfo: {
+				title: "",
+				content: "",
+				create_time: "",
+				images: "",
+				view_count: 0,
+				author: "",
+				like_count: 0,
+				is_like: false,
+			},
+			workInfo: {},
+			myInfo: {
+				user_id: getApp().globalData.user_id, // 用户id
+				user_name: getApp().globalData.nickname, // 用户名
+				user_avatar: getApp().globalData.avator, // 用户头像地址
+			},
+			// 文章作者信息(提示: 一般来自localstorage, 如果是实时获取的话, 那么获取到数据后再v-if显示评论组件)
+			userInfo: {
+				user_id: 0, // 用户id
+				user_name: "", // 用户名
+				user_avatar: "", // 用户头像地址
+			},
+			isItMe: false,
+		};
+	},
+	onLoad(parms) {
+		let self = this;
+		this.arcID = parms.id || 396;
+
+	},
+	onShow() {
+		uni.$emit("check_update");
+		this.$nextTick(() => {
+			if (this.$refs.commentSection) {
+				this.$refs.commentSection.loadCommentData();
+			}
+		});
+
+		this.loadData();
+	},
+	methods: {
+		openConfirmationBox() {
+			this.$refs.confirmationBox.open();
+		},
+		closeConfirmationBox() {
+			this.$refs.confirmationBox.close();
+		},
+		// 返回上一页
+		goBack() {
+			uni.navigateBack({
+				delta: 1,
+			});
+		},
 
-    // 文章点赞
-    likeArticle() {
-      uni.request({
-        url: this.$apiHost + "/Work/zanTA",
-        data: {
-          uuid: getApp().globalData.uuid,
-          id: this.arcID,
-        },
-        header: {
-          "content-type": "application/json",
-          sign: getApp().globalData.headerSign,
-        },
-        success: (res) => {
-          console.log("点赞结果:", res.data);
-          if (res.data.success === "yes") {
-            // 更新点赞状态
-            if (!this.articleInfo.is_like) {
-              this.articleInfo.like_count =
-                (this.articleInfo.like_count || 0) + 1;
-              this.articleInfo.is_like = true;
-              uni.showToast({
-                title: "点赞成功",
-                icon: "none",
-              });
-            } else {
-              this.articleInfo.like_count =
-                (this.articleInfo.like_count || 0) - 1;
-              this.articleInfo.is_like = false;
-              uni.showToast({
-                title: res.data.str,
-                icon: "none",
-              });
-            }
-          } else {
-            uni.showToast({
-              title: res.data.str,
-              icon: "none",
-            });
-          }
-        },
-        fail: (e) => {
-          console.log("点赞失败:", e);
-          uni.showToast({
-            title: "网络请求失败",
-            icon: "none",
-          });
-        },
-      });
-    },
-    // 关注作者
-    followTheAuthor(n) {
-      uni.request({
-        url: this.$apiHost + "/Member/attention",
-        data: {
-          uuid: getApp().globalData.uuid,
-          id: this.author.id,
-        },
-        header: {
-          "content-type": "application/json",
-          sign: getApp().globalData.headerSign,
-        },
-        success: (res) => {
-          console.log("点赞结果:", res.data);
-          uni.showToast({
-            title: res.data.str,
-            icon: "none",
-          });
-          if (res.data.success === "yes") {
-            console.log("关注结果:", res.data, n);
+		// 文章点赞
+		likeArticle() {
+			uni.request({
+				url: this.$apiHost + "/Work/zanTA",
+				data: {
+					uuid: getApp().globalData.uuid,
+					id: this.arcID,
+				},
+				header: {
+					"content-type": "application/json",
+					sign: getApp().globalData.headerSign,
+				},
+				success: (res) => {
+					console.log("点赞结果:", res.data);
+					if (res.data.success === "yes") {
+						// 更新点赞状态
+						if (!this.articleInfo.is_like) {
+							this.articleInfo.like_count =
+								(this.articleInfo.like_count || 0) + 1;
+							this.articleInfo.is_like = true;
+							uni.showToast({
+								title: "点赞成功",
+								icon: "none",
+							});
+						} else {
+							this.articleInfo.like_count =
+								(this.articleInfo.like_count || 0) - 1;
+							this.articleInfo.is_like = false;
+							uni.showToast({
+								title: res.data.str,
+								icon: "none",
+							});
+						}
+					} else {
+						uni.showToast({
+							title: res.data.str,
+							icon: "none",
+						});
+					}
+				},
+				fail: (e) => {
+					console.log("点赞失败:", e);
+					uni.showToast({
+						title: "网络请求失败",
+						icon: "none",
+					});
+				},
+			});
+		},
+		// 关注作者
+		followTheAuthor(n) {
+			uni.request({
+				url: this.$apiHost + "/Member/attention",
+				data: {
+					uuid: getApp().globalData.uuid,
+					id: this.author.id,
+				},
+				header: {
+					"content-type": "application/json",
+					sign: getApp().globalData.headerSign,
+				},
+				success: (res) => {
+					console.log("点赞结果:", res.data);
+					uni.showToast({
+						title: res.data.str,
+						icon: "none",
+					});
+					if (res.data.success === "yes") {
+						console.log("关注结果:", res.data, n);
 
-            this.author.is_attention = n;
-          }
-        },
-        fail: (e) => {
-          console.log("关注失败:", e);
-          uni.showToast({
-            title: "网络请求失败",
-            icon: "none",
-          });
-        },
-      });
-    },
-    totalNumberOfComments(tableTotal) {
-      this.tableTotal = tableTotal;
-    },
-    onLinqu(item) {
-      uni.navigateTo({
-        url: "/pages/my/wishHelp?id=" + item.myid,
-      });
-    },
-    selPhoto(item, sel) {
-      this.selImg = sel;
-      this.home_image = this.image_list[sel];
-    },
-    goToDetails(id) {
-      console.log("goToDetails", id);
-      uni.navigateTo({
-        url: "/pages/index/workDetail?id=" + id,
-      });
-      console.log("goToDetails", id);
-    },
-    toArr(imgs) {
-      let arr = imgs.split("|");
-      return arr;
-    },
-    previewOpen(imgs1, index) {
-      this.imgs = imgs1.split("|");
-      setTimeout(() => this.$refs.previewImage.open(index), 0);
-      return; //如需测试和uni原生预览差别可注释这两行
-    },
-    loadData() {
-      uni.request({
-        url: this.$apiHost + "/Article/getinfo",
-        data: {
-          uuid: getApp().globalData.uuid,
-          id: this.arcID,
-        },
-        header: {
-          "content-type": "application/json",
-          sign: getApp().globalData.headerSign,
-        },
-        success: (res) => {
-          console.log("文章信息:", res.data);
-          if (res.data.success === "yes") {
-            // 更新文章信息
-            this.articleInfo = res.data.article;
-            this.swperImages = this.imageString(res.data.article.images);
-            console.log("swperImages", this.swperImages);
-            this.userInfo.user_id = res.data.id; // 用户id
-            this.userInfo.user_name = res.data.nickname; // 用户名
-            this.userInfo.user_avatar = res.data.avator; // 用户头像地址
-            this.author = res.data.author;
-            this.workInfo = res.data.work_info;
-            const nodes = htmlParser(res.data.article.content);
-            // #ifdef APP-PLUS-NVUE
-            parseImgs(nodes);
-            // #endif
-            this.content = nodes;
+						this.author.is_attention = n;
+					}
+				},
+				fail: (e) => {
+					console.log("关注失败:", e);
+					uni.showToast({
+						title: "网络请求失败",
+						icon: "none",
+					});
+				},
+			});
+		},
+		totalNumberOfComments(tableTotal) {
+			this.tableTotal = tableTotal;
+		},
+		onLinqu(item) {
+			uni.navigateTo({
+				url: "/pages/my/wishHelp?id=" + item.myid,
+			});
+		},
+		selPhoto(item, sel) {
+			this.selImg = sel;
+			this.home_image = this.image_list[sel];
+		},
+		goToDetails(id) {
+			console.log("goToDetails", id);
+			uni.navigateTo({
+				url: "/pages/index/workDetail?id=" + id,
+			});
+			console.log("goToDetails", id);
+		},
+		toArr(imgs) {
+			let arr = imgs.split("|");
+			return arr;
+		},
+		previewOpen(imgs1, index) {
+			this.imgs = imgs1.split("|");
+			setTimeout(() => this.$refs.previewImage.open(index), 0);
+			return; //如需测试和uni原生预览差别可注释这两行
+		},
+		loadData() {
+			uni.request({
+				url: this.$apiHost + "/Article/getinfo",
+				data: {
+					uuid: getApp().globalData.uuid,
+					id: this.arcID,
+				},
+				header: {
+					"content-type": "application/json",
+					sign: getApp().globalData.headerSign,
+				},
+				success: (res) => {
+					console.log("文章信息:", res.data);
+					if (res.data.success === "yes") {
+						// 更新文章信息
+						this.articleInfo = res.data.article;
+						this.swperImages = this.imageString(res.data.article.images);
+						console.log("swperImages", this.swperImages);
+						this.userInfo.user_id = res.data.id; // 用户id
+						this.userInfo.user_name = res.data.nickname; // 用户名
+						this.userInfo.user_avatar = res.data.avator; // 用户头像地址
+						this.author = res.data.author;
+						this.workInfo = res.data.work_info;
+						this.isItMe = res.data.article.userID == getApp().globalData.user_id ? true : false;
 
-            // 更新图片列表
-            if (res.data.article.images && res.data.article.images !== "") {
-              this.image_list = res.data.article.images.split(",");
-              this.home_image = this.image_list[0];
-            } else {
-              this.home_image = "../../static/home/avator.png";
-            }
-            console.log("img", this.home_image);
+						const nodes = htmlParser(res.data.article.content);
+						// #ifdef APP-PLUS-NVUE
+						parseImgs(nodes);
+						// #endif
+						this.content = nodes;
 
-            // 更新标签列表
-            // if (res.data.data.tags && res.data.data.tags !== "") {
-            // 	this.tag_list = res.data.data.tags.split(",");
-            // }
-          } else {
-            uni.showToast({
-              title: "获取文章信息失败",
-              icon: "none",
-            });
-          }
-        },
-        complete: (com) => {
-          // uni.hideLoading();
-        },
-        fail: (e) => {
-          console.log("请求失败:", e);
-          uni.showToast({
-            title: "网络请求失败",
-            icon: "none",
-          });
-        },
-      });
-    },
-    // 评论相关方法已移至CommentSection组件
+						// 更新图片列表
+						if (res.data.article.images && res.data.article.images !== "") {
+							this.image_list = res.data.article.images.split(",");
+							this.home_image = this.image_list[0];
+						} else {
+							this.home_image = "../../static/home/avator.png";
+						}
+						console.log("img", this.home_image);
 
-    // 唤起新评论弹框
-    openComment() {
-      if (this.$refs.commentSection) {
-        this.$refs.commentSection.openComment();
-      }
-    },
-    // 文章点赞
-    // 分享文章
-    shareArticle() {
-      // 如果在微信小程序环境
-      if (uni.getSystemInfoSync().platform === "mp-weixin") {
-        uni.showShareMenu({
-          withShareTicket: true,
-          menus: ["shareAppMessage", "shareTimeline"],
-        });
-      } else {
-        // 其他环境,如APP
-        uni.share({
-          provider: "weixin",
-          scene: "WXSceneSession",
-          type: 0,
-          title: this.articleInfo.title,
-          summary: this.articleInfo.content.substring(0, 40) + "...",
-          imageUrl: this.home_image,
-          success: function (res) {
-            console.log("分享成功:" + JSON.stringify(res));
-          },
-          fail: function (err) {
-            console.log("分享失败:" + JSON.stringify(err));
-          },
-        });
-      }
-    },
-    // 评论回调事件
-    replyFun({ params }, callback) {
-      // params = {
-      // 	...params,
-      // 	user_id: this.myInfo.user_id, // 用户id
-      // 	user_name: this.myInfo.user_name, // 用户名
-      // 	user_avatar: this.myInfo.user_avatar, // 用户头像地址
-      // 	user_content: this.commentValue, // 用户评论内容
-      // 	is_like: false, // 是否点赞
-      // 	like_count: 0, // 点赞数统计
-      // 	create_time: "刚刚", // 创建时间
-      // 	owner: true, // 是否为所有者 所有者可以进行删除 管理员默认true
-      // };
-      console.log("replyFun", {
-        uuid: getApp().globalData.uuid,
-        article_id: this.arcID, // 文章ID
-        content: params.user_content, // 评论内容
-        parent_id: params.parent_id || 0, // 父评论ID
-        reply_id: params.reply_id || 0, // 回复的评论ID
-        reply_name: params.reply_name || "", // 被回复人名称
-      });
-      uni.request({
-        url: this.$apiHost + "/Article/newComment",
-        data: {
-          uuid: getApp().globalData.uuid,
-          type: "article",
-          article_id: this.arcID, // 文章ID
-          content: params.user_content, // 评论内容
-          parent_id: params.parent_id || 0, // 父评论ID
-          reply_id: params.reply_id || 0, // 回复的评论ID
-          reply_name: params.reply_name || "", // 被回复人名称
-        },
-        header: {
-          "content-type": "application/json",
-          sign: getApp().globalData.headerSign,
-        },
-        success: (res) => {
-          console.log("评论结果:", res.data);
-          if (res.data.success === "yes") {
-            callback(res.data); // 评论成功,传入后端返回的数据
-          }
-        },
-        fail: (e) => {
-          console.log("评论失败:", e);
-          uni.showToast({
-            title: "评论失败,请重试",
-            icon: "none",
-          });
-        },
-      });
-    },
-    /** 删除回调事件
-     * mode 删除模式
-     * -- bind: 当被删除的一级评论存在回复评论, 那么该评论内容变更显示为[当前评论内容已被移除]
-     * -- only: 仅删除当前评论(后端删除相关联的回复评论, 否则总数显示不对)
-     * -- all : 删除所有评论包括回复评论 前端遍历子评论上报
-     */
+						// 更新标签列表
+						// if (res.data.data.tags && res.data.data.tags !== "") {
+						// 	this.tag_list = res.data.data.tags.split(",");
+						// }
+					} else {
+						uni.showToast({
+							title: "获取文章信息失败",
+							icon: "none",
+						});
+					}
+				},
+				complete: (com) => {
+					// uni.hideLoading();
+				},
+				fail: (e) => {
+					console.log("请求失败:", e);
+					uni.showToast({
+						title: "网络请求失败",
+						icon: "none",
+					});
+				},
+			});
+		},
+		// 评论相关方法已移至CommentSection组件
 
-    deleteFun({ params, mode }, callback) {
-      console.log("deleteFun", {
-        params,
-        mode,
-      });
-      // 将params转换为逗号分隔的字符串
-      const idsString = Array.isArray(params)
-        ? params.join(",")
-        : params.toString();
+		// 唤起新评论弹框
+		openComment() {
+			if (this.$refs.commentSection) {
+				this.$refs.commentSection.openComment();
+			}
+		},
+		// 文章点赞
+		// 分享文章
+		shareArticle() {
+			// 如果在微信小程序环境
+			if (uni.getSystemInfoSync().platform === "mp-weixin") {
+				uni.showShareMenu({
+					withShareTicket: true,
+					menus: ["shareAppMessage", "shareTimeline"],
+				});
+			} else {
+				// 其他环境,如APP
+				uni.share({
+					provider: "weixin",
+					scene: "WXSceneSession",
+					type: 0,
+					title: this.articleInfo.title,
+					summary: this.articleInfo.content.substring(0, 40) + "...",
+					imageUrl: this.home_image,
+					success: function (res) {
+						console.log("分享成功:" + JSON.stringify(res));
+					},
+					fail: function (err) {
+						console.log("分享失败:" + JSON.stringify(err));
+					},
+				});
+			}
+		},
+		// 评论回调事件
+		replyFun({ params }, callback) {
+			// params = {
+			// 	...params,
+			// 	user_id: this.myInfo.user_id, // 用户id
+			// 	user_name: this.myInfo.user_name, // 用户名
+			// 	user_avatar: this.myInfo.user_avatar, // 用户头像地址
+			// 	user_content: this.commentValue, // 用户评论内容
+			// 	is_like: false, // 是否点赞
+			// 	like_count: 0, // 点赞数统计
+			// 	create_time: "刚刚", // 创建时间
+			// 	owner: true, // 是否为所有者 所有者可以进行删除 管理员默认true
+			// };
+			console.log("replyFun", {
+				uuid: getApp().globalData.uuid,
+				article_id: this.arcID, // 文章ID
+				content: params.user_content, // 评论内容
+				parent_id: params.parent_id || 0, // 父评论ID
+				reply_id: params.reply_id || 0, // 回复的评论ID
+				reply_name: params.reply_name || "", // 被回复人名称
+			});
+			uni.request({
+				url: this.$apiHost + "/Article/newComment",
+				data: {
+					uuid: getApp().globalData.uuid,
+					type: "article",
+					article_id: this.arcID, // 文章ID
+					content: params.user_content, // 评论内容
+					parent_id: params.parent_id || 0, // 父评论ID
+					reply_id: params.reply_id || 0, // 回复的评论ID
+					reply_name: params.reply_name || "", // 被回复人名称
+				},
+				header: {
+					"content-type": "application/json",
+					sign: getApp().globalData.headerSign,
+				},
+				success: (res) => {
+					console.log("评论结果:", res.data);
+					if (res.data.success === "yes") {
+						callback(res.data); // 评论成功,传入后端返回的数据
+					}
+				},
+				fail: (e) => {
+					console.log("评论失败:", e);
+					uni.showToast({
+						title: "评论失败,请重试",
+						icon: "none",
+					});
+				},
+			});
+		},
+		/** 删除回调事件
+		 * mode 删除模式
+		 * -- bind: 当被删除的一级评论存在回复评论, 那么该评论内容变更显示为[当前评论内容已被移除]
+		 * -- only: 仅删除当前评论(后端删除相关联的回复评论, 否则总数显示不对)
+		 * -- all : 删除所有评论包括回复评论 前端遍历子评论上报
+		 */
 
-      console.log("删除评论", idsString, mode);
-      uni.request({
-        url: this.$apiHost + "/Article/delComment",
-        data: {
-          uuid: getApp().globalData.uuid,
-          ids: idsString, // 将params转换为逗号分隔的字符串传递给ids
-          mode: mode,
-        },
-        header: {
-          "content-type": "application/json",
-          sign: getApp().globalData.headerSign,
-        },
-        success: (res) => {
-          console.log("删除结果:", res.data);
-          if (res.data.success === "yes") {
-            callback(res);
-          }
-        },
-        fail: (e) => {},
-      });
-      // switch (mode) {
-      // 	case "bind":
-      // 		// 逻辑: 调用接口进行评论内容修改 update
-      // 		setTimeout(() => callback(), 500); // 目前为了展示效果, 直接执行callback
-      // 		break;
-      // 	case "only":
-      // 		// 逻辑: 调用接口删除一个评论 delete
-      // 		setTimeout(() => callback(), 500); // 目前为了展示效果, 直接执行callback
-      // 		break;
-      // 	default:
-      // 		// all
-      // 		// 逻辑: 调用接口删除多个评论 [delete]
-      // 		setTimeout(() => callback(), 500); // 目前为了展示效果, 直接执行callback
-      // 		break;
-      // }
-    },
+		deleteFun({ params, mode }, callback) {
+			console.log("deleteFun", {
+				params,
+				mode,
+			});
+			// 将params转换为逗号分隔的字符串
+			const idsString = Array.isArray(params)
+				? params.join(",")
+				: params.toString();
 
-    showActionSheet(n) {
-      if (n == 0) {
-        this.items = [
-          {
-            text: "分享作品",
-            icon: "../../static/icon/cz_icon_fenxiangzuopin.png",
-          },
-        ];
-      }
-      if (n == 1) {
-        this.items = [
-          {
-            text: "分享作品",
-            icon: "../../static/icon/cz_icon_fenxiangzuopin.png",
-          },
-          {
-            text: "修改帖子",
-            icon: "../../static/icon/cz_icon_xiugaifengmian.png",
-          },
-          {
-            text: "删除作品",
-            icon: "../../static/icon/sy_icon_shanchu.png",
-            danger: true,
-          },
-        ];
-      }
-      this.$refs.actionSheet.show();
-    },
-    handleActionCancel() {
-      console.log("ActionSheet cancelled");
-    },
-    handleActionSelect(index, item) {
-      console.log("ActionSheet selected index:", item.text);
-      switch (item.text) {
-        case "分享作品":
-          console.log("分享作品"); 
-          break;
-        case "修改帖子":
-          console.log("修改帖子");
-          uni.navigateTo({
-            url: "/pages/make/fabuArticle?id=" + this.fileInformation.id,
-          });
-          break;
-        case "删除作品":
-          console.log("删除作品");
+			console.log("删除评论", idsString, mode);
+			uni.request({
+				url: this.$apiHost + "/Article/delComment",
+				data: {
+					uuid: getApp().globalData.uuid,
+					ids: idsString, // 将params转换为逗号分隔的字符串传递给ids
+					mode: mode,
+				},
+				header: {
+					"content-type": "application/json",
+					sign: getApp().globalData.headerSign,
+				},
+				success: (res) => {
+					console.log("删除结果:", res.data);
+					if (res.data.success === "yes") {
+						callback(res);
+					}
+				},
+				fail: (e) => { },
+			});
+			// switch (mode) {
+			// 	case "bind":
+			// 		// 逻辑: 调用接口进行评论内容修改 update
+			// 		setTimeout(() => callback(), 500); // 目前为了展示效果, 直接执行callback
+			// 		break;
+			// 	case "only":
+			// 		// 逻辑: 调用接口删除一个评论 delete
+			// 		setTimeout(() => callback(), 500); // 目前为了展示效果, 直接执行callback
+			// 		break;
+			// 	default:
+			// 		// all
+			// 		// 逻辑: 调用接口删除多个评论 [delete]
+			// 		setTimeout(() => callback(), 500); // 目前为了展示效果, 直接执行callback
+			// 		break;
+			// }
+		},
 
-          break;
-      }
-    },
-    imageString(str) {
-      if (!str) {
-        return [];
-      }
-      return str.split("|");
-    },
-    cutTime(time) {
-      if (!time) {
-        return "";
-      }
-      return time.split(" ")[0];
-    },
-  },
+		showActionSheet() {
+			if (!this.isItMe) {
+				this.items = [
+					{
+						text: "分享作品",
+						icon: "../../static/icon/cz_icon_fenxiangzuopin.png",
+					},
+				];
+			}
+			if (this.isItMe) {
+				this.items = [
+					{
+						text: "分享作品",
+						icon: "../../static/icon/cz_icon_fenxiangzuopin.png",
+					},
+					{
+						text: "修改帖子",
+						icon: "../../static/icon/cz_icon_xiugaifengmian.png",
+					},
+					{
+						text: "删除作品",
+						icon: "../../static/icon/sy_icon_shanchu.png",
+						danger: true,
+					},
+				];
+			}
+			this.$refs.actionSheet.show();
+		},
+		handleActionCancel() {
+			console.log("ActionSheet cancelled");
+		},
+		handleActionSelect(index, item) {
+			console.log("ActionSheet selected index:", item.text);
+			switch (item.text) {
+				case "分享作品":
+					console.log("分享作品");
+					break;
+				case "修改帖子":
+					console.log("修改帖子");
+					uni.navigateTo({
+						url: "/pages/make/fabuArticle?id=" + this.articleInfo.id,
+					});
+					break;
+				case "删除作品":
+					console.log("删除作品");
+					this.deleteArticle();
+					break;
+			}
+		},
+		imageString(str) {
+			if (!str) {
+				return [];
+			}
+			return str.split("|");
+		},
+		cutTime(time) {
+			if (!time) {
+				return "";
+			}
+			return time.split(" ")[0];
+		},
+		deleteArticle() {
+			this.$refs['DialogBox'].confirm({
+				title: '提示',
+				content: '确定删除该文章吗',
+				DialogType: 'inquiry',
+				btn1: '取消',
+				btn2: '确定',
+				animation: 0
+			}).then(() => {
+				uni.request({
+					url: this.$apiHost + '/Article/doAct',
+					data: {
+						uuid: getApp().globalData.uuid,
+						act: 'del',
+						id: this.articleInfo.id
+					},
+					header: {
+						'content-type': 'application/json',
+						'sign': getApp().globalData.headerSign
+					},
+					success: (res) => {
+						if (res.data.success === 'yes') {
+							uni.showToast({
+								title: '删除成功',
+								icon: 'success'
+							});
+							// 重新获取列表
+							// this.getArticleList();
+							setTimeout(() => {
+								uni.navigateBack({ 
+								});
+							}, 800);
+						} else {
+							uni.showToast({
+								title: res.data.str || '删除失败',
+								icon: 'none'
+							});
+						}
+					}
+				});
+			}).catch(() => {
+				// this.delCloseFun()
+			})
+		}
+	},
 };
 </script>
 

+ 10 - 73
pages/index/index.vue

@@ -32,7 +32,7 @@
 								<p>{{ getDayOfWeek }}</p>
 							</view>
 						</view>
-						<view class="search">
+						<view class="search" @click="goPage('/pages/index/Search')">
 							<uv-input placeholder="🔥“潮玩大作战” 派对季重磅开启!" border="none"
 								:custom-style="{ background: '#fff', paddingLeft: '25rpx' }" shape="circle">
 
@@ -208,7 +208,7 @@
 					</view> -->
 					<w-waterfall v-show="currentTab === 2 && newsList.length > 0" :data="newsList">
 						<template v-slot:content="{ item, width }">
-							<card :item="formatItem(item)" :width="width" :custom-style="{ background: '#fff' }"
+							<card :item="formatItem(item)" :width="width" goLink="/pages/index/articleDetail?id=" :custom-style="{ background: '#fff' }"
 								textColor="#000"></card>
 						</template>
 					</w-waterfall>
@@ -255,6 +255,7 @@ export default {
 		pageNavbar,
 		wWaterfall,
 		lhSelectCity,
+		card
 		// noviceGuidance
 	},
 	mixins: [tabbar],
@@ -494,76 +495,7 @@ export default {
 					break;
 			}
 		},
-		async init() {
-			const {
-				data
-			} = await this.getData();
-			this.list.push.apply(this.list, data);
-		},
-		// 模拟的后端数据
-		getData() {
-			return new Promise((resolve) => {
-				const imgs = [{
-					url: "https://e.zhichao.art/AI_images/a_1112_10.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1112_108.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1112_113.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1112_13.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1112_137.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1112_141.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1114__562.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1114__568.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1114__569.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1114__570.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1114__571.png",
-				},
-				{
-					url: "https://e.zhichao.art/AI_images/a_1114__575.png",
-				},
-				];
-				let list = [];
-				const doFn = (i) => {
-					const randomIndex = Math.floor(Math.random() * 10);
-					return {
-						allowEdit: i == 0,
-						image: imgs[randomIndex].url,
-						w: imgs[randomIndex].width,
-						h: imgs[randomIndex].height,
-						title: i % 2 == 0 ? `玛丽` : `凌音`,
-						desc: i % 2 == 0 ?
-							`欢迎使用uv-ui,uni-app生态专用的UI框架` : `开发者编写一套代码, 可发布到iOS、Android、H5、以及各种小程序`,
-					};
-				};
-				// 模拟异步
-				setTimeout(() => {
-					for (let i = 0; i < 20; i++) {
-						list.push(doFn(i));
-					}
-					resolve({
-						data: list,
-					});
-				}, 200);
-			});
-		},
+	 
 		switchTab(index) {
 			this.currentTab = index;
 
@@ -876,6 +808,11 @@ export default {
 				.catch((error) => {
 					console.error("获取图像失败:", error);
 				});
+				let img = ''
+				if (item.images) { 
+					img = item.images.split("|")[0]
+				}
+
 			// 处理接口返回的数据,使其适配card组件
 			return {
 				id: item.id,
@@ -884,7 +821,7 @@ export default {
 				avator: item.avator,
 				num_like: item.num_like,
 				num_view: item.num_view,
-				image: item.images || item.img_url || item.image, // 优先使用images字段
+				image: img || item.img_url || item.image, // 优先使用images字段
 				w: item.width,
 				h: item.height,
 				title: item.title || "",

+ 2 - 2
pages/index/workDetail.vue

@@ -316,9 +316,9 @@ export default {
       },
       items: [],
       myInfo: {
-        user_id: getApp().globalData.mobile, // 用户id
+        user_id: getApp().globalData.user_id, // 用户id
         user_name: getApp().globalData.nickname, // 用户名
-        user_avatar: getApp().globalData.avator , // 用户头像地址
+        user_avatar: getApp().globalData.avator, // 用户头像地址
       },
       // 文章作者信息(提示: 一般来自localstorage, 如果是实时获取的话, 那么获取到数据后再v-if显示评论组件)
       userInfo: {

+ 293 - 276
pages/login/login.vue

@@ -40,7 +40,7 @@
 					<view class="item">
 						<input type="text" class="input" v-model="code" placeholder="请输入验证码" maxlength="6" />
 						<view class="btn" v-if="captchaTime === 0" @click="getCode">获取验证码</view>
-						<view class="btn" v-if="captchaTime > 0">{{captchaTime}}秒后重试</view>
+						<view class="btn" v-if="captchaTime > 0">{{ captchaTime }}秒后重试</view>
 					</view>
 					<view class="other_list">
 						<text class="left"></text>
@@ -69,7 +69,7 @@
 				</text>
 			</view>
-			<view class="agree" v-if="false"><text class="agree2">{{push_token}}</text></view>
+			<view class="agree" v-if="false"><text class="agree2">{{ push_token }}</text></view>
 		</view>
 		<view class="blankHeight"></view>
 
@@ -78,318 +78,335 @@
 </template>
 
 <script>
-	import pubc from '@/common/public.js'
-	export default {
-		components: {},
-		data() {
-			return {
-				skey: '',
-				sel: 1,
-				is_agree: 0,
-				type: 'pass',
-				myinfo: {
-					nickname: '王思思',
-					join_name: '注册日期:2024年5月',
-					num_1: 0,
-					num_2: 0,
-					num_3: 0,
-					num_4: 0,
-					is_login: 'no',
-					num_history: 0,
-					num_collection: 0
-				},
-				mobile: '18899990000',
-				password: '111111',
-				code: '',
-				captchaTime: 0,
-				push_token: '',
-				lat: '',
-				lng: '',
-			}
-		},
-		onLoad() {
-			setTimeout(function() {
-				uni.setNavigationBarColor({
-					frontColor: '#ffffff',
-					backgroundColor: '#00000000',
-					animation: {
-						duration: 400,
-						timingFunc: 'easeIn'
-					}
-				})
-			}, 200);
-			let that = this;
-			uni.getPushClientId({
-				success: (res) => {
-					console.log("su:", res.cid);
-					that.push_token = res.cid;
-				},
-				fail(err) {
-					that.push_token = '';
-					console.log(err)
+import pubc from '@/common/public.js'
+export default {
+	components: {},
+	data() {
+		return {
+			skey: '',
+			sel: 1,
+			is_agree: 0,
+			type: 'pass',
+			myinfo: {
+				nickname: '王思思',
+				join_name: '注册日期:2024年5月',
+				num_1: 0,
+				num_2: 0,
+				num_3: 0,
+				num_4: 0,
+				is_login: 'no',
+				num_history: 0,
+				num_collection: 0
+			},
+			mobile: '18899990000',
+			password: '111111',
+			code: '',
+			captchaTime: 0,
+			push_token: '',
+			lat: '',
+			lng: '',
+		}
+	},
+	onLoad() {
+		setTimeout(function () {
+			uni.setNavigationBarColor({
+				frontColor: '#ffffff',
+				backgroundColor: '#00000000',
+				animation: {
+					duration: 400,
+					timingFunc: 'easeIn'
 				}
-			});
-		},
-		onShow() {
-			// let hsign = pubc.Encrypt("aboa-wifl-kwfl-zjfk-wlaa_FA0412932BAE9D98506580ADB348BEF9");
-			// console.log("hsign", hsign)
+			})
+		}, 200);
+		let that = this;
+		uni.getPushClientId({
+			success: (res) => {
+				console.log("su:", res.cid);
+				that.push_token = res.cid;
+			},
+			fail(err) {
+				that.push_token = '';
+				console.log(err)
+			}
+		});
+	},
+	onShow() {
+		// let hsign = pubc.Encrypt("aboa-wifl-kwfl-zjfk-wlaa_FA0412932BAE9D98506580ADB348BEF9");
+		// console.log("hsign", hsign)
 
 
-			// let skey = uni.getStorageSync("wapptoken");
-			// if (skey != null && skey != "" && skey != undefined) {
-			// 	uni.switchTab({
-			// 		url: "/pages/index/index"
-			// 	});
-			// 	return;
-			// }
-			// let that = this;
-			// uni.getLocation({
-			// 	type: 'wgs84',
-			// 	success: function(res) {
-			// 		that.lat = res.latitude;
-			// 		that.lng = res.longitude;
-			// 	},
-			// 	fail: function(error) {
-			// 		console.error('获取位置失败:', error);
-			// 	}
-			// });
+		// let skey = uni.getStorageSync("wapptoken");
+		// if (skey != null && skey != "" && skey != undefined) {
+		// 	uni.switchTab({
+		// 		url: "/pages/index/index"
+		// 	});
+		// 	return;
+		// }
+		// let that = this;
+		// uni.getLocation({
+		// 	type: 'wgs84',
+		// 	success: function(res) {
+		// 		that.lat = res.latitude;
+		// 		that.lng = res.longitude;
+		// 	},
+		// 	fail: function(error) {
+		// 		console.error('获取位置失败:', error);
+		// 	}
+		// });
+	},
+	methods: {
+		onBack() { },
+		agreeChk() {
+			if (this.is_agree == 0) {
+				this.is_agree = 1;
+			} else {
+				this.is_agree = 0;
+			}
 		},
-		methods: {
-			onBack() {},
-			agreeChk() {
-				if (this.is_agree == 0) {
-					this.is_agree = 1;
-				} else {
-					this.is_agree = 0;
-				}
-			},
-			goPage(page) {
-				if (page == 'yhxy') {
-					uni.navigateTo({
-						url: '/pages/AboutUs/yhxy'
-					})
-				} else if (page == 'yszc') {
-					// #ifdef APP-PLUS
-					plus.runtime.openWeb('https://e.zhichao.art/web/yszc.php') // plus.runtime.openWeb(href);
-					// #endif
+		goPage(page) {
+			if (page == 'yhxy') {
+				uni.navigateTo({
+					url: '/pages/AboutUs/yhxy'
+				})
+			} else if (page == 'yszc') {
+				// #ifdef APP-PLUS
+				plus.runtime.openWeb('https://e.zhichao.art/web/yszc.php') // plus.runtime.openWeb(href);
+				// #endif
 
-					// #ifdef H5
-					window.open('https://e.zhichao.art/web/yszc.php')
-					// #endif
+				// #ifdef H5
+				window.open('https://e.zhichao.art/web/yszc.php')
+				// #endif
 
-					// uni.navigateTo({
-					// 	url: '/pages/AboutUs/yszc'
-					// })
-				} else {
-					uni.navigateTo({
-						url: page,
-					});
-				}
-			},
-			toRegist() {
+				// uni.navigateTo({
+				// 	url: '/pages/AboutUs/yszc'
+				// })
+			} else {
 				uni.navigateTo({
-					url: "/pages/login/reg",
+					url: page,
 				});
-			},
-			toLogin() {
-				console.log('aaa');
-				let that = this;
-				if (this.mobile.length != 11) {
+			}
+		},
+		toRegist() {
+			uni.navigateTo({
+				url: "/pages/login/reg",
+			});
+		},
+		toLogin() {
+			console.log('aaa');
+			let that = this;
+			if (this.mobile.length != 11) {
+				uni.showToast({
+					title: "请输入手机号",
+					icon: "none",
+				});
+				return;
+			}
+			if (this.type == 'pass') {
+				if (this.password.length < 6) {
 					uni.showToast({
-						title: "请输入手机号",
+						title: "请输入不小于6位的密码",
 						icon: "none",
 					});
 					return;
 				}
-				if (this.type == 'pass') {
-					if (this.password.length < 6) {
-						uni.showToast({
-							title: "请输入不小于6位的密码",
-							icon: "none",
-						});
-						return;
-					}
-				} else {
-					if (this.code.length < 4) {
-						uni.showToast({
-							title: "请输入手机验证码",
-							icon: "none",
-						});
-						return;
-					}
-				}
-				if (this.is_agree == 0) {
+			} else {
+				if (this.code.length < 4) {
 					uni.showToast({
-						title: "请确认并选择协议",
+						title: "请输入手机验证码",
 						icon: "none",
 					});
 					return;
 				}
-				let method = '/Member/gamelogin';
-				if (this.btn_type == 'reg') {
-					method = '/Member/register';
-					if (this.code.length < 4) {
-						uni.showToast({
-							title: '请输入验证码',
-							icon: 'none'
-						});
-						return;
-					}
-				} else if (this.btn_type == 'reset') {
-					method = '/Member/resetPass';
-					if (this.code.length < 4) {
-						uni.showToast({
-							title: '请输入验证码',
-							icon: 'none'
-						});
-						return;
-					}
-				}
-				let channel = "";
-				// #ifdef APP-PLUS
-				channel = plus.runtime.channel;
-				// #endif
-
-				uni.showLoading({
-					mask: true,
-				});
-				console.log("host", this.$apiHost + method);
-				uni.request({
-					url: this.$apiHost + method,
-					data: {
-						uuid: getApp().globalData.uuid,
-						channel: channel,
-						loginType: this.type,
-						mobile: this.mobile,
-						password: this.password,
-						code: this.code,
-						push_token: this.push_token,
-						lat: this.lat,
-						lng: this.lng,
-					},
-					header: {
-						"content-type": "application/json",
-					},
-					success: (res) => {
-						console.log("----", res.data);
-						if (res.data.success == "yes") {
-							console.log("res.data", res.data);
-							if (res.data.uuid.length > 5) {
-								uni.setStorageSync("app_uuid", res.data.uuid);
-								getApp().globalData.uuid = res.data.uuid;
-							}
-							uni.setStorageSync("is_login", "yes");
-							uni.setStorageSync("wapptoken", res.data.skey);
-							getApp().globalData.skey = res.data.skey;
-							if (res.data.header_c != "") {
-								getApp().globalData.headerSign = pubc.Encrypt(res.data.header_c + "_" +
-									getApp().globalData.uuid);
-								uni.setStorageSync("headerSign", getApp().globalData.headerSign);
-							}
-							getApp().globalData.headerC = res.data.header_c;
-							if (res.data.nickname == '') {
-								uni.redirectTo({
-									url: '/pages/my/step'
-								});
-								return;
-							}
-							// 获取用户信息储存全局
-							uni.request({
-					url: this.$apiHost + '/User/getinfo',
-					data: {
-						uuid: getApp().globalData.uuid,
-						skey: getApp().globalData.skey
-					},
-					header: {
-						"content-type": "application/json",
-						'sign': getApp().globalData.headerSign
-					},
-					success: (res) => {
-						console.log("--获取用户信息--:", res.data);
-						if (res.data.need_login == "yes") {
-							
-						}
-						if (res.data.aihao) {
-							this.aihao_tags = res.data.aihao.split(",");
-						}
-						getApp().globalData.nickname=res.data.nickname
-							getApp().globalData.avator=res.data.avator
-							getApp().globalData.mobile=res.data.mobile
-						 
-					},
-					complete: (com) => {
-						// uni.hideLoading();
-					},
-					fail: (e) => {
-						console.log("----e:", e);
-					}
-				});
-							uni.switchTab({
-								url: "/pages/index/index",
-							});
-						} else if (res.data.success == "no") {
-							uni.showToast({
-								title: res.data.str,
-								icon: "none",
-							});
-						} else {
-							uni.showToast({
-								title: "操作失败,请联系客服",
-								icon: "none",
-							});
-						}
-					},
-					complete: (com) => {
-						uni.hideLoading();
-					},
+			}
+			if (this.is_agree == 0) {
+				uni.showToast({
+					title: "请确认并选择协议",
+					icon: "none",
 				});
-			},
-			getCode() {
-				if (this.mobile.length != 11) {
+				return;
+			}
+			let method = '/Member/gamelogin';
+			if (this.btn_type == 'reg') {
+				method = '/Member/register';
+				if (this.code.length < 4) {
 					uni.showToast({
-						title: '请输入手机号',
+						title: '请输入验证码',
 						icon: 'none'
 					});
 					return;
 				}
-				if (this.captchaTime > 0) {
+			} else if (this.btn_type == 'reset') {
+				method = '/Member/resetPass';
+				if (this.code.length < 4) {
 					uni.showToast({
-						title: '不能重复获取',
+						title: '请输入验证码',
 						icon: 'none'
 					});
 					return;
 				}
-				this.captchaTime = 60;
+			}
+			let channel = "";
+			// #ifdef APP-PLUS
+			channel = plus.runtime.channel;
+			// #endif
 
-				uni.request({
-					url: this.$apiHost + '/Web/getcode', //仅为示例,并非真实接口地址。
-					data: {
-						skey: this.skey,
-						mobile: this.mobile,
-					},
-					header: {
-						'content-type': 'application/json' //自定义请求头信息
-					},
-					success: (res) => {
-						console.log("----", res.data)
+			uni.showLoading({
+				mask: true,
+			});
+			console.log("host", this.$apiHost + method);
+			uni.request({
+				url: this.$apiHost + method,
+				data: {
+					uuid: getApp().globalData.uuid,
+					channel: channel,
+					loginType: this.type,
+					mobile: this.mobile,
+					password: this.password,
+					code: this.code,
+					push_token: this.push_token,
+					lat: this.lat,
+					lng: this.lng,
+				},
+				header: {
+					"content-type": "application/json",
+				},
+				success: (res) => {
+					console.log("----", res.data);
+					if (res.data.success == "yes") {
+						console.log("res.data", res.data);
+						if (res.data.uuid.length > 5) {
+							uni.setStorageSync("app_uuid", res.data.uuid);
+							getApp().globalData.uuid = res.data.uuid;
+						}
+						uni.setStorageSync("is_login", "yes");
+						uni.setStorageSync("wapptoken", res.data.skey);
+						getApp().globalData.skey = res.data.skey;
+						if (res.data.header_c != "") {
+							getApp().globalData.headerSign = pubc.Encrypt(res.data.header_c + "_" +
+								getApp().globalData.uuid);
+							uni.setStorageSync("headerSign", getApp().globalData.headerSign);
+						}
+						getApp().globalData.headerC = res.data.header_c;
+						if (res.data.nickname == '') {
+							uni.redirectTo({
+								url: '/pages/my/step'
+							});
+							return;
+						}
+						// 获取用户信息储存全局
+						uni.request({
+							url: this.$apiHost + '/User/getinfo',
+							data: {
+								uuid: getApp().globalData.uuid,
+								skey: getApp().globalData.skey
+							},
+							header: {
+								"content-type": "application/json",
+								'sign': getApp().globalData.headerSign
+							},
+							success: (res) => {
+								console.log("--获取用户信息--:", res.data);
+								if (res.data.need_login == "yes") {
+
+								}
+								if (res.data.aihao) {
+									this.aihao_tags = res.data.aihao.split(",");
+								}
+								if (res.data.nickname) {
+									getApp().globalData.nickname = res.data.nickname
+									uni.removeStorageSync("nickname" );
+									uni.setStorageSync("nickname", res.data.nickname);
+								}
+								if (res.data.mobile) {
+									getApp().globalData.mobile = res.data.mobile
+									uni.removeStorageSync("mobile", res.data.mobile);
+									uni.setStorageSync("mobile", res.data.mobile);
+								}
+								if (res.data.user_id) {
+									getApp().globalData.user_id = res.data.user_id
+									uni.removeStorageSync("user_id" );
+									uni.setStorageSync("user_id", res.data.user_id);
+								}
+								if (res.data.avator) {
+									getApp().globalData.avator = res.data.avator
+									uni.removeStorageSync("avator" );
+									uni.setStorageSync("avator", res.data.avator);
+								}
+
+							},
+							complete: (com) => {
+								// uni.hideLoading();
+							},
+							fail: (e) => {
+								console.log("----e:", e);
+							}
+						});
+						uni.switchTab({
+							url: "/pages/index/index",
+						});
+					} else if (res.data.success == "no") {
 						uni.showToast({
 							title: res.data.str,
-							icon: 'none'
-						})
-						if (res.data.success == 'yes') {
-							this.getCodeTime();
-						} else {
-							this.captchaTime = 0;
-						}
+							icon: "none",
+						});
+					} else {
+						uni.showToast({
+							title: "操作失败,请联系客服",
+							icon: "none",
+						});
 					}
+				},
+				complete: (com) => {
+					uni.hideLoading();
+				},
+			});
+		},
+		getCode() {
+			if (this.mobile.length != 11) {
+				uni.showToast({
+					title: '请输入手机号',
+					icon: 'none'
 				});
+				return;
+			}
+			if (this.captchaTime > 0) {
+				uni.showToast({
+					title: '不能重复获取',
+					icon: 'none'
+				});
+				return;
+			}
+			this.captchaTime = 60;
 
-			},
+			uni.request({
+				url: this.$apiHost + '/Web/getcode', //仅为示例,并非真实接口地址。
+				data: {
+					skey: this.skey,
+					mobile: this.mobile,
+				},
+				header: {
+					'content-type': 'application/json' //自定义请求头信息
+				},
+				success: (res) => {
+					console.log("----", res.data)
+					uni.showToast({
+						title: res.data.str,
+						icon: 'none'
+					})
+					if (res.data.success == 'yes') {
+						this.getCodeTime();
+					} else {
+						this.captchaTime = 0;
+					}
+				}
+			});
+
+		},
 
-		}
 	}
+}
 </script>
 
 <style scoped lang="scss">
-	@import 'login.scss';
+@import 'login.scss';
 </style>

+ 2 - 2
pages/makedetail/makeDetail.vue

@@ -128,7 +128,7 @@
 			<view class="failureReason">
 				<view class="title"> 审核未通关</view>
 				<view class="reviewContent">
-					{{ fileInformation.reason }}
+					<uv-parse :content="fileInformation.reason"></uv-parse>
 				</view>
 				<view class="btn-box" @click="closeConfirmationBox">知道了</view>
 			</view>
@@ -218,7 +218,7 @@ export default {
 				task_type: 0,
 				tdate: "",
 				title: "",
-				userID: 0,
+				userID: 0, 
 				xinzuo: "",
 			},
 			myinfo: {},

+ 32 - 30
pages/my/DelMemVerify.vue

@@ -125,6 +125,7 @@ export default {
             icon: 'none'
           });
           if (res.data.success == "yes") {
+            var that = this;
             this.$refs['DialogBox'].confirm({
               title: '警告',
               content: '1、注销账号是不可逆操作,该账号下所有一切资料一旦注销无法恢复;\n2、注销后,你账号下所有权益将被清除。',
@@ -133,24 +134,22 @@ export default {
               btn2: '是',
               animation: 0
             }).then(() => {
-              console.log('确定删除账号');
+              uni.request({
+                url: that.$apiHost + '/My/delete', //检测是否已绑定
+                data: {
+                  uuid: getApp().globalData.uuid
+                },
+                header: {
+                  'content-type': 'application/json' //自定义请求头信息
+                },
+                success: (res) => {
+                  uni.removeStorageSync("wapptoken");
+                  uni.redirectTo({
+                    url: '/pages/login/login',
+                  })
 
-              // uni.request({
-              // 	url: that.$apiHost + '/My/delete', //检测是否已绑定
-              // 	data: {
-              // 		uuid: getApp().globalData.uuid
-              // 	},
-              // 	header: {
-              // 		'content-type': 'application/json' //自定义请求头信息
-              // 	},
-              // 	success: (res) => {
-              // 		uni.removeStorageSync("wapptoken");
-              // 		uni.redirectTo({
-              // 			url: '/pages/login/login',
-              // 		})
-
-              // 	}
-              // });
+                }
+              });
             })
           }
         }
@@ -310,25 +309,28 @@ export default {
     }
   }
 }
-.btn{
+
+.btn {
   color: #0084FF;
   text-align: center;
   font-family: 'PingFang SC-Medium';
   font-weight: 400;
-  &.disabled{
+
+  &.disabled {
     color: #7C7C7C;
   }
 }
- .btn_submit {
-	background: #1F1F1F;
-	border-radius: 76rpx;
-	color: #fff;
-	margin: 0 auto;
-	margin-top: 500rpx;
-	width: 626rpx;
-	height:88rpx;
-	display: flex;
-	align-items: center;
-	justify-content: center;
+
+.btn_submit {
+  background: #1F1F1F;
+  border-radius: 76rpx;
+  color: #fff;
+  margin: 0 auto;
+  margin-top: 500rpx;
+  width: 626rpx;
+  height: 88rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 </style>

+ 16 - 3
pages/my/editInfo.vue

@@ -326,7 +326,18 @@ export default {
 						title: res.data.str,
 						animation: 0
 					});
-					if (res.data.success == 'yes' && isBack == true) {
+					if (this.success == 'yes' && isBack == true) {
+						if (this.nickname) {
+							getApp().globalData.nickname = this.nickname
+							uni.removeStorageSync("nickname" );
+							uni.setStorageSync("nickname", this.nickname);
+						}
+						 
+						if (this.avator) {
+							getApp().globalData.avator = this.avator
+							uni.removeStorageSync("avator" );
+							uni.setStorageSync("avator", this.avator);
+						}
 						setTimeout(function () {
 							console.log('返回');
 
@@ -460,6 +471,7 @@ page {
 		box-sizing: border-box;
 		padding: 30rpx 40rpx 46rpx 20rpx;
 		border-radius: 20rpx;
+
 		.avator-box {
 			display: flex;
 			align-items: center;
@@ -525,7 +537,7 @@ page {
 		width: 690rpx;
 		padding: 20rpx;
 
-		.tag { 
+		.tag {
 			border-radius: 94rpx;
 			margin-right: 10rpx;
 			margin-top: 10rpx;
@@ -538,6 +550,7 @@ page {
 			align-items: center;
 			background: #fff;
 			border-width: 0;
+
 			.close {
 				width: 28rpx;
 				margin-left: 2rpx;
@@ -545,7 +558,7 @@ page {
 			}
 		}
 
-		.active { 
+		.active {
 			border-width: 0;
 			background: #ACF934;
 		}

+ 10 - 5
pages/my/editMobile.vue

@@ -123,11 +123,11 @@ export default {
 			}
 			if (this.processProgress == 1) {
 				url = '/Web/getcode'
-				data = { mobile: _this.mobile, skey: this.skey,}
+				data = { mobile: _this.mobile, skey: this.skey, }
 			}
 			uni.request({
 				url: this.$apiHost + url,
-				data: { 
+				data: {
 					uuid: getApp().globalData.uuid,
 					...data
 				},
@@ -150,7 +150,7 @@ export default {
 		},
 		submitData() {
 			console.log('submitData');
-			
+
 			if (this.mobile == "") {
 				uni.showToast({
 					title: "请输入手机号",
@@ -195,7 +195,7 @@ export default {
 							this.code = ''
 							this.captchaTime = 0
 							this.processProgress = 1
-							
+
 							uni.showToast({
 								title: "请输入新手机号",
 								icon: 'none'
@@ -212,7 +212,7 @@ export default {
 		},
 		newSubmitData() {
 			console.log("newSubmitData");
-			
+
 			if (this.mobile == "") {
 				uni.showToast({
 					title: "请输入手机号",
@@ -255,6 +255,11 @@ export default {
 					});
 					if (res.data.success == "yes") {
 						// 可以在此处添加修改成功后的逻辑
+						if (this.mobile) {
+							getApp().globalData.mobile = this.mobile
+							uni.removeStorageSync("mobile" );
+							uni.setStorageSync("mobile", this.mobile);
+						}
 					}
 				}
 			});

+ 7 - 3
pages/my/setting.vue

@@ -126,7 +126,7 @@ export default {
 		// 
 		var isContentRecommendation = getStorage('isContentRecommendation')
 		if (isContentRecommendation != undefined) {
-			this.isContentRecommendation = isContentRecommendation == 'false' ? false : true  
+			this.isContentRecommendation = isContentRecommendation == 'false' ? false : true
 		} else {
 			setStorage('isContentRecommendation', true)
 		}
@@ -153,8 +153,8 @@ export default {
 			});
 		},
 		switch1Change(e) {
-			console.log(e.detail.value,55555555555);
-			
+			console.log(e.detail.value, 55555555555);
+
 			setStorage('isContentRecommendation', e.detail.value)
 		},
 		onBack() { },
@@ -309,6 +309,10 @@ export default {
 				animation: 0
 			}).then(() => {
 				uni.removeStorageSync("wapptoken");
+				uni.removeStorageSync("nickname");
+				uni.removeStorageSync("avator");
+				uni.removeStorageSync("mobile");
+				uni.removeStorageSync("user_id");
 				uni.redirectTo({
 					url: '/pages/login/login',
 				})

BIN
static/home/sy_icon_chakanquanbu.png


BIN
static/home/sy_icon_lishijilu.png


BIN
static/home/sy_icon_shanchu.png


BIN
static/home/sy_icon_shuaxin@3x.png


BIN
static/home/sy_icon_sousuo.png


+ 13 - 0
uni_modules/uv-parse/changelog.md

@@ -0,0 +1,13 @@
+## 1.0.4(2023-07-17)
+1. 优化文档
+2. 优化其他
+## 1.0.3(2023-06-19)
+1. 修复nvue模式下不显示的BUG
+## 1.0.2(2023-06-02)
+1. 修复可能存在的BUG
+2. 优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-parse 富文本解析器

+ 576 - 0
uni_modules/uv-parse/components/uv-parse/node/node.vue

@@ -0,0 +1,576 @@
+<template>
+  <view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style">
+    <block v-for="(n, i) in childs" v-bind:key="i">
+      <!-- 图片 -->
+      <!-- 占位图 -->
+      <image v-if="n.name==='img'&&!n.t&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
+      <!-- 显示图片 -->
+      <!-- #ifdef H5 || (APP-PLUS && VUE2) -->
+      <img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #endif -->
+      <!-- #ifndef H5 || (APP-PLUS && VUE2) -->
+      <!-- 表格中的图片,使用 rich-text 防止大小不正确 -->
+      <rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style,src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" />
+      <!-- #endif -->
+      <!-- #ifndef H5 || APP-PLUS -->
+      <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #endif -->
+      <!-- #ifdef APP-PLUS && VUE3 -->
+      <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #endif -->
+      <!-- 文本 -->
+      <!-- #ifdef MP-WEIXIN -->
+      <text v-else-if="n.text" :user-select="opts[4]=='force'&&isiOS" decode>{{n.text}}</text>
+      <!-- #endif -->
+      <!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
+      <text v-else-if="n.text" decode>{{n.text}}</text>
+      <!-- #endif -->
+      <text v-else-if="n.name==='br'">\n</text>
+      <!-- 链接 -->
+      <view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
+        <node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
+      </view>
+      <!-- 视频 -->
+      <!-- #ifdef APP-PLUS -->
+      <view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" />
+      <!-- #endif -->
+      <!-- #ifndef APP-PLUS -->
+      <video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
+      <!-- #endif -->
+      <!-- #ifdef H5 || APP-PLUS -->
+      <iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
+      <embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" />
+      <!-- #endif -->
+      <!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) -->
+      <!-- 音频 -->
+      <audio v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
+      <!-- #endif -->
+      <view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
+        <node v-if="n.name==='li'" :childs="n.children" :opts="opts" />
+        <view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
+          <node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" />
+          <block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
+            <view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
+              <node :childs="tr.children" :opts="opts" />
+            </view>
+            <view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
+              <view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
+                <node :childs="td.children" :opts="opts" />
+              </view>
+            </view>
+          </block>
+        </view>
+      </view>
+      
+      <!-- 富文本 -->
+      <!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
+      <rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
+      <!-- #endif -->
+      <!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
+      <rich-text v-else-if="!n.c" :id="n.attrs.id" :style="'display:inline;'+n.f" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
+      <!-- #endif -->
+      <!-- 继续递归 -->
+      <view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
+        <node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" />
+      </view>
+      <node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
+    </block>
+  </view>
+</template>
+<script module="handler" lang="wxs">
+// 行内标签列表
+var inlineTags = {
+  abbr: true,
+  b: true,
+  big: true,
+  code: true,
+  del: true,
+  em: true,
+  i: true,
+  ins: true,
+  label: true,
+  q: true,
+  small: true,
+  span: true,
+  strong: true,
+  sub: true,
+  sup: true
+}
+/**
+ * @description 判断是否为行内标签
+ */
+module.exports = {
+  isInline: function (tagName, style) {
+    return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1
+  }
+}
+</script>
+<script>
+
+import node from './node'
+export default {
+  name: 'node',
+  options: {
+    // #ifdef MP-WEIXIN
+    virtualHost: true,
+    // #endif
+    // #ifdef MP-TOUTIAO
+    addGlobalClass: false
+    // #endif
+  },
+  data () {
+    return {
+      ctrl: {},
+      // #ifdef MP-WEIXIN
+      isiOS: uni.getSystemInfoSync().system.includes('iOS')
+      // #endif
+    }
+  },
+  props: {
+    name: String,
+    attrs: {
+      type: Object,
+      default () {
+        return {}
+      }
+    },
+    childs: Array,
+    opts: Array
+  },
+  components: {
+
+    // #ifndef (H5 || APP-PLUS) && VUE3
+    node
+    // #endif
+  },
+  mounted () {
+    this.$nextTick(() => {
+      for (this.root = this.$parent; this.root.$options.name !== 'uv-parse'; this.root = this.root.$parent);
+    })
+    // #ifdef H5 || APP-PLUS
+    if (this.opts[0]) {
+      let i
+      for (i = this.childs.length; i--;) {
+        if (this.childs[i].name === 'img') break
+      }
+      if (i !== -1) {
+        this.observer = uni.createIntersectionObserver(this).relativeToViewport({
+          top: 500,
+          bottom: 500
+        })
+        this.observer.observe('._img', res => {
+          if (res.intersectionRatio) {
+            this.$set(this.ctrl, 'load', 1)
+            this.observer.disconnect()
+          }
+        })
+      }
+    }
+    // #endif
+  },
+  beforeDestroy () {
+    // #ifdef H5 || APP-PLUS
+    if (this.observer) {
+      this.observer.disconnect()
+    }
+    // #endif
+  },
+  methods:{
+    // #ifdef MP-WEIXIN
+    toJSON () { return this },
+    // #endif
+    /**
+     * @description 播放视频事件
+     * @param {Event} e
+     */
+    play (e) {
+      this.root.$emit('play')
+      // #ifndef APP-PLUS
+      if (this.root.pauseVideo) {
+        let flag = false
+        const id = e.target.id
+        for (let i = this.root._videos.length; i--;) {
+          if (this.root._videos[i].id === id) {
+            flag = true
+          } else {
+            this.root._videos[i].pause() // 自动暂停其他视频
+          }
+        }
+        // 将自己加入列表
+        if (!flag) {
+          const ctx = uni.createVideoContext(id
+            // #ifndef MP-BAIDU
+            , this
+            // #endif
+          )
+          ctx.id = id
+          if (this.root.playbackRate) {
+            ctx.playbackRate(this.root.playbackRate)
+          }
+          this.root._videos.push(ctx)
+        }
+      }
+      // #endif
+    },
+
+    /**
+     * @description 图片点击事件
+     * @param {Event} e
+     */
+    imgTap (e) {
+      const node = this.childs[e.currentTarget.dataset.i]
+      if (node.a) {
+        this.linkTap(node.a)
+        return
+      }
+      if (node.attrs.ignore) return
+      // #ifdef H5 || APP-PLUS
+      node.attrs.src = node.attrs.src || node.attrs['data-src']
+      // #endif
+      this.root.$emit('imgtap', node.attrs)
+      // 自动预览图片
+      if (this.root.previewImg) {
+        uni.previewImage({
+          // #ifdef MP-WEIXIN
+          showmenu: this.root.showImgMenu,
+          // #endif
+          // #ifdef MP-ALIPAY
+          enablesavephoto: this.root.showImgMenu,
+          enableShowPhotoDownload: this.root.showImgMenu,
+          // #endif
+          current: parseInt(node.attrs.i),
+          urls: this.root.imgList
+        })
+      }
+    },
+
+    /**
+     * @description 图片长按
+     */
+    imgLongTap (e) {
+      // #ifdef APP-PLUS
+      const attrs = this.childs[e.currentTarget.dataset.i].attrs
+      if (this.opts[3] && !attrs.ignore) {
+        uni.showActionSheet({
+          itemList: ['保存图片'],
+          success: () => {
+            const save = path => {
+              uni.saveImageToPhotosAlbum({
+                filePath: path,
+                success () {
+                  uni.showToast({
+                    title: '保存成功'
+                  })
+                }
+              })
+            }
+            if (this.root.imgList[attrs.i].startsWith('http')) {
+              uni.downloadFile({
+                url: this.root.imgList[attrs.i],
+                success: res => save(res.tempFilePath)
+              })
+            } else {
+              save(this.root.imgList[attrs.i])
+            }
+          }
+        })
+      }
+      // #endif
+    },
+
+    /**
+     * @description 图片加载完成事件
+     * @param {Event} e
+     */
+    imgLoad (e) {
+      const i = e.currentTarget.dataset.i
+      /* #ifndef H5 || (APP-PLUS && VUE2) */
+      if (!this.childs[i].w) {
+        // 设置原宽度
+        this.$set(this.ctrl, i, e.detail.width)
+      } else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {
+        // 加载完毕,取消加载中占位图
+        this.$set(this.ctrl, i, 1)
+      }
+      this.checkReady()
+    },
+
+    /**
+     * @description 检查是否所有图片加载完毕
+     */
+    checkReady () {
+      if (this.root && !this.root.lazyLoad) {
+        this.root._unloadimgs -= 1
+        if (!this.root._unloadimgs) {
+          setTimeout(() => {
+            this.root.getRect().then(rect => {
+              this.root.$emit('ready', rect)
+            }).catch(() => {
+              this.root.$emit('ready', {})
+            })
+          }, 350)
+        }
+      }
+    },
+
+    /**
+     * @description 链接点击事件
+     * @param {Event} e
+     */
+    linkTap (e) {
+      const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
+      const attrs = node.attrs || e
+      const href = attrs.href
+      this.root.$emit('linktap', Object.assign({
+        innerText: this.root.getText(node.children || []) // 链接内的文本内容
+      }, attrs))
+      if (href) {
+        if (href[0] === '#') {
+          // 跳转锚点
+          this.root.navigateTo(href.substring(1)).catch(() => { })
+        } else if (href.split('?')[0].includes('://')) {
+          // 复制外部链接
+          if (this.root.copyLink) {
+            // #ifdef H5
+            window.open(href)
+            // #endif
+            // #ifdef MP
+            uni.setClipboardData({
+              data: href,
+              success: () =>
+                uni.showToast({
+                  title: '链接已复制'
+                })
+            })
+            // #endif
+            // #ifdef APP-PLUS
+            plus.runtime.openWeb(href)
+            // #endif
+          }
+        } else {
+          // 跳转页面
+          uni.navigateTo({
+            url: href,
+            fail () {
+              uni.switchTab({
+                url: href,
+                fail () { }
+              })
+            }
+          })
+        }
+      }
+    },
+
+    /**
+     * @description 错误事件
+     * @param {Event} e
+     */
+    mediaError (e) {
+      const i = e.currentTarget.dataset.i
+      const node = this.childs[i]
+      // 加载其他源
+      if (node.name === 'video' || node.name === 'audio') {
+        let index = (this.ctrl[i] || 0) + 1
+        if (index > node.src.length) {
+          index = 0
+        }
+        if (index < node.src.length) {
+          this.$set(this.ctrl, i, index)
+          return
+        }
+      } else if (node.name === 'img') {
+        // #ifdef H5 && VUE3
+        if (this.opts[0] && !this.ctrl.load) return
+        // #endif
+        // 显示错误占位图
+        if (this.opts[2]) {
+          this.$set(this.ctrl, i, -1)
+        }
+        this.checkReady()
+      }
+      if (this.root) {
+        this.root.$emit('error', {
+          source: node.name,
+          attrs: node.attrs,
+          // #ifndef H5 && VUE3
+          errMsg: e.detail.errMsg
+          // #endif
+        })
+      }
+    }
+  }
+}
+</script>
+<style>
+/* a 标签默认效果 */
+._a {
+  padding: 1.5px 0 1.5px 0;
+  color: #366092;
+  word-break: break-all;
+}
+
+/* a 标签点击态效果 */
+._hover {
+  text-decoration: underline;
+  opacity: 0.7;
+}
+
+/* 图片默认效果 */
+._img {
+  max-width: 100%;
+  -webkit-touch-callout: none;
+}
+
+/* 内部样式 */
+
+._block {
+  display: block;
+}
+
+._b,
+._strong {
+  font-weight: bold;
+}
+
+._code {
+  font-family: monospace;
+}
+
+._del {
+  text-decoration: line-through;
+}
+
+._em,
+._i {
+  font-style: italic;
+}
+
+._h1 {
+  font-size: 2em;
+}
+
+._h2 {
+  font-size: 1.5em;
+}
+
+._h3 {
+  font-size: 1.17em;
+}
+
+._h5 {
+  font-size: 0.83em;
+}
+
+._h6 {
+  font-size: 0.67em;
+}
+
+._h1,
+._h2,
+._h3,
+._h4,
+._h5,
+._h6 {
+  display: block;
+  font-weight: bold;
+}
+
+._image {
+  height: 1px;
+}
+
+._ins {
+  text-decoration: underline;
+}
+
+._li {
+  display: list-item;
+}
+
+._ol {
+  list-style-type: decimal;
+}
+
+._ol,
+._ul {
+  display: block;
+  padding-left: 40px;
+  margin: 1em 0;
+}
+
+._q::before {
+  content: '"';
+}
+
+._q::after {
+  content: '"';
+}
+
+._sub {
+  font-size: smaller;
+  vertical-align: sub;
+}
+
+._sup {
+  font-size: smaller;
+  vertical-align: super;
+}
+
+._thead,
+._tbody,
+._tfoot {
+  display: table-row-group;
+}
+
+._tr {
+  display: table-row;
+}
+
+._td,
+._th {
+  display: table-cell;
+  vertical-align: middle;
+}
+
+._th {
+  font-weight: bold;
+  text-align: center;
+}
+
+._ul {
+  list-style-type: disc;
+}
+
+._ul ._ul {
+  margin: 0;
+  list-style-type: circle;
+}
+
+._ul ._ul ._ul {
+  list-style-type: square;
+}
+
+._abbr,
+._b,
+._code,
+._del,
+._em,
+._i,
+._ins,
+._label,
+._q,
+._span,
+._strong,
+._sub,
+._sup {
+  display: inline;
+}
+
+/* #ifdef APP-PLUS */
+._video {
+  width: 300px;
+  height: 225px;
+}
+/* #endif */
+</style>

+ 1335 - 0
uni_modules/uv-parse/components/uv-parse/parser.js

@@ -0,0 +1,1335 @@
+/**
+ * @fileoverview html 解析器
+ */
+
+// 配置
+const config = {
+  // 信任的标签(保持标签名不变)
+  trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,ruby,rt,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
+
+  // 块级标签(转为 div,其他的非信任标签转为 span)
+  blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
+
+  // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
+  // 行内标签
+  inlineTags: makeMap('abbr,b,big,code,del,em,i,ins,label,q,small,span,strong,sub,sup'),
+  // #endif
+
+  // 要移除的标签
+  ignoreTags: makeMap('area,base,canvas,embed,frame,head,iframe,input,link,map,meta,param,rp,script,source,style,textarea,title,track,wbr'),
+
+  // 自闭合的标签
+  voidTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
+
+  // html 实体
+  entities: {
+    lt: '<',
+    gt: '>',
+    quot: '"',
+    apos: "'",
+    ensp: '\u2002',
+    emsp: '\u2003',
+    nbsp: '\xA0',
+    semi: ';',
+    ndash: '–',
+    mdash: '—',
+    middot: '·',
+    lsquo: '‘',
+    rsquo: '’',
+    ldquo: '“',
+    rdquo: '”',
+    bull: '•',
+    hellip: '…',
+    larr: '←',
+    uarr: '↑',
+    rarr: '→',
+    darr: '↓'
+  },
+
+  // 默认的标签样式
+  tagStyle: {
+    // #ifndef APP-PLUS-NVUE
+    address: 'font-style:italic',
+    big: 'display:inline;font-size:1.2em',
+    caption: 'display:table-caption;text-align:center',
+    center: 'text-align:center',
+    cite: 'font-style:italic',
+    dd: 'margin-left:40px',
+    mark: 'background-color:yellow',
+    pre: 'font-family:monospace;white-space:pre',
+    s: 'text-decoration:line-through',
+    small: 'display:inline;font-size:0.8em',
+    strike: 'text-decoration:line-through',
+    u: 'text-decoration:underline'
+    // #endif
+  },
+
+  // svg 大小写对照表
+  svgDict: {
+    animatetransform: 'animateTransform',
+    lineargradient: 'linearGradient',
+    viewbox: 'viewBox',
+    attributename: 'attributeName',
+    repeatcount: 'repeatCount',
+    repeatdur: 'repeatDur'
+  }
+}
+const tagSelector={}
+const {
+  windowWidth,
+  // #ifdef MP-WEIXIN
+  system
+  // #endif
+} = uni.getSystemInfoSync()
+const blankChar = makeMap(' ,\r,\n,\t,\f')
+let idIndex = 0
+
+// #ifdef H5 || APP-PLUS
+config.ignoreTags.iframe = undefined
+config.trustTags.iframe = true
+config.ignoreTags.embed = undefined
+config.trustTags.embed = true
+// #endif
+// #ifdef APP-PLUS-NVUE
+config.ignoreTags.source = undefined
+config.ignoreTags.style = undefined
+// #endif
+
+/**
+ * @description 创建 map
+ * @param {String} str 逗号分隔
+ */
+function makeMap (str) {
+  const map = Object.create(null)
+  const list = str.split(',')
+  for (let i = list.length; i--;) {
+    map[list[i]] = true
+  }
+  return map
+}
+
+/**
+ * @description 解码 html 实体
+ * @param {String} str 要解码的字符串
+ * @param {Boolean} amp 要不要解码 &amp;
+ * @returns {String} 解码后的字符串
+ */
+function decodeEntity (str, amp) {
+  let i = str.indexOf('&')
+  while (i !== -1) {
+    const j = str.indexOf(';', i + 3)
+    let code
+    if (j === -1) break
+    if (str[i + 1] === '#') {
+      // &#123; 形式的实体
+      code = parseInt((str[i + 2] === 'x' ? '0' : '') + str.substring(i + 2, j))
+      if (!isNaN(code)) {
+        str = str.substr(0, i) + String.fromCharCode(code) + str.substr(j + 1)
+      }
+    } else {
+      // &nbsp; 形式的实体
+      code = str.substring(i + 1, j)
+      if (config.entities[code] || (code === 'amp' && amp)) {
+        str = str.substr(0, i) + (config.entities[code] || '&') + str.substr(j + 1)
+      }
+    }
+    i = str.indexOf('&', i + 1)
+  }
+  return str
+}
+
+/**
+ * @description 合并多个块级标签,加快长内容渲染
+ * @param {Array} nodes 要合并的标签数组
+ */
+function mergeNodes (nodes) {
+  let i = nodes.length - 1
+  for (let j = i; j >= -1; j--) {
+    if (j === -1 || nodes[j].c || !nodes[j].name || (nodes[j].name !== 'div' && nodes[j].name !== 'p' && nodes[j].name[0] !== 'h') || (nodes[j].attrs.style || '').includes('inline')) {
+      if (i - j >= 5) {
+        nodes.splice(j + 1, i - j, {
+          name: 'div',
+          attrs: {},
+          children: nodes.slice(j + 1, i + 1)
+        })
+      }
+      i = j - 1
+    }
+  }
+}
+
+/**
+ * @description html 解析器
+ * @param {Object} vm 组件实例
+ */
+function Parser (vm) {
+  this.options = vm || {}
+  this.tagStyle = Object.assign({}, config.tagStyle, this.options.tagStyle)
+  this.imgList = vm.imgList || []
+  this.imgList._unloadimgs = 0
+  this.plugins = vm.plugins || []
+  this.attrs = Object.create(null)
+  this.stack = []
+  this.nodes = []
+  this.pre = (this.options.containerStyle || '').includes('white-space') && this.options.containerStyle.includes('pre') ? 2 : 0
+}
+
+/**
+ * @description 执行解析
+ * @param {String} content 要解析的文本
+ */
+Parser.prototype.parse = function (content) {
+  // 插件处理
+  for (let i = this.plugins.length; i--;) {
+    if (this.plugins[i].onUpdate) {
+      content = this.plugins[i].onUpdate(content, config) || content
+    }
+  }
+
+  new Lexer(this).parse(content)
+  // 出栈未闭合的标签
+  while (this.stack.length) {
+    this.popNode()
+  }
+  if (this.nodes.length > 50) {
+    mergeNodes(this.nodes)
+  }
+  return this.nodes
+}
+
+/**
+ * @description 将标签暴露出来(不被 rich-text 包含)
+ */
+Parser.prototype.expose = function () {
+  // #ifndef APP-PLUS-NVUE
+  for (let i = this.stack.length; i--;) {
+    const item = this.stack[i]
+    if (item.c || item.name === 'a' || item.name === 'video' || item.name === 'audio') return
+    item.c = 1
+  }
+  // #endif
+}
+
+/**
+ * @description 处理插件
+ * @param {Object} node 要处理的标签
+ * @returns {Boolean} 是否要移除此标签
+ */
+Parser.prototype.hook = function (node) {
+  for (let i = this.plugins.length; i--;) {
+    if (this.plugins[i].onParse && this.plugins[i].onParse(node, this) === false) {
+      return false
+    }
+  }
+  return true
+}
+
+/**
+ * @description 将链接拼接上主域名
+ * @param {String} url 需要拼接的链接
+ * @returns {String} 拼接后的链接
+ */
+Parser.prototype.getUrl = function (url) {
+  const domain = this.options.domain
+  if (url[0] === '/') {
+    if (url[1] === '/') {
+      // // 开头的补充协议名
+      url = (domain ? domain.split('://')[0] : 'http') + ':' + url
+    } else if (domain) {
+      // 否则补充整个域名
+      url = domain + url
+    } /* #ifdef APP-PLUS */ else {
+      url = plus.io.convertLocalFileSystemURL(url)
+    } /* #endif */
+  } else if (!url.includes('data:') && !url.includes('://')) {
+    if (domain) {
+      url = domain + '/' + url
+    } /* #ifdef APP-PLUS */ else {
+      url = plus.io.convertLocalFileSystemURL(url)
+    } /* #endif */
+  }
+  return url
+}
+
+/**
+ * @description 解析样式表
+ * @param {Object} node 标签
+ * @returns {Object}
+ */
+Parser.prototype.parseStyle = function (node) {
+  const attrs = node.attrs
+  const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';'))
+  const styleObj = {}
+  let tmp = ''
+
+  if (attrs.id && !this.xml) {
+    // 暴露锚点
+    if (this.options.useAnchor) {
+      this.expose()
+    } else if (node.name !== 'img' && node.name !== 'a' && node.name !== 'video' && node.name !== 'audio') {
+      attrs.id = undefined
+    }
+  }
+
+  // 转换 width 和 height 属性
+  if (attrs.width) {
+    styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')
+    attrs.width = undefined
+  }
+  if (attrs.height) {
+    styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')
+    attrs.height = undefined
+  }
+
+  for (let i = 0, len = list.length; i < len; i++) {
+    const info = list[i].split(':')
+    if (info.length < 2) continue
+    const key = info.shift().trim().toLowerCase()
+    let value = info.join(':').trim()
+    if ((value[0] === '-' && value.lastIndexOf('-') > 0) || value.includes('safe')) {
+      // 兼容性的 css 不压缩
+      tmp += `;${key}:${value}`
+    } else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) {
+      // 重复的样式进行覆盖
+      if (value.includes('url')) {
+        // 填充链接
+        let j = value.indexOf('(') + 1
+        if (j) {
+          while (value[j] === '"' || value[j] === "'" || blankChar[value[j]]) {
+            j++
+          }
+          value = value.substr(0, j) + this.getUrl(value.substr(j))
+        }
+      } else if (value.includes('rpx')) {
+        // 转换 rpx(rich-text 内部不支持 rpx)
+        value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px')
+      }
+      styleObj[key] = value
+    }
+  }
+
+  node.attrs.style = tmp
+  return styleObj
+}
+
+/**
+ * @description 解析到标签名
+ * @param {String} name 标签名
+ * @private
+ */
+Parser.prototype.onTagName = function (name) {
+  this.tagName = this.xml ? name : name.toLowerCase()
+  if (this.tagName === 'svg') {
+    this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感
+    config.ignoreTags.style = undefined // svg 标签内 style 可用
+  }
+}
+
+/**
+ * @description 解析到属性名
+ * @param {String} name 属性名
+ * @private
+ */
+Parser.prototype.onAttrName = function (name) {
+  name = this.xml ? name : name.toLowerCase()
+  if (name.substr(0, 5) === 'data-') {
+    if (name === 'data-src' && !this.attrs.src) {
+      // data-src 自动转为 src
+      this.attrName = 'src'
+    } else if (this.tagName === 'img' || this.tagName === 'a') {
+      // a 和 img 标签保留 data- 的属性,可以在 imgtap 和 linktap 事件中使用
+      this.attrName = name
+    } else {
+      // 剩余的移除以减小大小
+      this.attrName = undefined
+    }
+  } else {
+    this.attrName = name
+    this.attrs[name] = 'T' // boolean 型属性缺省设置
+  }
+}
+
+/**
+ * @description 解析到属性值
+ * @param {String} val 属性值
+ * @private
+ */
+Parser.prototype.onAttrVal = function (val) {
+  const name = this.attrName || ''
+  if (name === 'style' || name === 'href') {
+    // 部分属性进行实体解码
+    this.attrs[name] = decodeEntity(val, true)
+  } else if (name.includes('src')) {
+    // 拼接主域名
+    this.attrs[name] = this.getUrl(decodeEntity(val, true))
+  } else if (name) {
+    this.attrs[name] = val
+  }
+}
+
+/**
+ * @description 解析到标签开始
+ * @param {Boolean} selfClose 是否有自闭合标识 />
+ * @private
+ */
+Parser.prototype.onOpenTag = function (selfClose) {
+  // 拼装 node
+  const node = Object.create(null)
+  node.name = this.tagName
+  node.attrs = this.attrs
+  // 避免因为自动 diff 使得 type 被设置为 null 导致部分内容不显示
+  if (this.options.nodes.length) {
+    node.type = 'node'
+  }
+  this.attrs = Object.create(null)
+
+  const attrs = node.attrs
+  const parent = this.stack[this.stack.length - 1]
+  const siblings = parent ? parent.children : this.nodes
+  const close = this.xml ? selfClose : config.voidTags[node.name]
+
+  // 替换标签名选择器
+  if (tagSelector[node.name]) {
+    attrs.class = tagSelector[node.name] + (attrs.class ? ' ' + attrs.class : '')
+  }
+
+  // 转换 embed 标签
+  if (node.name === 'embed') {
+    // #ifndef H5 || APP-PLUS
+    const src = attrs.src || ''
+    // 按照后缀名和 type 将 embed 转为 video 或 audio
+    if (src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8') || (attrs.type || '').includes('video')) {
+      node.name = 'video'
+    } else if (src.includes('.mp3') || src.includes('.wav') || src.includes('.aac') || src.includes('.m4a') || (attrs.type || '').includes('audio')) {
+      node.name = 'audio'
+    }
+    if (attrs.autostart) {
+      attrs.autoplay = 'T'
+    }
+    attrs.controls = 'T'
+    // #endif
+    // #ifdef H5 || APP-PLUS
+    this.expose()
+    // #endif
+  }
+
+  // #ifndef APP-PLUS-NVUE
+  // 处理音视频
+  if (node.name === 'video' || node.name === 'audio') {
+    // 设置 id 以便获取 context
+    if (node.name === 'video' && !attrs.id) {
+      attrs.id = 'v' + idIndex++
+    }
+    // 没有设置 controls 也没有设置 autoplay 的自动设置 controls
+    if (!attrs.controls && !attrs.autoplay) {
+      attrs.controls = 'T'
+    }
+    // 用数组存储所有可用的 source
+    node.src = []
+    if (attrs.src) {
+      node.src.push(attrs.src)
+      attrs.src = undefined
+    }
+    this.expose()
+  }
+  // #endif
+
+  // 处理自闭合标签
+  if (close) {
+    if (!this.hook(node) || config.ignoreTags[node.name]) {
+      // 通过 base 标签设置主域名
+      if (node.name === 'base' && !this.options.domain) {
+        this.options.domain = attrs.href
+      } /* #ifndef APP-PLUS-NVUE */ else if (node.name === 'source' && parent && (parent.name === 'video' || parent.name === 'audio') && attrs.src) {
+        // 设置 source 标签(仅父节点为 video 或 audio 时有效)
+        parent.src.push(attrs.src)
+      } /* #endif */
+      return
+    }
+
+    // 解析 style
+    const styleObj = this.parseStyle(node)
+
+    // 处理图片
+    if (node.name === 'img') {
+      if (attrs.src) {
+        // 标记 webp
+        if (attrs.src.includes('webp')) {
+          node.webp = 'T'
+        }
+        // data url 图片如果没有设置 original-src 默认为不可预览的小图片
+        if (attrs.src.includes('data:') && !attrs['original-src']) {
+          attrs.ignore = 'T'
+        }
+        if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {
+          for (let i = this.stack.length; i--;) {
+            const item = this.stack[i]
+            if (item.name === 'a') {
+              node.a = item.attrs
+            }
+            if (item.name === 'table' && !node.webp && !attrs.src.includes('cloud://')) {
+              if (!styleObj.display || styleObj.display.includes('inline')) {
+                node.t = 'inline-block'
+              } else {
+                node.t = styleObj.display
+              }
+              styleObj.display = undefined
+            }
+            // #ifndef H5 || APP-PLUS
+            const style = item.attrs.style || ''
+            if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || parseInt(styleObj.width) > 100)) {
+              styleObj.width = '100% !important'
+              styleObj.height = ''
+              for (let j = i + 1; j < this.stack.length; j++) {
+                this.stack[j].attrs.style = (this.stack[j].attrs.style || '').replace('inline-', '')
+              }
+            } else if (style.includes('flex') && styleObj.width === '100%') {
+              for (let j = i + 1; j < this.stack.length; j++) {
+                const style = this.stack[j].attrs.style || ''
+                if (!style.includes(';width') && !style.includes(' width') && style.indexOf('width') !== 0) {
+                  styleObj.width = ''
+                  break
+                }
+              }
+            } else if (style.includes('inline-block')) {
+              if (styleObj.width && styleObj.width[styleObj.width.length - 1] === '%') {
+                item.attrs.style += ';max-width:' + styleObj.width
+                styleObj.width = ''
+              } else {
+                item.attrs.style += ';max-width:100%'
+              }
+            }
+            // #endif
+            item.c = 1
+          }
+          attrs.i = this.imgList.length.toString()
+          let src = attrs['original-src'] || attrs.src
+          // #ifndef H5 || MP-ALIPAY || APP-PLUS || MP-360
+          if (this.imgList.includes(src)) {
+            // 如果有重复的链接则对域名进行随机大小写变换避免预览时错位
+            let i = src.indexOf('://')
+            if (i !== -1) {
+              i += 3
+              let newSrc = src.substr(0, i)
+              for (; i < src.length; i++) {
+                if (src[i] === '/') break
+                newSrc += Math.random() > 0.5 ? src[i].toUpperCase() : src[i]
+              }
+              newSrc += src.substr(i)
+              src = newSrc
+            }
+          }
+          // #endif
+          this.imgList.push(src)
+          if (!node.t) {
+            this.imgList._unloadimgs += 1
+          }
+          // #ifdef H5 || APP-PLUS
+          if (this.options.lazyLoad) {
+            attrs['data-src'] = attrs.src
+            attrs.src = undefined
+          }
+          // #endif
+        }
+      }
+      if (styleObj.display === 'inline') {
+        styleObj.display = ''
+      }
+      // #ifndef APP-PLUS-NVUE
+      if (attrs.ignore) {
+        styleObj['max-width'] = styleObj['max-width'] || '100%'
+        attrs.style += ';-webkit-touch-callout:none'
+      }
+      // #endif
+      // 设置的宽度超出屏幕,为避免变形,高度转为自动
+      if (parseInt(styleObj.width) > windowWidth) {
+        styleObj.height = undefined
+      }
+      // 记录是否设置了宽高
+      if (!isNaN(parseInt(styleObj.width))) {
+        node.w = 'T'
+      }
+      if (!isNaN(parseInt(styleObj.height)) && (!styleObj.height.includes('%') || (parent && (parent.attrs.style || '').includes('height')))) {
+        node.h = 'T'
+      }
+    } else if (node.name === 'svg') {
+      siblings.push(node)
+      this.stack.push(node)
+      this.popNode()
+      return
+    }
+    for (const key in styleObj) {
+      if (styleObj[key]) {
+        attrs.style += `;${key}:${styleObj[key].replace(' !important', '')}`
+      }
+    }
+    attrs.style = attrs.style.substr(1) || undefined
+    // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
+    if (!attrs.style) {
+      delete attrs.style
+    }
+    // #endif
+  } else {
+    if ((node.name === 'pre' || ((attrs.style || '').includes('white-space') && attrs.style.includes('pre'))) && this.pre !== 2) {
+      this.pre = node.pre = 1
+    }
+    node.children = []
+    this.stack.push(node)
+  }
+
+  // 加入节点树
+  siblings.push(node)
+}
+
+/**
+ * @description 解析到标签结束
+ * @param {String} name 标签名
+ * @private
+ */
+Parser.prototype.onCloseTag = function (name) {
+  // 依次出栈到匹配为止
+  name = this.xml ? name : name.toLowerCase()
+  let i
+  for (i = this.stack.length; i--;) {
+    if (this.stack[i].name === name) break
+  }
+  if (i !== -1) {
+    while (this.stack.length > i) {
+      this.popNode()
+    }
+  } else if (name === 'p' || name === 'br') {
+    const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
+    siblings.push({
+      name,
+      attrs: {
+        class: tagSelector[name] || '',
+        style: this.tagStyle[name] || ''
+      }
+    })
+  }
+}
+
+/**
+ * @description 处理标签出栈
+ * @private
+ */
+Parser.prototype.popNode = function () {
+  const node = this.stack.pop()
+  let attrs = node.attrs
+  const children = node.children
+  const parent = this.stack[this.stack.length - 1]
+  const siblings = parent ? parent.children : this.nodes
+
+  if (!this.hook(node) || config.ignoreTags[node.name]) {
+    // 获取标题
+    if (node.name === 'title' && children.length && children[0].type === 'text' && this.options.setTitle) {
+      uni.setNavigationBarTitle({
+        title: children[0].text
+      })
+    }
+    siblings.pop()
+    return
+  }
+
+  if (node.pre && this.pre !== 2) {
+    // 是否合并空白符标识
+    this.pre = node.pre = undefined
+    for (let i = this.stack.length; i--;) {
+      if (this.stack[i].pre) {
+        this.pre = 1
+      }
+    }
+  }
+
+  const styleObj = {}
+
+  // 转换 svg
+  if (node.name === 'svg') {
+    if (this.xml > 1) {
+      // 多层 svg 嵌套
+      this.xml--
+      return
+    }
+    // #ifdef APP-PLUS-NVUE
+    (function traversal (node) {
+      if (node.name) {
+        // 调整 svg 的大小写
+        node.name = config.svgDict[node.name] || node.name
+        for (const item in node.attrs) {
+          if (config.svgDict[item]) {
+            node.attrs[config.svgDict[item]] = node.attrs[item]
+            node.attrs[item] = undefined
+          }
+        }
+        for (let i = 0; i < (node.children || []).length; i++) {
+          traversal(node.children[i])
+        }
+      }
+    })(node)
+    // #endif
+    // #ifndef APP-PLUS-NVUE
+    let src = ''
+    const style = attrs.style
+    attrs.style = ''
+    attrs.xmlns = 'http://www.w3.org/2000/svg';
+    (function traversal (node) {
+      if (node.type === 'text') {
+        src += node.text
+        return
+      }
+      const name = config.svgDict[node.name] || node.name
+      src += '<' + name
+      for (const item in node.attrs) {
+        const val = node.attrs[item]
+        if (val) {
+          src += ` ${config.svgDict[item] || item}="${val}"`
+        }
+      }
+      if (!node.children) {
+        src += '/>'
+      } else {
+        src += '>'
+        for (let i = 0; i < node.children.length; i++) {
+          traversal(node.children[i])
+        }
+        src += '</' + name + '>'
+      }
+    })(node)
+    node.name = 'img'
+    node.attrs = {
+      src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
+      style,
+      ignore: 'T'
+    }
+    node.children = undefined
+    // #endif
+    this.xml = false
+    config.ignoreTags.style = true
+    return
+  }
+
+  // #ifndef APP-PLUS-NVUE
+  // 转换 align 属性
+  if (attrs.align) {
+    if (node.name === 'table') {
+      if (attrs.align === 'center') {
+        styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'
+      } else {
+        styleObj.float = attrs.align
+      }
+    } else {
+      styleObj['text-align'] = attrs.align
+    }
+    attrs.align = undefined
+  }
+
+  // 转换 dir 属性
+  if (attrs.dir) {
+    styleObj.direction = attrs.dir
+    attrs.dir = undefined
+  }
+
+  // 转换 font 标签的属性
+  if (node.name === 'font') {
+    if (attrs.color) {
+      styleObj.color = attrs.color
+      attrs.color = undefined
+    }
+    if (attrs.face) {
+      styleObj['font-family'] = attrs.face
+      attrs.face = undefined
+    }
+    if (attrs.size) {
+      let size = parseInt(attrs.size)
+      if (!isNaN(size)) {
+        if (size < 1) {
+          size = 1
+        } else if (size > 7) {
+          size = 7
+        }
+        styleObj['font-size'] = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'xxx-large'][size - 1]
+      }
+      attrs.size = undefined
+    }
+  }
+  // #endif
+
+  // 一些编辑器的自带 class
+  if ((attrs.class || '').includes('align-center')) {
+    styleObj['text-align'] = 'center'
+  }
+
+  Object.assign(styleObj, this.parseStyle(node))
+
+  if (node.name !== 'table' && parseInt(styleObj.width) > windowWidth) {
+    styleObj['max-width'] = '100%'
+    styleObj['box-sizing'] = 'border-box'
+  }
+
+  // #ifndef APP-PLUS-NVUE
+  if (config.blockTags[node.name]) {
+    node.name = 'div'
+  } else if (!config.trustTags[node.name] && !this.xml) {
+    // 未知标签转为 span,避免无法显示
+    node.name = 'span'
+  }
+
+  if (node.name === 'a' || node.name === 'ad'
+    // #ifdef H5 || APP-PLUS
+    || node.name === 'iframe' // eslint-disable-line
+    // #endif
+  ) {
+    this.expose()
+  } else if (node.name === 'video') {
+    if ((styleObj.height || '').includes('auto')) {
+      styleObj.height = undefined
+    }
+    /* #ifdef APP-PLUS */
+    let str = '<video style="width:100%;height:100%"'
+    for (const item in attrs) {
+      if (attrs[item]) {
+        str += ' ' + item + '="' + attrs[item] + '"'
+      }
+    }
+    if (this.options.pauseVideo) {
+      str += ' onplay="this.dispatchEvent(new CustomEvent(\'vplay\',{bubbles:!0}));for(var e=document.getElementsByTagName(\'video\'),t=0;t<e.length;t++)e[t]!=this&&e[t].pause()"'
+    }
+    str += '>'
+    for (let i = 0; i < node.src.length; i++) {
+      str += '<source src="' + node.src[i] + '">'
+    }
+    str += '</video>'
+    node.html = str
+    /* #endif */
+  } else if ((node.name === 'ul' || node.name === 'ol') && node.c) {
+    // 列表处理
+    const types = {
+      a: 'lower-alpha',
+      A: 'upper-alpha',
+      i: 'lower-roman',
+      I: 'upper-roman'
+    }
+    if (types[attrs.type]) {
+      attrs.style += ';list-style-type:' + types[attrs.type]
+      attrs.type = undefined
+    }
+    for (let i = children.length; i--;) {
+      if (children[i].name === 'li') {
+        children[i].c = 1
+      }
+    }
+  } else if (node.name === 'table') {
+    // 表格处理
+    // cellpadding、cellspacing、border 这几个常用表格属性需要通过转换实现
+    let padding = parseFloat(attrs.cellpadding)
+    let spacing = parseFloat(attrs.cellspacing)
+    const border = parseFloat(attrs.border)
+    const bordercolor = styleObj['border-color']
+    const borderstyle = styleObj['border-style']
+    if (node.c) {
+      // padding 和 spacing 默认 2
+      if (isNaN(padding)) {
+        padding = 2
+      }
+      if (isNaN(spacing)) {
+        spacing = 2
+      }
+    }
+    if (border) {
+      attrs.style += `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}`
+    }
+    if (node.flag && node.c) {
+      // 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现
+      styleObj.display = 'grid'
+      if (spacing) {
+        styleObj['grid-gap'] = spacing + 'px'
+        styleObj.padding = spacing + 'px'
+      } else if (border) {
+        // 无间隔的情况下避免边框重叠
+        attrs.style += ';border-left:0;border-top:0'
+      }
+
+      const width = [] // 表格的列宽
+      const trList = [] // tr 列表
+      const cells = [] // 保存新的单元格
+      const map = {}; // 被合并单元格占用的格子
+
+      (function traversal (nodes) {
+        for (let i = 0; i < nodes.length; i++) {
+          if (nodes[i].name === 'tr') {
+            trList.push(nodes[i])
+          } else {
+            traversal(nodes[i].children || [])
+          }
+        }
+      })(children)
+
+      for (let row = 1; row <= trList.length; row++) {
+        let col = 1
+        for (let j = 0; j < trList[row - 1].children.length; j++) {
+          const td = trList[row - 1].children[j]
+          if (td.name === 'td' || td.name === 'th') {
+            // 这个格子被上面的单元格占用,则列号++
+            while (map[row + '.' + col]) {
+              col++
+            }
+            let style = td.attrs.style || ''
+            let start = style.indexOf('width') ? style.indexOf(';width') : 0
+            // 提取出 td 的宽度
+            if (start !== -1) {
+              let end = style.indexOf(';', start + 6)
+              if (end === -1) {
+                end = style.length
+              }
+              if (!td.attrs.colspan) {
+                width[col] = style.substring(start ? start + 7 : 6, end)
+              }
+              style = style.substr(0, start) + style.substr(end)
+            }
+            // 设置竖直对齐
+            style += ';display:flex'
+            start = style.indexOf('vertical-align')
+            if (start !== -1) {
+              const val = style.substr(start + 15, 10)
+              if (val.includes('middle')) {
+                style += ';align-items:center'
+              } else if (val.includes('bottom')) {
+                style += ';align-items:flex-end'
+              }
+            } else {
+              style += ';align-items:center'
+            }
+            // 设置水平对齐
+            start = style.indexOf('text-align')
+            if (start !== -1) {
+              const val = style.substr(start + 11, 10)
+              if (val.includes('center')) {
+                style += ';justify-content: center'
+              } else if (val.includes('right')) {
+                style += ';justify-content: right'
+              }
+            }
+            style = (border ? `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}` + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '') + ';' + style
+            // 处理列合并
+            if (td.attrs.colspan) {
+              style += `;grid-column-start:${col};grid-column-end:${col + parseInt(td.attrs.colspan)}`
+              if (!td.attrs.rowspan) {
+                style += `;grid-row-start:${row};grid-row-end:${row + 1}`
+              }
+              col += parseInt(td.attrs.colspan) - 1
+            }
+            // 处理行合并
+            if (td.attrs.rowspan) {
+              style += `;grid-row-start:${row};grid-row-end:${row + parseInt(td.attrs.rowspan)}`
+              if (!td.attrs.colspan) {
+                style += `;grid-column-start:${col};grid-column-end:${col + 1}`
+              }
+              // 记录下方单元格被占用
+              for (let rowspan = 1; rowspan < td.attrs.rowspan; rowspan++) {
+                for (let colspan = 0; colspan < (td.attrs.colspan || 1); colspan++) {
+                  map[(row + rowspan) + '.' + (col - colspan)] = 1
+                }
+              }
+            }
+            if (style) {
+              td.attrs.style = style
+            }
+            cells.push(td)
+            col++
+          }
+        }
+        if (row === 1) {
+          let temp = ''
+          for (let i = 1; i < col; i++) {
+            temp += (width[i] ? width[i] : 'auto') + ' '
+          }
+          styleObj['grid-template-columns'] = temp
+        }
+      }
+      node.children = cells
+    } else {
+      // 没有使用合并单元格的表格通过 table 布局实现
+      if (node.c) {
+        styleObj.display = 'table'
+      }
+      if (!isNaN(spacing)) {
+        styleObj['border-spacing'] = spacing + 'px'
+      }
+      if (border || padding) {
+        // 遍历
+        (function traversal (nodes) {
+          for (let i = 0; i < nodes.length; i++) {
+            const td = nodes[i]
+            if (td.name === 'th' || td.name === 'td') {
+              if (border) {
+                td.attrs.style = `border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'};${td.attrs.style || ''}`
+              }
+              if (padding) {
+                td.attrs.style = `padding:${padding}px;${td.attrs.style || ''}`
+              }
+            } else if (td.children) {
+              traversal(td.children)
+            }
+          }
+        })(children)
+      }
+    }
+    // 给表格添加一个单独的横向滚动层
+    if (this.options.scrollTable && !(attrs.style || '').includes('inline')) {
+      const table = Object.assign({}, node)
+      node.name = 'div'
+      node.attrs = {
+        style: 'overflow:auto'
+      }
+      node.children = [table]
+      attrs = table.attrs
+    }
+  } else if ((node.name === 'td' || node.name === 'th') && (attrs.colspan || attrs.rowspan)) {
+    for (let i = this.stack.length; i--;) {
+      if (this.stack[i].name === 'table') {
+        this.stack[i].flag = 1 // 指示含有合并单元格
+        break
+      }
+    }
+  } else if (node.name === 'ruby') {
+    // 转换 ruby
+    node.name = 'span'
+    for (let i = 0; i < children.length - 1; i++) {
+      if (children[i].type === 'text' && children[i + 1].name === 'rt') {
+        children[i] = {
+          name: 'div',
+          attrs: {
+            style: 'display:inline-block;text-align:center'
+          },
+          children: [{
+            name: 'div',
+            attrs: {
+              style: 'font-size:50%;' + (children[i + 1].attrs.style || '')
+            },
+            children: children[i + 1].children
+          }, children[i]]
+        }
+        children.splice(i + 1, 1)
+      }
+    }
+  } else if (node.c) {
+    (function traversal (node) {
+      node.c = 2
+      for (let i = node.children.length; i--;) {
+        const child = node.children[i]
+        // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
+        if (child.name && (config.inlineTags[child.name] || ((child.attrs.style || '').includes('inline') && child.children)) && !child.c) {
+          traversal(child)
+        }
+        // #endif
+        if (!child.c || child.name === 'table') {
+          node.c = 1
+        }
+      }
+    })(node)
+  }
+
+  if ((styleObj.display || '').includes('flex') && !node.c) {
+    for (let i = children.length; i--;) {
+      const item = children[i]
+      if (item.f) {
+        item.attrs.style = (item.attrs.style || '') + item.f
+        item.f = undefined
+      }
+    }
+  }
+  // flex 布局时部分样式需要提取到 rich-text 外层
+  const flex = parent && ((parent.attrs.style || '').includes('flex') || (parent.attrs.style || '').includes('grid'))
+    // #ifdef MP-WEIXIN
+    // 检查基础库版本 virtualHost 是否可用
+    && !(node.c && wx.getNFCAdapter) // eslint-disable-line
+    // #endif
+    // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
+    && !node.c // eslint-disable-line
+  // #endif
+  if (flex) {
+    node.f = ';max-width:100%'
+  }
+
+  if (children.length >= 50 && node.c && !(styleObj.display || '').includes('flex')) {
+    mergeNodes(children)
+  }
+  // #endif
+
+  for (const key in styleObj) {
+    if (styleObj[key]) {
+      const val = `;${key}:${styleObj[key].replace(' !important', '')}`
+      /* #ifndef APP-PLUS-NVUE */
+      if (flex && ((key.includes('flex') && key !== 'flex-direction') || key === 'align-self' || key.includes('grid') || styleObj[key][0] === '-' || (key.includes('width') && val.includes('%')))) {
+        node.f += val
+        if (key === 'width') {
+          attrs.style += ';width:100%'
+        }
+      } else /* #endif */ {
+        attrs.style += val
+      }
+    }
+  }
+  attrs.style = attrs.style.substr(1) || undefined
+  // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
+  for (const key in attrs) {
+    if (!attrs[key]) {
+      delete attrs[key]
+    }
+  }
+  // #endif
+}
+
+/**
+ * @description 解析到文本
+ * @param {String} text 文本内容
+ */
+Parser.prototype.onText = function (text) {
+  if (!this.pre) {
+    // 合并空白符
+    let trim = ''
+    let flag
+    for (let i = 0, len = text.length; i < len; i++) {
+      if (!blankChar[text[i]]) {
+        trim += text[i]
+      } else {
+        if (trim[trim.length - 1] !== ' ') {
+          trim += ' '
+        }
+        if (text[i] === '\n' && !flag) {
+          flag = true
+        }
+      }
+    }
+    // 去除含有换行符的空串
+    if (trim === ' ') {
+      if (flag) return
+      // #ifdef VUE3
+      else {
+        const parent = this.stack[this.stack.length - 1]
+        if (parent && parent.name[0] === 't') return
+      }
+      // #endif
+    }
+    text = trim
+  }
+  const node = Object.create(null)
+  node.type = 'text'
+  // #ifdef (MP-BAIDU || MP-ALIPAY || MP-TOUTIAO) && VUE3
+  node.attrs = {}
+  // #endif
+  node.text = decodeEntity(text)
+  if (this.hook(node)) {
+    // #ifdef MP-WEIXIN
+    if (this.options.selectable === 'force' && system.includes('iOS') && !uni.canIUse('rich-text.user-select')) {
+      this.expose()
+    }
+    // #endif
+    const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
+    siblings.push(node)
+  }
+}
+
+/**
+ * @description html 词法分析器
+ * @param {Object} handler 高层处理器
+ */
+function Lexer (handler) {
+  this.handler = handler
+}
+
+/**
+ * @description 执行解析
+ * @param {String} content 要解析的文本
+ */
+Lexer.prototype.parse = function (content) {
+  this.content = content || ''
+  this.i = 0 // 标记解析位置
+  this.start = 0 // 标记一个单词的开始位置
+  this.state = this.text // 当前状态
+  for (let len = this.content.length; this.i !== -1 && this.i < len;) {
+    this.state()
+  }
+}
+
+/**
+ * @description 检查标签是否闭合
+ * @param {String} method 如果闭合要进行的操作
+ * @returns {Boolean} 是否闭合
+ * @private
+ */
+Lexer.prototype.checkClose = function (method) {
+  const selfClose = this.content[this.i] === '/'
+  if (this.content[this.i] === '>' || (selfClose && this.content[this.i + 1] === '>')) {
+    if (method) {
+      this.handler[method](this.content.substring(this.start, this.i))
+    }
+    this.i += selfClose ? 2 : 1
+    this.start = this.i
+    this.handler.onOpenTag(selfClose)
+    if (this.handler.tagName === 'script') {
+      this.i = this.content.indexOf('</', this.i)
+      if (this.i !== -1) {
+        this.i += 2
+        this.start = this.i
+      }
+      this.state = this.endTag
+    } else {
+      this.state = this.text
+    }
+    return true
+  }
+  return false
+}
+
+/**
+ * @description 文本状态
+ * @private
+ */
+Lexer.prototype.text = function () {
+  this.i = this.content.indexOf('<', this.i) // 查找最近的标签
+  if (this.i === -1) {
+    // 没有标签了
+    if (this.start < this.content.length) {
+      this.handler.onText(this.content.substring(this.start, this.content.length))
+    }
+    return
+  }
+  const c = this.content[this.i + 1]
+  if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+    // 标签开头
+    if (this.start !== this.i) {
+      this.handler.onText(this.content.substring(this.start, this.i))
+    }
+    this.start = ++this.i
+    this.state = this.tagName
+  } else if (c === '/' || c === '!' || c === '?') {
+    if (this.start !== this.i) {
+      this.handler.onText(this.content.substring(this.start, this.i))
+    }
+    const next = this.content[this.i + 2]
+    if (c === '/' && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
+      // 标签结尾
+      this.i += 2
+      this.start = this.i
+      this.state = this.endTag
+      return
+    }
+    // 处理注释
+    let end = '-->'
+    if (c !== '!' || this.content[this.i + 2] !== '-' || this.content[this.i + 3] !== '-') {
+      end = '>'
+    }
+    this.i = this.content.indexOf(end, this.i)
+    if (this.i !== -1) {
+      this.i += end.length
+      this.start = this.i
+    }
+  } else {
+    this.i++
+  }
+}
+
+/**
+ * @description 标签名状态
+ * @private
+ */
+Lexer.prototype.tagName = function () {
+  if (blankChar[this.content[this.i]]) {
+    // 解析到标签名
+    this.handler.onTagName(this.content.substring(this.start, this.i))
+    while (blankChar[this.content[++this.i]]);
+    if (this.i < this.content.length && !this.checkClose()) {
+      this.start = this.i
+      this.state = this.attrName
+    }
+  } else if (!this.checkClose('onTagName')) {
+    this.i++
+  }
+}
+
+/**
+ * @description 属性名状态
+ * @private
+ */
+Lexer.prototype.attrName = function () {
+  let c = this.content[this.i]
+  if (blankChar[c] || c === '=') {
+    // 解析到属性名
+    this.handler.onAttrName(this.content.substring(this.start, this.i))
+    let needVal = c === '='
+    const len = this.content.length
+    while (++this.i < len) {
+      c = this.content[this.i]
+      if (!blankChar[c]) {
+        if (this.checkClose()) return
+        if (needVal) {
+          // 等号后遇到第一个非空字符
+          this.start = this.i
+          this.state = this.attrVal
+          return
+        }
+        if (this.content[this.i] === '=') {
+          needVal = true
+        } else {
+          this.start = this.i
+          this.state = this.attrName
+          return
+        }
+      }
+    }
+  } else if (!this.checkClose('onAttrName')) {
+    this.i++
+  }
+}
+
+/**
+ * @description 属性值状态
+ * @private
+ */
+Lexer.prototype.attrVal = function () {
+  const c = this.content[this.i]
+  const len = this.content.length
+  if (c === '"' || c === "'") {
+    // 有冒号的属性
+    this.start = ++this.i
+    this.i = this.content.indexOf(c, this.i)
+    if (this.i === -1) return
+    this.handler.onAttrVal(this.content.substring(this.start, this.i))
+  } else {
+    // 没有冒号的属性
+    for (; this.i < len; this.i++) {
+      if (blankChar[this.content[this.i]]) {
+        this.handler.onAttrVal(this.content.substring(this.start, this.i))
+        break
+      } else if (this.checkClose('onAttrVal')) return
+    }
+  }
+  while (blankChar[this.content[++this.i]]);
+  if (this.i < len && !this.checkClose()) {
+    this.start = this.i
+    this.state = this.attrName
+  }
+}
+
+/**
+ * @description 结束标签状态
+ * @returns {String} 结束的标签名
+ * @private
+ */
+Lexer.prototype.endTag = function () {
+  const c = this.content[this.i]
+  if (blankChar[c] || c === '>' || c === '/') {
+    this.handler.onCloseTag(this.content.substring(this.start, this.i))
+    if (c !== '>') {
+      this.i = this.content.indexOf('>', this.i)
+      if (this.i === -1) return
+    }
+    this.start = ++this.i
+    this.state = this.text
+  } else {
+    this.i++
+  }
+}
+
+export default Parser

+ 498 - 0
uni_modules/uv-parse/components/uv-parse/uv-parse.vue

@@ -0,0 +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/uv-parse/static/app-plus/uv-parse/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
+    <!-- #endif -->
+  </view>
+</template>
+
+<script>
+/**
+ * uv-parse v1.0.3
+ * @description 富文本组件
+ * @tutorial https://www.uvui.cn/components/parse.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: 'uv-parse',
+  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>

+ 87 - 0
uni_modules/uv-parse/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-parse",
+  "displayName": "uv-parse 富文本解析器 全面兼容vue3+2、app、h5、小程序等多端",
+  "version": "1.0.4",
+  "description": "uv-parse 该组件一般用于富文本解析场景,比如解析文章内容,商品详情,带原生HTML标签的各类字符串等,此组件和uni-app官方的rich-text组件功能有重合之处,但是也有不同的地方。",
+  "keywords": [
+    "uv-parse",
+    "uvui",
+    "uv-ui",
+    "parse",
+    "富文本"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 21 - 0
uni_modules/uv-parse/readme.md

@@ -0,0 +1,21 @@
+## Parse 富文本解析器
+
+> **组件名:uv-parse**
+
+该组件一般用于富文本解析场景,比如解析文章内容,商品详情,带原生`HTML`标签的各类字符串等,此组件和`uni-app`官方的`rich-text`组件功能有重合之处,但是也有不同的地方。
+
+该插件只提供富文本的解析,该功能已经足够丰富。如果需要富文本的编辑,可使用`uniapp`官方提供的组件。
+
+# <a href="https://www.uvui.cn/components/parse.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 224 - 0
uni_modules/uv-parse/static/app-plus/uv-parse/js/handler.js

@@ -0,0 +1,224 @@
+'use strict'
+
+// 等待初始化完毕
+document.addEventListener('UniAppJSBridgeReady', () => {
+    document.body.onclick = function () {
+        return uni.postMessage({
+            data: {
+                action: 'onClick'
+            }
+        })
+    }
+
+    uni.postMessage({
+        data: {
+            action: 'onJSBridgeReady'
+        }
+    })
+})
+let options
+let medias = []
+/**
+ * @description 获取标签的所有属性
+ * @param {Element} ele
+ */
+
+function getAttrs(ele) {
+    const attrs = Object.create(null)
+
+    for (let i = ele.attributes.length; i--;) {
+        attrs[ele.attributes[i].name] = ele.attributes[i].value
+    }
+
+    return attrs
+}
+/**
+ * @description 图片加载出错
+ */
+
+function onImgError() {
+    if (options[1]) {
+        this.src = options[1]
+        this.onerror = null
+    } // 取消监听点击
+
+    this.onclick = null
+    this.ontouchstart = null
+    uni.postMessage({
+        data: {
+            action: 'onError',
+            source: 'img',
+            attrs: getAttrs(this)
+        }
+    })
+}
+/**
+ * @description 创建 dom 结构
+ * @param {object[]} nodes 节点数组
+ * @param {Element} parent 父节点
+ * @param {string} namespace 命名空间
+ */
+
+function createDom(nodes, parent, namespace) {
+    const _loop = function _loop(i) {
+        const node = nodes[i]
+        let ele = void 0
+
+        if (!node.type || node.type == 'node') {
+            let { name } = node // svg 需要设置 namespace
+
+            if (name == 'svg') namespace = 'http://www.w3.org/2000/svg'
+            if (name == 'html' || name == 'body') name = 'div' // 创建标签
+
+            if (!namespace) ele = document.createElement(name); else ele = document.createElementNS(namespace, name) // 设置属性
+
+            for (const item in node.attrs) {
+                ele.setAttribute(item, node.attrs[item])
+            } // 递归创建子节点
+
+            if (node.children) createDom(node.children, ele, namespace) // 处理图片
+
+            if (name == 'img') {
+                if (!ele.src && ele.getAttribute('data-src')) ele.src = ele.getAttribute('data-src')
+
+                if (!node.attrs.ignore) {
+                    // 监听图片点击事件
+                    ele.onclick = function (e) {
+                        e.stopPropagation()
+                        uni.postMessage({
+                            data: {
+                                action: 'onImgTap',
+                                attrs: getAttrs(this)
+                            }
+                        })
+                    }
+                }
+
+                if (options[2]) {
+                    image = new Image()
+                    image.src = ele.src
+                    ele.src = options[2]
+
+                    image.onload = function () {
+                        ele.src = this.src
+                    }
+
+                    image.onerror = function () {
+                        ele.onerror()
+                    }
+                }
+
+                ele.onerror = onImgError
+            } // 处理链接
+            else if (name == 'a') {
+                ele.addEventListener('click', function (e) {
+                    e.stopPropagation()
+                    e.preventDefault() // 阻止默认跳转
+
+                    const href = this.getAttribute('href')
+                    let offset
+                    if (href && href[0] == '#') offset = (document.getElementById(href.substr(1)) || {}).offsetTop
+                    uni.postMessage({
+                        data: {
+                            action: 'onLinkTap',
+                            attrs: getAttrs(this),
+                            offset
+                        }
+                    })
+                }, true)
+            } // 处理音视频
+            else if (name == 'video' || name == 'audio') {
+                medias.push(ele)
+
+                if (!node.attrs.autoplay) {
+                    if (!node.attrs.controls) ele.setAttribute('controls', 'true') // 空白图占位
+
+                    if (!node.attrs.poster) ele.setAttribute('poster', "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>")
+                }
+
+                if (options[3]) {
+                    ele.onplay = function () {
+                        for (let _i = 0; _i < medias.length; _i++) {
+                            if (medias[_i] != this) medias[_i].pause()
+                        }
+                    }
+                }
+
+                ele.onerror = function () {
+                    uni.postMessage({
+                        data: {
+                            action: 'onError',
+                            source: name,
+                            attrs: getAttrs(this)
+                        }
+                    })
+                }
+            } // 处理表格
+            else if (name == 'table' && options[4] && !ele.style.cssText.includes('inline')) {
+                const div = document.createElement('div')
+                div.style.overflow = 'auto'
+                div.appendChild(ele)
+                ele = div
+            } else if (name == 'svg') namespace = void 0
+        } else ele = document.createTextNode(node.text.replace(/&amp;/g, '&'))
+
+        parent.appendChild(ele)
+    }
+
+    for (let i = 0; i < nodes.length; i++) {
+        var image
+
+        _loop(i)
+    }
+} // 设置 html 内容
+
+window.setContent = function (nodes, opts, append) {
+    const ele = document.getElementById('content') // 背景颜色
+
+    if (opts[0]) document.body.bgColor = opts[0] // 长按复制
+
+    if (!opts[5]) ele.style.userSelect = 'none'
+
+    if (!append) {
+        ele.innerHTML = '' // 不追加则先清空
+
+        medias = []
+    }
+
+    options = opts
+    const fragment = document.createDocumentFragment()
+    createDom(nodes, fragment)
+    ele.appendChild(fragment) // 触发事件
+
+    let height = ele.scrollHeight
+    uni.postMessage({
+        data: {
+            action: 'onLoad',
+            height
+        }
+    })
+    clearInterval(window.timer)
+    let ready = false
+    window.timer = setInterval(() => {
+        if (ele.scrollHeight != height) {
+            height = ele.scrollHeight
+            uni.postMessage({
+                data: {
+                    action: 'onHeightChange',
+                    height
+                }
+            })
+        } else if (!ready) {
+            ready = true
+            uni.postMessage({
+                data: {
+                    action: 'onReady'
+                }
+            })
+        }
+    }, 350)
+} // 回收计时器
+
+window.onunload = function () {
+    clearInterval(window.timer)
+}

+ 19 - 0
uni_modules/uv-parse/static/app-plus/uv-parse/js/uni.webview.min.js

@@ -0,0 +1,19 @@
+!(function (e, n) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = n() : typeof define === 'function' && define.amd ? define(n) : (e = e || self).uni = n() }(this, (() => {
+    'use strict'
+
+    try { const e = {}; Object.defineProperty(e, 'passive', { get() { !0 } }), window.addEventListener('test-passive', null, e) } catch (e) {} const n = Object.prototype.hasOwnProperty; function t(e, t) { return n.call(e, t) } const i = []; const a = function (e, n) { const t = { options: { timestamp: +new Date() }, name: e, arg: n }; if (window.__dcloud_weex_postMessage || window.__dcloud_weex_) { if (e === 'postMessage') { const a = { data: [n] }; return window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessage(a) : window.__dcloud_weex_.postMessage(JSON.stringify(a)) } const o = { type: 'WEB_INVOKE_APPSERVICE', args: { data: t, webviewIds: i } }; window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessageToService(o) : window.__dcloud_weex_.postMessageToService(JSON.stringify(o)) } if (!window.plus) return window.parent.postMessage({ type: 'WEB_INVOKE_APPSERVICE', data: t, pageId: '' }, '*'); if (i.length === 0) { const r = plus.webview.currentWebview(); if (!r) throw new Error('plus.webview.currentWebview() is undefined'); const d = r.parent(); let s = ''; s = d ? d.id : r.id, i.push(s) } if (plus.webview.getWebviewById('__uniapp__service'))plus.webview.postMessageToUniNView({ type: 'WEB_INVOKE_APPSERVICE', args: { data: t, webviewIds: i } }, '__uniapp__service'); else { const w = JSON.stringify(t); plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat('WEB_INVOKE_APPSERVICE', '",').concat(w, ',').concat(JSON.stringify(i), ');')) } }; const o = {
+        navigateTo() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('navigateTo', { url: encodeURI(n) }) }, navigateBack() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.delta; a('navigateBack', { delta: parseInt(n) || 1 }) }, switchTab() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('switchTab', { url: encodeURI(n) }) }, reLaunch() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('reLaunch', { url: encodeURI(n) }) }, redirectTo() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('redirectTo', { url: encodeURI(n) }) }, getEnv(e) { window.plus ? e({ plus: !0 }) : e({ h5: !0 }) }, postMessage() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; a('postMessage', e.data || {}) }
+    }; const r = /uni-app/i.test(navigator.userAgent); const d = /Html5Plus/i.test(navigator.userAgent); const s = /complete|loaded|interactive/; const w = window.my && navigator.userAgent.indexOf('AlipayClient') > -1; const u = window.swan && window.swan.webView && /swan/i.test(navigator.userAgent); const c = window.qq && window.qq.miniProgram && /QQ/i.test(navigator.userAgent) && /miniProgram/i.test(navigator.userAgent); const g = window.tt && window.tt.miniProgram && /toutiaomicroapp/i.test(navigator.userAgent); const v = window.wx && window.wx.miniProgram && /micromessenger/i.test(navigator.userAgent) && /miniProgram/i.test(navigator.userAgent); const p = window.qa && /quickapp/i.test(navigator.userAgent); for (var l, _ = function () { window.UniAppJSBridge = !0, document.dispatchEvent(new CustomEvent('UniAppJSBridgeReady', { bubbles: !0, cancelable: !0 })) }, f = [function (e) { if (r || d) return window.__dcloud_weex_postMessage || window.__dcloud_weex_ ? document.addEventListener('DOMContentLoaded', e) : window.plus && s.test(document.readyState) ? setTimeout(e, 0) : document.addEventListener('plusready', e), o }, function (e) { if (v) return window.WeixinJSBridge && window.WeixinJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('WeixinJSBridgeReady', e), window.wx.miniProgram }, function (e) { if (c) return window.QQJSBridge && window.QQJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('QQJSBridgeReady', e), window.qq.miniProgram }, function (e) {
+            if (w) {
+                document.addEventListener('DOMContentLoaded', e); const n = window.my; return {
+                    navigateTo: n.navigateTo, navigateBack: n.navigateBack, switchTab: n.switchTab, reLaunch: n.reLaunch, redirectTo: n.redirectTo, postMessage: n.postMessage, getEnv: n.getEnv
+                }
+            }
+        }, function (e) { if (u) return document.addEventListener('DOMContentLoaded', e), window.swan.webView }, function (e) { if (g) return document.addEventListener('DOMContentLoaded', e), window.tt.miniProgram }, function (e) {
+            if (p) {
+                window.QaJSBridge && window.QaJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('QaJSBridgeReady', e); const n = window.qa; return {
+                    navigateTo: n.navigateTo, navigateBack: n.navigateBack, switchTab: n.switchTab, reLaunch: n.reLaunch, redirectTo: n.redirectTo, postMessage: n.postMessage, getEnv: n.getEnv
+                }
+            }
+        }, function (e) { return document.addEventListener('DOMContentLoaded', e), o }], m = 0; m < f.length && !(l = f[m](_)); m++);l || (l = {}); const E = typeof uni !== 'undefined' ? uni : {}; if (!E.navigateTo) for (const b in l)t(l, b) && (E[b] = l[b]); return E.webView = l, E
+})))

+ 1 - 0
uni_modules/uv-parse/static/app-plus/uv-parse/local.html

@@ -0,0 +1 @@
+<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}@keyframes show{0%{opacity:0}100%{opacity:1}}</style></head><body><div id="content"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body>

+ 1 - 1
uni_modules/uv-swiper/components/uv-swiper/uv-swiper.vue

@@ -63,7 +63,7 @@
 						:src="getSource(item)"
 						:poster="getPoster(item)"
 						:title="showTitle && $uv.test.object(item) && item.title ? item.title : ''"
-						:style="{
+		 				:style="{ 
 							height: $uv.addUnit(height)
 						}"
 						controls