Quellcode durchsuchen

feat: 钱包、添加银行卡、银行卡解绑、收入明细、申述文件上传、消息

chenjj vor 1 Monat
Ursprung
Commit
57503b63f8

+ 9 - 0
api/volunteer.js

@@ -62,4 +62,13 @@ export function getVolunteerFinishSecondOrder(data) {
         method: 'post',
         data: data
     })
+}
+
+
+//获取志愿者账户详细信息
+export function getVolunteerAccount(volunteerAccountId) {
+    return request({
+        url: `/core/volunteer/account/${volunteerAccountId}`,
+        method: 'get',
+    })
 }

+ 250 - 0
components/bank-item/bank-item.vue

@@ -0,0 +1,250 @@
+<template>
+	<view class="bank-item" :style="bankThem">
+		<!-- #ifndef MP-WEIXIN -->
+		<canvas v-if="showCanvas" class="bank-icon" :id="uuid" :canvas-id="uuid" />
+		<!-- #endif -->
+		<!-- #ifdef MP-WEIXIN -->
+		<canvas v-if="showCanvas" class="bank-icon" id="bankIcon" canvas-id="bankIcon" />
+		<!-- #endif -->
+		<view class="bank-head">
+			<image :src="image"></image>
+			<view class="bank-info">
+				<text class="bank-name">{{bankName}}</text>
+				<text class="card-type">{{cardType}}</text>
+			</view>
+		</view>
+		<view class="card-code">
+			<text class="omit">****</text>
+			<text class="omit">****</text>
+			<text class="omit">****</text>
+			<text>{{endNumber}}</text>
+		</view>
+		<view class="bank-watermark" :style="waterMark" />
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'bankItem',
+		props: {
+			bankCode: { type: String, required: true},
+			bankName: { type: String, required: true},
+			cardType: { type: String, default: '储蓄卡' },
+			cardCode: { type: String, required: true}
+		},
+		computed: {
+			waterMark() {
+				return `background-image: url(${this.image});`
+			},
+			endNumber() {
+				let length = this.cardCode.length;
+				return this.cardCode.substr(length - 4, length);
+			}
+		},
+		data() {
+			// #ifndef MP-WEIXIN
+			const buildUuid = () => {
+				return 'bank_' + parseInt(Math.random() * 100000000);
+			};
+			// #endif
+			return {
+				bankThem: '',
+				image: '',
+				showCanvas: true,
+				// #ifdef MP-WEIXIN
+				uuid: 'bankIcon',
+				// #endif
+				// #ifndef MP-WEIXIN
+				uuid: buildUuid()
+				// #endif
+			};
+		},
+		methods: {
+			async buildItem() {
+				this.bankThem = uni.getStorageSync(`BANK_${this.bankCode}`);
+				this.image = await this.getBankLogo();
+				await this.getThemColor();
+				this.showCanvas = false;
+			},
+			async getThemColor() {
+				if(this.bankThem != null && this.bankThem != '') return;
+				let bgSize = uni.upx2px(100);
+				let iconSize = uni.upx2px(72);
+				this.iconContext = uni.createCanvasContext(this.uuid, this);
+				this.iconContext.width = bgSize;
+				this.iconContext.height = bgSize;
+				this.iconContext.fillStyle = '#FFFFFF';
+				this.iconContext.beginPath();
+				let bgRadio = bgSize / 2;
+				this.iconContext.arc(bgRadio, bgRadio, bgRadio - 1 , 0, 2 * Math.PI, 0, true);
+				this.iconContext.closePath();
+				this.iconContext.fill();
+				let iconRadio =  bgSize / 2 - iconSize / 2;
+				this.iconContext.drawImage(this.image, iconRadio, iconRadio, iconSize, iconSize);
+				await this.draw(this.iconContext);
+				let imageData = await this.getImageData(iconRadio, iconSize);
+				this.parsingImageData(imageData);
+			},
+			parsingImageData(imageData) {
+				let statistics = {};
+				for (let i = 0, length = imageData.length; i < length; i += 4) {
+					let r = imageData[i];
+					let g = imageData[i + 1];
+					let b = imageData[i + 2];
+					if((r + g + b) < 400) {
+						let rgb = [r, g, b];
+						let key = rgb.join(', ');
+						statistics[key] = statistics[key] == null ? 1 : statistics[key] + 1;
+					}	
+				}
+				let maxKey = '';
+				Object.keys(statistics).forEach(key => {
+					if (maxKey === '') {
+						maxKey = key;
+					} else {
+						maxKey = statistics[maxKey] > statistics[key] ? maxKey : key;
+					}
+				});
+				let beginColor =  maxKey.split(', ').map((item, index) => {
+					item = parseInt(item);
+					if(index > 1) return item;
+					let newColor = item + 50;
+					return newColor > 255 ? 255 : newColor;
+				}).join(', ');
+				this.bankThem = `background-image: linear-gradient(45deg, rgba(${beginColor}, 1), rgba(${maxKey}, 1));`;
+				uni.setStorageSync(`BANK_${this.bankCode}`, this.bankThem);
+			},
+			getImageData(radio, size) {
+				return new Promise((resolve, reject) => {
+					uni.canvasGetImageData({
+							canvasId: this.uuid,
+							x: radio,
+							y: radio,
+							width: size,
+							height: size,
+							success(res) {
+								resolve(res.data);
+							},
+							fail(err) {
+								console.log(err);
+								reject();
+							}
+						},
+						this
+					);
+				});
+			},
+			getBankLogo() {
+				return new Promise((resolve, reject) => {
+					uni.downloadFile({
+						url: `https://banklogo.yfb.now.sh/resource/logo/${this.bankCode}.png`,
+						success(res) {
+							resolve(res.tempFilePath);
+						},
+						fail(err) {
+							console.log(err);
+							reject();
+						}
+					});
+				});
+			},
+			draw(context, reserve = false) {
+				return new Promise((resolve) => {
+					context.draw(reserve, () => {
+						resolve();
+					});
+				});
+			}
+		},
+		created() {
+			this.$nextTick(() => {
+				this.buildItem();
+			});
+		}
+	};
+</script>
+
+<style>
+	.omit {
+		font-size: 48rpx;
+		margin-right: 30rpx;
+	}
+	.card-code {
+		margin-top: 15rpx;
+		display: flex;
+		justify-content: flex-end;
+		color: #FFFFFF;
+		font-size: 38rpx;
+		
+	}
+	.flex-1 {
+		flex: 1;
+	}
+	.card-type {
+		font-size: 24rpx;
+		color: #F1F1F1;
+	}
+	.bank-name {
+		font-size: 32rpx;
+		color: #FFFFFF;
+	}
+	.bank-info {
+		display: flex;
+		flex-direction: column;
+		margin-left: 30rpx;
+	}
+	.bank-head {
+		display: flex;
+		flex: 1;
+		align-items: center;
+	}
+	.bank-head image {
+		width: 100rpx;
+		height: 100rpx;
+		padding: 15rpx;
+		background-color: #FFFFFF;
+		border-radius: 50%;
+		overflow: hidden;
+	}
+	.bank-icon {
+		position: absolute;
+		top: 20rpx;
+		left: 20rpx;
+		width: 100rpx;
+		height: 100rpx;
+	}
+
+	.bank-watermark {
+		position: absolute;
+		right: -184rpx;
+		bottom: 0rpx;
+		width: 144rpx;
+		height: 90rpx;
+		background-repeat: no-repeat;
+		filter: drop-shadow(-204rpx 0rpx 0rpx #fff);
+		opacity: 0.1;
+	}
+
+	.bank-item {
+		position: relative;
+		flex: 1;
+		padding: 14px 20px;
+		margin-bottom: 12px;
+	}
+	.bank-item:after {
+		content: "";
+		display: block;
+		background: inherit;
+		filter: blur(10rpx);
+		position: absolute;
+		width: 100%;
+		height: 100%;
+		top: 10rpx;
+		left: 10rpx;
+		z-index: -1;
+		opacity: 0.4;
+		transform-origin: 0 0;
+		border-radius: inherit;
+		transform: scale(1, 1);
+	}
+</style>

+ 4 - 7
components/updata-imgs/index.vue

@@ -1,15 +1,12 @@
 <template>
     <view class="updata-img">
         <view class="updata-title-box">
-            <view class="updata-title-title font-title ">{{ data.title }} <span style="color: #f56c6c;" v-if="data.required">*</span> </view>
+            <view class="updata-title-title font-title ">{{ data.title }} <span style="color: #f56c6c;"
+                    v-if="data.required">*</span> </view>
             <view class="updata-title-text font-text ">{{ data.text }}</view>
         </view>
         <view class="updata-img-box" @click="updataFile">
-
             <image :src="img" style="width: 117px;height: 75px;" />
-            <!-- <up-upload :fileList="fileList" @afterRead="afterRead"  name="1" multiple :maxCount="1">
-                <image :src="img"  style="width: 117px;height: 75px;" />
-            </up-upload> -->
         </view>
     </view>
 </template>
@@ -59,8 +56,8 @@ function updataFile() {
                     },
                     success(res) {
                         const data = JSON.parse(res.data);
-                        file.value = data.url;                        
-                        emit('onSubmit',{key: props.data.key,url: data.url})
+                        file.value = data.url;
+                        emit('onSubmit', { key: props.data.key, url: data.url })
                         fs.readFile({
                             filePath: tempFilePath,
                             encoding: 'base64', // 指定编码格式 

+ 1 - 1
config.js

@@ -3,7 +3,7 @@ const config = {
 	// baseUrl: 'https://vue.ruoyi.vip/prod-api',
 	//cloud后台网关地址
 	// baseUrl: 'http://192.168.10.3:8080',
-	baseUrl: 'http://192.168.100.85:9527',
+	baseUrl: 'http://192.168.100.89:9527',
 	// 应用信息
 	appInfo: {
 		// 应用名称

+ 35 - 0
pages.json

@@ -93,6 +93,13 @@
 				"navigationBarTitleText": "订单处理"
 			}
 		},
+		{
+			"path" : "pages/myCenter/wallet",
+			"style" : 
+			{
+				"navigationBarTitleText" : "钱包"
+			}
+		},
 		{
 			"path" : "pages/myCenter/withdrawal",
 			"style" : 
@@ -100,6 +107,27 @@
 				"navigationBarTitleText" : "提现"
 			}
 		},
+		{
+			"path" : "pages/myCenter/income",
+			"style" : 
+			{
+				"navigationBarTitleText" : "收入明细"
+			}
+		},
+		{
+			"path" : "pages/myCenter/addBankCard",
+			"style" : 
+			{
+				"navigationBarTitleText" : "添加银行卡"
+			}
+		},
+		{
+			"path" : "pages/myCenter/bankCardDetails",
+			"style" : 
+			{
+				"navigationBarTitleText" : "银行卡详情"
+			}
+		},
 		{
 			"path" : "pages/myCenter/bad",
 			"style" : 
@@ -113,6 +141,13 @@
 			{
 				"navigationBarTitleText" : "申诉"
 			}
+		},
+		{
+			"path" : "pages/talk/pages/index/index",
+			"style" : 
+			{
+				"navigationBarTitleText" : "消息"
+			}
 		}
 	    
         // ,{

+ 7 - 2
pages/Client/new_file.vue

@@ -104,7 +104,7 @@
 	 //志愿者
 	 const serviceList2 = ref([{
 	 		icon: '/static/img/统一知识平台-营运@1x.png',
-	 		name: '家庭辅导注册',
+	 		name: '家庭教育注册',
 			key:2,
 	 	},
 	 	{
@@ -114,7 +114,7 @@
 	 	},
 	 	{
 	 		icon: '/static/img/清空.png',
-	 		name: '家庭助注册',
+	 		name: '家庭助注册',
 			key:3,
 	 	},
 	 	{
@@ -132,6 +132,11 @@
 	 		name: '家务帮手注册',
 			key:6
 	 	},
+		{
+	 		icon: '/static/img/报事报修@6x.png',
+	 		name: '咨询与服务注册',
+			key:8
+	 	},
 	 	{
 	 		icon: '/static/img/清空.png',
 	 		name: '排班管理',

+ 7 - 3
pages/classify.vue

@@ -86,10 +86,10 @@ async function getList() {
  * 2:沟通
  * 3:上传照片
  */
-function btnClick(row,type) {
+function btnClick(row, type) {
 	console.log('btnClick', type, row);
 	if (type === 1) {
-		
+
 		uni.navigateTo({
 			url: `/pages/order/handle?orderId=${row.secondOrderId}`
 		});
@@ -99,7 +99,11 @@ function btnClick(row,type) {
 
 		return
 	}
-	
+
+	uni.navigateTo({
+		url: `/pages/talk/pages/index/index?orderId=${row.secondOrderId}`
+	});
+
 }
 provide('onClick', btnClick);
 

+ 5 - 5
pages/index.vue

@@ -20,15 +20,15 @@
 			<Client></Client>
 
 			<!-- 瀑布流 -->
-			<volunteerSide ></volunteerSide>
+			<volunteerSide></volunteerSide>
 
 			<view v-if="userType == 2">
 				<view class="font-title index-title">月度排名</view>
-				<List :data="data" @refresh="getList" v-if="data.length > 0" :type="'ranking'"/>
+				<List :data="data" @refresh="getList" v-if="data.length > 0" :type="'ranking'" />
 				<view v-else class="empty-null">
 					<img src="/static/empty/订单为空.png" alt="">
 				</view>
-				</view>
+			</view>
 		</view>
 	</view>
 
@@ -36,8 +36,8 @@
 </template>
 
 <script setup>
-	import {
-		ref,
+import {
+	ref,
 		onMounted
 	} from 'vue';
 	import {

+ 38 - 8
pages/mine.vue

@@ -19,6 +19,7 @@
 								<up-icon :customStyle="{ paddingTop: 20 + 'rpx' }" :name="listItem.iconName"
 									:size="22"></up-icon>
 								<text class="grid-text">{{ listItem.name }}</text>
+
 							</view>
 						</up-grid-item>
 					</up-grid>
@@ -31,6 +32,8 @@
 					<view class="price-data flex-center">
 						<up-count-to :startVal="0" :endVal="data[item.key]" :decimals="item.decimals"></up-count-to>
 					</view>
+					<text class="grid-min-price" v-if="item.key === 'balance'">待入账余额:{{ data[item.key] }}</text>
+
 				</view>
 			</view>
 
@@ -40,6 +43,7 @@
 						<view class="grid-box">
 							<text class="grid-text">{{ listItem.name }}</text>
 							<up-count-to :startVal="0" :endVal="data[listItem.key]"></up-count-to>
+
 						</view>
 					</up-grid-item>
 				</up-grid>
@@ -64,26 +68,28 @@
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { onMounted, ref } from 'vue';
+import { getVolunteerAccount } from '@/api/volunteer.js'
+
 const serviceList = ref([
 	{
 		name: '待服务',
 		iconName: 'clock',
 		page: '/pages/classify',
-		value:1
+		value: 1
 	},
 
 	{
 		name: '进行中',
 		iconName: 'car',
 		page: '/pages/classify',
-		value:2
+		value: 2
 	},
 	{
 		name: '已完成',
 		iconName: 'car-fill',
 		page: '/pages/classify',
-		value:3
+		value: 3
 	},
 	{
 		name: '差评申述',
@@ -93,7 +99,7 @@ const serviceList = ref([
 	{
 		name: '钱包',
 		iconName: 'rmb-circle',
-		page: '/pages/myCenter/withdrawal'
+		page: '/pages/myCenter/wallet'
 	},
 	{
 		name: '帮助与客服',
@@ -146,20 +152,32 @@ const onClick = (record) => {
 	console.log('record', record, record.page);
 	if (record.page && record.value) {
 		const app = getApp();
-		app.globalData.switchTabParams  = { tabKey: record.value };
+		app.globalData.switchTabParams = { tabKey: record.value };
 		// JS跳转 
 		uni.switchTab({
 			url: record.page
 		});
 		return;
 	}
-	if(record.page){
+	if (record.page) {
 		uni.navigateTo({
 			url: record.page
 		});
 	}
 }
 
+const getDetails = async() => {
+	try {
+		// const res = await getVolunteerAccount();
+		// console.log('res',res);
+
+	} catch (error) {
+		console.log('error',error);
+		
+	}
+}
+
+onMounted(getDetails)
 
 </script>
 
@@ -236,6 +254,7 @@ const onClick = (record) => {
 		/* #endif */
 	}
 
+
 	.grid-box {
 		display: flex;
 		align-items: center;
@@ -257,7 +276,10 @@ const onClick = (record) => {
 		.price-item {
 			width: 50%;
 			padding: 12px;
-
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			justify-content: center;
 			.price-name {
 				font-size: 16px;
 				font-weight: 700;
@@ -272,6 +294,14 @@ const onClick = (record) => {
 				line-height: 23.44px;
 				color: rgba(51, 51, 51, 1);
 			}
+
+			.grid-min-price {
+				font-size: 12px;
+				font-weight: 500;
+				line-height: 17.38px;
+				color: rgba(153, 153, 153, 1);
+				margin-top: 4px;
+			}
 		}
 
 		.price-item:first-child {

+ 207 - 0
pages/myCenter/addBankCard.vue

@@ -0,0 +1,207 @@
+<template>
+    <view class="bankCard-main">
+        <view class="card-box ">
+            <view class="font-title">*请填写您名下有效的储蓄卡</view>
+            <CustForm :column="column" ref="cust_form_ref" />
+            <up-button type="primary" text="确定" @click="onSubmit"></up-button>
+        </view>
+       
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import CustForm from "@/components/cust-form/index.vue";
+
+const cust_form_ref = ref(null);
+const rules = {
+    name: [
+        {
+            type: 'string',
+            required: true,
+            message: '请填写姓名',
+            trigger: ['blur', 'change']
+        },
+    ],
+}
+const column = [
+    {
+        label: "户名",
+        key: "name",
+        type: "input",
+        rules: rules.name,
+        required:true
+    },
+    {
+        label: "银行",
+        key: "name",
+        type: "input",
+        rules: rules.name,
+        required:true
+    },
+    {
+        label: "卡号",
+        key: "name",
+        type: "input",
+        rules: rules.name,
+        required:true
+    },
+     {
+        label: "预留手机号",
+        key: "phonenumber",
+        type: "phone-code",
+    },
+]
+
+function onSubmit() {
+	try {
+		// 校验表单并获取数据
+		cust_form_ref.value.onSubmit().then(async (res) => {			
+			console.log('提交',res);
+
+			// return;
+			// 提交接口,注册人员
+			// const submit_res = await add(parmas);
+			// if(submit_res.code == 200){
+			// 	uni.showToast({
+			// 		title: '注册成功',
+			// 		icon: 'none'
+			// 	})
+			// 	uni.navigateBack();
+			// 	return;
+			// }
+			// uni.showToast({
+			// 	title: res.msg,
+			// 	icon: 'none'
+			// })
+			// console.log('==submit_res====>', submit_res);
+		})
+
+
+	} catch (error) {
+		console.log('error', error);
+
+	} finally {
+
+	}
+
+}
+
+
+
+</script>
+
+<style lang="scss" scoped>
+.card-box {
+    border-radius: 8px;
+    background: rgba(255, 255, 255, 1);
+    padding: 20px;
+    margin-bottom: 12px;
+}
+.bank-box {
+    border-radius: 8px;
+    margin-bottom: 12px;
+}
+
+.bankCard-main {
+    position: fixed;
+    top: 0px;
+    left: 0px;
+    right: 0px;
+    bottom: 0px;
+    background: rgba(245, 245, 245, 1);
+    padding: 12px;
+    overflow: auto;
+
+    .bankCard-hi-title {
+        font-size: 20px;
+        font-weight: 700;
+        line-height: 28.96px;
+        color: rgba(51, 51, 51, 1);
+    }
+
+    .bankCard-hi-text {
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 20.27px;
+        color: rgba(153, 153, 153, 1);
+    }
+
+    .bankCard-active {
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 20.27px;
+        color: rgba(255, 87, 4, 1);
+    }
+
+    .bankCard-hi {
+        margin-bottom: 20px;
+    }
+
+    .bankCard-box-title {
+        font-size: 12px;
+        font-weight: 500;
+        line-height: 17.38px;
+        color: rgba(199, 199, 199, 1);
+        margin-bottom: 8px;
+    }
+
+    .bankCard-box-price {
+        font-size: 30px;
+        font-weight: 500;
+        line-height: 35.16px;
+        color: rgba(252, 228, 187, 1);
+    }
+    .bankCard-box-price2 {
+        font-size: 24px;
+        font-weight: 400;
+        line-height: 28.13px;
+        color: rgba(252, 228, 187, 1);
+    }
+
+    .bankCard-box {
+        border-radius: 12px;
+        background: linear-gradient(131.81deg, rgba(65, 65, 95, 1) 0%, rgba(45, 48, 74, 1) 100%);
+        margin-bottom: 14px;
+        .bankCard-box-top {
+            display: flex;
+            align-content: center;
+            justify-content: space-between;
+
+            .bankCard-box-btn1 {
+                width: 78px;
+                height: 30px;
+                opacity: 1;
+                border-radius: 4px;
+                background: linear-gradient(222.81deg, rgba(255, 227, 194, 1) 0%, rgba(255, 226, 192, 1) 0%, rgba(255, 225, 189, 1) 0%, rgba(251, 204, 147, 1) 100%);
+                margin-bottom: 14px;
+                font-size: 16px;
+                font-weight: 700;
+                line-height: 23.17px;
+                color: rgba(50, 52, 80, 1);
+            }
+
+            .bankCard-box-btn2 {
+                width: 78px;
+                height: 30px;
+                opacity: 1;
+                border-radius: 4px;
+                border: 1px solid rgba(255, 218, 172, 1);
+                font-size: 16px;
+                font-weight: 500;
+                letter-spacing: 0px;
+                line-height: 23.17px;
+                color: rgba(255, 218, 172, 1);
+            }
+        }
+        .bankCard-box-bottom{
+            display: flex;
+            align-items: center;
+            justify-content: space-around;
+        }
+
+
+    }
+
+}
+</style>

+ 43 - 2
pages/myCenter/appeal.vue

@@ -31,7 +31,17 @@
                 <view class="font-title">申诉内容</view>
                 <up-textarea v-model="textareaValue" placeholder="请输入申述内容" count ></up-textarea>
             </view>
-
+            <view class="appeal-text-box">
+                <view class="upload-box">
+				<view class="upload-img-item" v-for="(item, index) in fileList" :key="item.url">
+					<view class="delete-icon" @click="deletePic(index)"><up-icon name="close-circle-fill"
+							color="#f64a1f" size="18"></up-icon></view>
+					<img class="upload-img" :src="item.url" :alt="item.fileName" srcset="">
+				</view>
+				<img src="/static/img/upload.png" alt="" class="upload-img" @click="uploadClick('img')"
+					v-if="fileList.length < 10">
+			</view>
+            </view>
     
             
 
@@ -45,6 +55,7 @@
 
 <script setup>
 import { ref } from 'vue';
+import { wxUploadFile } from '@/utils/wxRequest.js'
 
 const list = [
     {
@@ -56,6 +67,7 @@ const list = [
 
     }
 ]
+const fileList = ref([]);
 
 const textareaValue = ref('');
 
@@ -67,7 +79,15 @@ const onAppeal = () => {
    console.log('提交申述',textareaValue.value);
    
 }
-
+// 删除图片
+const deletePic = (index) => {
+	fileList.value.splice(index, 1);
+};
+const uploadClick = async (type) => {
+	const res = await wxUploadFile(type);
+	fileList.value = [...fileList.value, ...res];
+	console.log('xxxxres', res, fileList.value);
+}
 </script>
 
 <style lang="scss" scoped>
@@ -180,5 +200,26 @@ const onAppeal = () => {
             }
         }
     }
+    .upload-img {
+		height: 68px;
+		width: 68px;
+		margin-right: 12px;
+		margin-bottom: 12px;
+	}
+    .upload-box {
+		display: flex;
+		flex-wrap: wrap;
+
+		.upload-img-item {
+			position: relative;
+
+			.delete-icon {
+				position: absolute;
+				top: -7px;
+				right: 7px;
+				z-index: 1;
+			}
+		}
+	}
 }
 </style>

+ 69 - 0
pages/myCenter/bankCardDetails.vue

@@ -0,0 +1,69 @@
+<template>
+    <view class="wallet-main">
+        <view class="bank-box">
+            <view v-for="item in bankList" :key="item.cardCode"
+                :style="`background: ${item.color};border-radius: 8px;`">
+                <BankItem bankCode="ABC" bankName="中国农业银行" cardType="储蓄卡" cardCode="5106365986893" />
+            </view>
+
+            <up-button type="primary" text="解除绑定" @click="onSubmit"></up-button>
+        </view>
+
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import BankItem from '../../components/bank-item/bank-item.vue';
+const bankList = ref([
+    {
+        bankCode: 'ABC',
+        bankName: '中国农业银行',
+        cardType: '储蓄卡',
+        cardCode: '5106365986893',
+        color: 'red'
+    },
+])
+
+const onSubmit = () => {
+    uni.showModal({
+        title: '提示',
+        content: '是否要解绑银行卡',
+        success(res) {
+            console.log('11');
+
+        }
+    });
+}
+
+
+
+</script>
+
+<style lang="scss" scoped>
+.card-box {
+    border-radius: 8px;
+    background: rgba(255, 255, 255, 1);
+    padding: 20px;
+    margin-bottom: 12px;
+}
+
+.bank-box {
+    border-radius: 8px;
+    margin-bottom: 12px;
+}
+
+.wallet-main {
+    position: fixed;
+    top: 0px;
+    left: 0px;
+    right: 0px;
+    bottom: 0px;
+    background: rgba(245, 245, 245, 1);
+    padding: 12px;
+    overflow: auto;
+
+
+
+}
+</style>

+ 173 - 0
pages/myCenter/income.vue

@@ -0,0 +1,173 @@
+<template>
+    <view class="income-main">
+        <view class="income-header card-box">
+            <text class="income-title" @click="onShow">
+                2025年4月
+            </text>
+            <view class="income-header-right">
+                <text>收入:1212</text>
+                <text>支出:233</text>
+            </view>
+        </view>
+
+        <view class="card-box icome-item" v-for="item in list" :key="item.code">
+            <view class="card-left">
+                <img :src="baseUrl" alt="" class="income-img">
+                <view class="card-left-text">
+                    <view class="card-left-name">{{ item.name }}</view>
+                    <view class="card-left-date">{{ item.date }}</view>
+                </view>
+            </view>
+            <view class="card-rigth">
+                {{ item.price }}
+            </view>
+
+        </view>
+
+
+        <up-picker :show="show" ref="uPickerRef" :columns="columns" @confirm="confirm"
+            @change="changeHandler"></up-picker>
+    </view>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import config from '@/config'
+const baseUrl = config.baseUrl
+
+const list = ref([
+    {
+        name: '交易提现-到银行卡',
+        date: '2022-03-20 20:00',
+        code: '88888888888888888',
+        price: '+1212'
+    },
+    {
+        name: '交易提现-到银行卡',
+        date: '2022-03-20 20:00',
+        code: '343434343434',
+        price: '-1212'
+    }
+])
+
+const show = ref(false);
+const columns = reactive([
+    ['中国', '美国'],
+    ['深圳', '厦门', '上海', '拉萨']
+]);
+const columnData = reactive([
+    ['深圳', '厦门', '上海', '拉萨'],
+    ['得州', '华盛顿', '纽约', '阿拉斯加']
+]);
+
+const uPickerRef = ref(null)
+const changeHandler = (e) => {
+    const {
+        columnIndex,
+        value,
+        values,
+        index,
+    } = e;
+
+    if (columnIndex === 0) {
+        uPickerRef.value.setColumnValues(1, columnData[index]);
+    }
+};
+
+const onShow = () => {
+    show.value = true;
+};
+
+const confirm = (e) => {
+    console.log('confirm', e);
+    show.value = false;
+};
+
+</script>
+
+<style lang="scss" scoped>
+.card-box {
+    border-radius: 8px;
+    background: rgba(255, 255, 255, 1);
+    padding: 12px;
+    margin-bottom: 12px;
+}
+
+.income-main {
+    position: fixed;
+    top: 0px;
+    left: 0px;
+    right: 0px;
+    bottom: 0px;
+    background: rgba(245, 245, 245, 1);
+    padding: 12px;
+    overflow: auto;
+
+    .income-title {
+        font-size: 16px;
+        font-weight: 500;
+        line-height: 23.17px;
+        color: rgba(0, 0, 0, 1);
+    }
+
+    .income-header {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+
+
+
+        .income-header-right {
+            font-size: 14px;
+            font-weight: 500;
+            line-height: 20.27px;
+            color: rgba(0, 0, 0, 1);
+            display: flex;
+            align-items: flex-start;
+            flex-direction: column;
+            justify-content: center;
+
+        }
+
+    }
+
+}
+
+.icome-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .card-left {
+        display: flex;
+        align-items: center;
+        .income-img {
+            width: 40px;
+            height: 40px;
+        }
+        .card-left-text {
+            margin-left: 12px;
+            .card-left-name {
+                font-size: 16px;
+            font-weight: 700;
+            line-height: 23.17px;
+            color: rgba(0, 0, 0, 1);
+            margin-bottom: 6px;
+            }
+            .card-left-date {
+                font-size: 14px;
+                font-weight: 500;
+                line-height: 16.41px;
+                color: rgba(153, 153, 153, 1);
+            }
+            
+        }
+        .card-rigth {
+            font-size: 20px;
+            font-weight: 700;
+            line-height: 23.44px;
+            color: rgba(51, 51, 51, 1);
+        }
+    }
+
+}
+</style>

+ 203 - 0
pages/myCenter/wallet.vue

@@ -0,0 +1,203 @@
+<template>
+    <view class="wallet-main">
+        <view class="wallet-hi">
+            <view class="wallet-hi-title">Hi,您好!</view>
+            <text class="wallet-hi-text">为了您更好地使用钱包功能, <text class="wallet-active" @click="onAddBankCard" >添加银行卡> </text> </text>
+        </view>
+        <view class="wallet-box card-box ">
+            <view class="wallet-box-top">
+                <view class="flex-center-column">
+                    <text class="wallet-box-title">钱包总额(元)</text>
+                    <view class="wallet-box-price">2000</view>
+                </view>
+                <view>
+                    <view class="wallet-box-btn1 flex-center" @click="onWithdrawal">提现</view>
+                    <view class="wallet-box-btn2 flex-center" @click="onIncome">收入明细</view>
+                </view>
+            </view>
+            <up-divider></up-divider>
+            <view class="wallet-box-bottom">
+                <view class="flex-center-column">
+                    <text class="wallet-box-title">可提现(元)</text>
+                    <view class="wallet-box-price2">2000</view>
+                </view>
+                <view class="flex-center-column">
+                    <text class="wallet-box-title">待入账(元)</text>
+                    <view class="wallet-box-price2">2000</view>
+                </view>
+                <view class="flex-center-column">
+                    <text class="wallet-box-title">银行卡(张)</text>
+                    <view class="wallet-box-price2">2</view>
+                </view>
+            </view>
+        </view>
+
+        <view class="bank-box">
+            <view v-for="item in bankList" :key="item.cardCode"  :style="`background: ${item.color};border-radius: 8px;`" @click="onBankCardDetails(item)" >
+            <BankItem bankCode="ABC" bankName="中国农业银行" cardType="储蓄卡" cardCode="5106365986893"/>
+        </view>
+        </view>
+       
+    </view>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import BankItem from '../../components/bank-item/bank-item.vue';
+const bankList = ref([
+    {
+        bankCode: 'ABC',
+        bankName: '中国农业银行',
+        cardType: '储蓄卡',
+        cardCode: '5106365986893',
+        color: 'red'
+    },
+    {
+        bankCode: 'ABC',
+        bankName: '中国农业银行',
+        cardType: '储蓄卡',
+        cardCode: '5106365986893',
+        color: 'red'
+    },
+])
+
+const onWithdrawal = () => {
+    uni.navigateTo({
+        url: '/pages/myCenter/withdrawal'
+    })
+}
+
+const onIncome = () => {
+    uni.navigateTo({
+        url: '/pages/myCenter/income'
+    })
+}
+
+const onAddBankCard = () => {
+    uni.navigateTo({
+        url: '/pages/myCenter/addBankCard'
+    })
+}
+
+const onBankCardDetails = (record) => {
+    uni.navigateTo({
+        url: '/pages/myCenter/bankCardDetails'
+    })
+}
+
+</script>
+
+<style lang="scss" scoped>
+.card-box {
+    border-radius: 8px;
+    background: rgba(255, 255, 255, 1);
+    padding: 20px;
+    margin-bottom: 12px;
+}
+.bank-box {
+    border-radius: 8px;
+    margin-bottom: 12px;
+}
+
+.wallet-main {
+    position: fixed;
+    top: 0px;
+    left: 0px;
+    right: 0px;
+    bottom: 0px;
+    background: rgba(245, 245, 245, 1);
+    padding: 12px;
+    overflow: auto;
+
+    .wallet-hi-title {
+        font-size: 20px;
+        font-weight: 700;
+        line-height: 28.96px;
+        color: rgba(51, 51, 51, 1);
+    }
+
+    .wallet-hi-text {
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 20.27px;
+        color: rgba(153, 153, 153, 1);
+    }
+
+    .wallet-active {
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 20.27px;
+        color: rgba(255, 87, 4, 1);
+    }
+
+    .wallet-hi {
+        margin-bottom: 20px;
+    }
+
+    .wallet-box-title {
+        font-size: 12px;
+        font-weight: 500;
+        line-height: 17.38px;
+        color: rgba(199, 199, 199, 1);
+        margin-bottom: 8px;
+    }
+
+    .wallet-box-price {
+        font-size: 30px;
+        font-weight: 500;
+        line-height: 35.16px;
+        color: rgba(252, 228, 187, 1);
+    }
+    .wallet-box-price2 {
+        font-size: 24px;
+        font-weight: 400;
+        line-height: 28.13px;
+        color: rgba(252, 228, 187, 1);
+    }
+
+    .wallet-box {
+        border-radius: 12px;
+        background: linear-gradient(131.81deg, rgba(65, 65, 95, 1) 0%, rgba(45, 48, 74, 1) 100%);
+        margin-bottom: 14px;
+        .wallet-box-top {
+            display: flex;
+            align-content: center;
+            justify-content: space-between;
+
+            .wallet-box-btn1 {
+                width: 78px;
+                height: 30px;
+                opacity: 1;
+                border-radius: 4px;
+                background: linear-gradient(222.81deg, rgba(255, 227, 194, 1) 0%, rgba(255, 226, 192, 1) 0%, rgba(255, 225, 189, 1) 0%, rgba(251, 204, 147, 1) 100%);
+                margin-bottom: 14px;
+                font-size: 16px;
+                font-weight: 700;
+                line-height: 23.17px;
+                color: rgba(50, 52, 80, 1);
+            }
+
+            .wallet-box-btn2 {
+                width: 78px;
+                height: 30px;
+                opacity: 1;
+                border-radius: 4px;
+                border: 1px solid rgba(255, 218, 172, 1);
+                font-size: 16px;
+                font-weight: 500;
+                letter-spacing: 0px;
+                line-height: 23.17px;
+                color: rgba(255, 218, 172, 1);
+            }
+        }
+        .wallet-box-bottom{
+            display: flex;
+            align-items: center;
+            justify-content: space-around;
+        }
+
+
+    }
+
+}
+</style>

+ 1 - 1
pages/order/handle.vue

@@ -46,7 +46,7 @@ const onPhone = (phone) => {
 const slideData = computed(() => {
     //服务已开始,待上传图片
     if (orderStatus.value) {
-        return { successText: '服务已完成', startText: '拖动滑块结束服务', successColor: '#f64a1f', btnText: '结束' }
+        return { successText: '服务已完成', startText: '拖动滑块结束服务', successColor: '#f64a1f', btnText: '上传照片' }
     }
     return { successText: '服务已开始', startText: '拖动滑块开始服务', successColor: '#72c13f', btnText: '开始' }
 })

+ 1 - 1
pages/order/list/listItem.vue

@@ -20,7 +20,7 @@
                 <view class="item-btns">
                     <up-button type="error" text="查看" size="mini" :custom-style="btn_style"
                         @click="onClick(1)"></up-button>
-                   <!-- <up-button type="error" text="沟通" size="mini" :custom-style="btn_style" @click="onClick(2)"></up-button> -->
+                   <up-button type="error" text="沟通" size="mini" :custom-style="btn_style" @click="onClick(2)"></up-button>
                          <!-- <up-button type="error" text="上传照片" size="mini" :custom-style="btn_style" @click="onClick(3)"></up-button> -->
                 </view>
             </view>

+ 2 - 3
pages/order/order.vue

@@ -115,10 +115,9 @@ const onSubmit = () => {
 
 onLoad((options) => {
 	console.log('options', options);
-	// orderId.value = options.orderId;
-	orderId.value = '1911685346559660034';
+	orderId.value = options.orderId;
+	// orderId.value = '1911685346559660034';
 
-	
 	getOrderDetail();
 
 })

Datei-Diff unterdrückt, da er zu groß ist
+ 1169 - 0
pages/talk/components/uni-icons/icons.js


+ 96 - 0
pages/talk/components/uni-icons/uni-icons.vue

@@ -0,0 +1,96 @@
+<template>
+	<!-- #ifdef APP-NVUE -->
+	<text :style="{ color: color, 'font-size': iconSize }" class="uni-icons" @click="_onClick">{{unicode}}</text>
+	<!-- #endif -->
+	<!-- #ifndef APP-NVUE -->
+	<text :style="{ color: color, 'font-size': iconSize }" class="uni-icons" :class="['uniui-'+type,customPrefix,customPrefix?type:'']" @click="_onClick"></text>
+	<!-- #endif -->
+</template>
+
+<script>
+	import icons from './icons.js';
+	const getVal = (val) => {
+		const reg = /^[0-9]*$/g
+		return (typeof val === 'number' || reg.test(val) )? val + 'px' : val;
+	} 
+	// #ifdef APP-NVUE
+	var domModule = weex.requireModule('dom');
+	import iconUrl from './uniicons.ttf'
+	domModule.addRule('fontFace', {
+		'fontFamily': "uniicons",
+		'src': "url('"+iconUrl+"')"
+	});
+	// #endif
+
+	/**
+	 * Icons 图标
+	 * @description 用于展示 icons 图标
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=28
+	 * @property {Number} size 图标大小
+	 * @property {String} type 图标图案,参考示例
+	 * @property {String} color 图标颜色
+	 * @property {String} customPrefix 自定义图标
+	 * @event {Function} click 点击 Icon 触发事件
+	 */
+	export default {
+		name: 'UniIcons',
+		emits:['click'],
+		props: {
+			type: {
+				type: String,
+				default: ''
+			},
+			color: {
+				type: String,
+				default: '#333333'
+			},
+			size: {
+				type: [Number, String],
+				default: 16
+			},
+			customPrefix:{
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {
+				icons: icons.glyphs
+			}
+		},
+		computed:{
+			unicode(){
+				let code = this.icons.find(v=>v.font_class === this.type)
+				if(code){
+					return unescape(`%u${code.unicode}`)
+				}
+				return ''
+			},
+			iconSize(){
+				return getVal(this.size)
+			}
+		},
+		methods: {
+			_onClick() {
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	/* #ifndef APP-NVUE */
+	@import './uniicons.css';
+	@font-face {
+		font-family: uniicons;
+		src: url('./uniicons.ttf') format('truetype');
+	}
+
+	/* #endif */
+	.uni-icons {
+		font-family: uniicons;
+		text-decoration: none;
+		text-align: center;
+	}
+
+</style>

+ 663 - 0
pages/talk/components/uni-icons/uniicons.css

@@ -0,0 +1,663 @@
+.uniui-color:before {
+  content: "\e6cf";
+}
+
+.uniui-wallet:before {
+  content: "\e6b1";
+}
+
+.uniui-settings-filled:before {
+  content: "\e6ce";
+}
+
+.uniui-auth-filled:before {
+  content: "\e6cc";
+}
+
+.uniui-shop-filled:before {
+  content: "\e6cd";
+}
+
+.uniui-staff-filled:before {
+  content: "\e6cb";
+}
+
+.uniui-vip-filled:before {
+  content: "\e6c6";
+}
+
+.uniui-plus-filled:before {
+  content: "\e6c7";
+}
+
+.uniui-folder-add-filled:before {
+  content: "\e6c8";
+}
+
+.uniui-color-filled:before {
+  content: "\e6c9";
+}
+
+.uniui-tune-filled:before {
+  content: "\e6ca";
+}
+
+.uniui-calendar-filled:before {
+  content: "\e6c0";
+}
+
+.uniui-notification-filled:before {
+  content: "\e6c1";
+}
+
+.uniui-wallet-filled:before {
+  content: "\e6c2";
+}
+
+.uniui-medal-filled:before {
+  content: "\e6c3";
+}
+
+.uniui-gift-filled:before {
+  content: "\e6c4";
+}
+
+.uniui-fire-filled:before {
+  content: "\e6c5";
+}
+
+.uniui-refreshempty:before {
+  content: "\e6bf";
+}
+
+.uniui-location-filled:before {
+  content: "\e6af";
+}
+
+.uniui-person-filled:before {
+  content: "\e69d";
+}
+
+.uniui-personadd-filled:before {
+  content: "\e698";
+}
+
+.uniui-back:before {
+  content: "\e6b9";
+}
+
+.uniui-forward:before {
+  content: "\e6ba";
+}
+
+.uniui-arrow-right:before {
+  content: "\e6bb";
+}
+
+.uniui-arrowthinright:before {
+  content: "\e6bb";
+}
+
+.uniui-arrow-left:before {
+  content: "\e6bc";
+}
+
+.uniui-arrowthinleft:before {
+  content: "\e6bc";
+}
+
+.uniui-arrow-up:before {
+  content: "\e6bd";
+}
+
+.uniui-arrowthinup:before {
+  content: "\e6bd";
+}
+
+.uniui-arrow-down:before {
+  content: "\e6be";
+}
+
+.uniui-arrowthindown:before {
+  content: "\e6be";
+}
+
+.uniui-bottom:before {
+  content: "\e6b8";
+}
+
+.uniui-arrowdown:before {
+  content: "\e6b8";
+}
+
+.uniui-right:before {
+  content: "\e6b5";
+}
+
+.uniui-arrowright:before {
+  content: "\e6b5";
+}
+
+.uniui-top:before {
+  content: "\e6b6";
+}
+
+.uniui-arrowup:before {
+  content: "\e6b6";
+}
+
+.uniui-left:before {
+  content: "\e6b7";
+}
+
+.uniui-arrowleft:before {
+  content: "\e6b7";
+}
+
+.uniui-eye:before {
+  content: "\e651";
+}
+
+.uniui-eye-filled:before {
+  content: "\e66a";
+}
+
+.uniui-eye-slash:before {
+  content: "\e6b3";
+}
+
+.uniui-eye-slash-filled:before {
+  content: "\e6b4";
+}
+
+.uniui-info-filled:before {
+  content: "\e649";
+}
+
+.uniui-reload:before {
+  content: "\e6b2";
+}
+
+.uniui-micoff-filled:before {
+  content: "\e6b0";
+}
+
+.uniui-map-pin-ellipse:before {
+  content: "\e6ac";
+}
+
+.uniui-map-pin:before {
+  content: "\e6ad";
+}
+
+.uniui-location:before {
+  content: "\e6ae";
+}
+
+.uniui-starhalf:before {
+  content: "\e683";
+}
+
+.uniui-star:before {
+  content: "\e688";
+}
+
+.uniui-star-filled:before {
+  content: "\e68f";
+}
+
+.uniui-calendar:before {
+  content: "\e6a0";
+}
+
+.uniui-fire:before {
+  content: "\e6a1";
+}
+
+.uniui-medal:before {
+  content: "\e6a2";
+}
+
+.uniui-font:before {
+  content: "\e6a3";
+}
+
+.uniui-gift:before {
+  content: "\e6a4";
+}
+
+.uniui-link:before {
+  content: "\e6a5";
+}
+
+.uniui-notification:before {
+  content: "\e6a6";
+}
+
+.uniui-staff:before {
+  content: "\e6a7";
+}
+
+.uniui-vip:before {
+  content: "\e6a8";
+}
+
+.uniui-folder-add:before {
+  content: "\e6a9";
+}
+
+.uniui-tune:before {
+  content: "\e6aa";
+}
+
+.uniui-auth:before {
+  content: "\e6ab";
+}
+
+.uniui-person:before {
+  content: "\e699";
+}
+
+.uniui-email-filled:before {
+  content: "\e69a";
+}
+
+.uniui-phone-filled:before {
+  content: "\e69b";
+}
+
+.uniui-phone:before {
+  content: "\e69c";
+}
+
+.uniui-email:before {
+  content: "\e69e";
+}
+
+.uniui-personadd:before {
+  content: "\e69f";
+}
+
+.uniui-chatboxes-filled:before {
+  content: "\e692";
+}
+
+.uniui-contact:before {
+  content: "\e693";
+}
+
+.uniui-chatbubble-filled:before {
+  content: "\e694";
+}
+
+.uniui-contact-filled:before {
+  content: "\e695";
+}
+
+.uniui-chatboxes:before {
+  content: "\e696";
+}
+
+.uniui-chatbubble:before {
+  content: "\e697";
+}
+
+.uniui-upload-filled:before {
+  content: "\e68e";
+}
+
+.uniui-upload:before {
+  content: "\e690";
+}
+
+.uniui-weixin:before {
+  content: "\e691";
+}
+
+.uniui-compose:before {
+  content: "\e67f";
+}
+
+.uniui-qq:before {
+  content: "\e680";
+}
+
+.uniui-download-filled:before {
+  content: "\e681";
+}
+
+.uniui-pyq:before {
+  content: "\e682";
+}
+
+.uniui-sound:before {
+  content: "\e684";
+}
+
+.uniui-trash-filled:before {
+  content: "\e685";
+}
+
+.uniui-sound-filled:before {
+  content: "\e686";
+}
+
+.uniui-trash:before {
+  content: "\e687";
+}
+
+.uniui-videocam-filled:before {
+  content: "\e689";
+}
+
+.uniui-spinner-cycle:before {
+  content: "\e68a";
+}
+
+.uniui-weibo:before {
+  content: "\e68b";
+}
+
+.uniui-videocam:before {
+  content: "\e68c";
+}
+
+.uniui-download:before {
+  content: "\e68d";
+}
+
+.uniui-help:before {
+  content: "\e679";
+}
+
+.uniui-navigate-filled:before {
+  content: "\e67a";
+}
+
+.uniui-plusempty:before {
+  content: "\e67b";
+}
+
+.uniui-smallcircle:before {
+  content: "\e67c";
+}
+
+.uniui-minus-filled:before {
+  content: "\e67d";
+}
+
+.uniui-micoff:before {
+  content: "\e67e";
+}
+
+.uniui-closeempty:before {
+  content: "\e66c";
+}
+
+.uniui-clear:before {
+  content: "\e66d";
+}
+
+.uniui-navigate:before {
+  content: "\e66e";
+}
+
+.uniui-minus:before {
+  content: "\e66f";
+}
+
+.uniui-image:before {
+  content: "\e670";
+}
+
+.uniui-mic:before {
+  content: "\e671";
+}
+
+.uniui-paperplane:before {
+  content: "\e672";
+}
+
+.uniui-close:before {
+  content: "\e673";
+}
+
+.uniui-help-filled:before {
+  content: "\e674";
+}
+
+.uniui-paperplane-filled:before {
+  content: "\e675";
+}
+
+.uniui-plus:before {
+  content: "\e676";
+}
+
+.uniui-mic-filled:before {
+  content: "\e677";
+}
+
+.uniui-image-filled:before {
+  content: "\e678";
+}
+
+.uniui-locked-filled:before {
+  content: "\e668";
+}
+
+.uniui-info:before {
+  content: "\e669";
+}
+
+.uniui-locked:before {
+  content: "\e66b";
+}
+
+.uniui-camera-filled:before {
+  content: "\e658";
+}
+
+.uniui-chat-filled:before {
+  content: "\e659";
+}
+
+.uniui-camera:before {
+  content: "\e65a";
+}
+
+.uniui-circle:before {
+  content: "\e65b";
+}
+
+.uniui-checkmarkempty:before {
+  content: "\e65c";
+}
+
+.uniui-chat:before {
+  content: "\e65d";
+}
+
+.uniui-circle-filled:before {
+  content: "\e65e";
+}
+
+.uniui-flag:before {
+  content: "\e65f";
+}
+
+.uniui-flag-filled:before {
+  content: "\e660";
+}
+
+.uniui-gear-filled:before {
+  content: "\e661";
+}
+
+.uniui-home:before {
+  content: "\e662";
+}
+
+.uniui-home-filled:before {
+  content: "\e663";
+}
+
+.uniui-gear:before {
+  content: "\e664";
+}
+
+.uniui-smallcircle-filled:before {
+  content: "\e665";
+}
+
+.uniui-map-filled:before {
+  content: "\e666";
+}
+
+.uniui-map:before {
+  content: "\e667";
+}
+
+.uniui-refresh-filled:before {
+  content: "\e656";
+}
+
+.uniui-refresh:before {
+  content: "\e657";
+}
+
+.uniui-cloud-upload:before {
+  content: "\e645";
+}
+
+.uniui-cloud-download-filled:before {
+  content: "\e646";
+}
+
+.uniui-cloud-download:before {
+  content: "\e647";
+}
+
+.uniui-cloud-upload-filled:before {
+  content: "\e648";
+}
+
+.uniui-redo:before {
+  content: "\e64a";
+}
+
+.uniui-images-filled:before {
+  content: "\e64b";
+}
+
+.uniui-undo-filled:before {
+  content: "\e64c";
+}
+
+.uniui-more:before {
+  content: "\e64d";
+}
+
+.uniui-more-filled:before {
+  content: "\e64e";
+}
+
+.uniui-undo:before {
+  content: "\e64f";
+}
+
+.uniui-images:before {
+  content: "\e650";
+}
+
+.uniui-paperclip:before {
+  content: "\e652";
+}
+
+.uniui-settings:before {
+  content: "\e653";
+}
+
+.uniui-search:before {
+  content: "\e654";
+}
+
+.uniui-redo-filled:before {
+  content: "\e655";
+}
+
+.uniui-list:before {
+  content: "\e644";
+}
+
+.uniui-mail-open-filled:before {
+  content: "\e63a";
+}
+
+.uniui-hand-down-filled:before {
+  content: "\e63c";
+}
+
+.uniui-hand-down:before {
+  content: "\e63d";
+}
+
+.uniui-hand-up-filled:before {
+  content: "\e63e";
+}
+
+.uniui-hand-up:before {
+  content: "\e63f";
+}
+
+.uniui-heart-filled:before {
+  content: "\e641";
+}
+
+.uniui-mail-open:before {
+  content: "\e643";
+}
+
+.uniui-heart:before {
+  content: "\e639";
+}
+
+.uniui-loop:before {
+  content: "\e633";
+}
+
+.uniui-pulldown:before {
+  content: "\e632";
+}
+
+.uniui-scan:before {
+  content: "\e62a";
+}
+
+.uniui-bars:before {
+  content: "\e627";
+}
+
+.uniui-cart-filled:before {
+  content: "\e629";
+}
+
+.uniui-checkbox:before {
+  content: "\e62b";
+}
+
+.uniui-checkbox-filled:before {
+  content: "\e62c";
+}
+
+.uniui-shop:before {
+  content: "\e62f";
+}
+
+.uniui-headphones:before {
+  content: "\e630";
+}
+
+.uniui-cart:before {
+  content: "\e631";
+}

BIN
pages/talk/components/uni-icons/uniicons.ttf


+ 118 - 0
pages/talk/lib/global.scss

@@ -0,0 +1,118 @@
+/*
+ *  uni-app 全局样式表
+ *  作者:helang
+ *  邮箱:helang.love@qq.com
+*/
+
+/* 根元素样式 设置页面背景、字体大小、字体颜色,字符间距、长单词换行 */
+page {
+  background-color: #f3f3f3;
+  font-size: 28rpx;
+  box-sizing: border-box;
+  color: #333;
+  letter-spacing: 0;
+  word-wrap: break-word;
+}
+
+/* 设置常用元素尺寸规则 */
+view,textarea,input,label,form,button,image{box-sizing: border-box;}
+/* 按钮样式处理 */
+button{font-size: 28rpx;}
+/* 取消按钮默认的边框线效果 */
+button:after{border:none;}
+/* 设置图片默认样式,取消默认尺寸 */
+image{display: block;height: auto;width: auto;}
+/* 输入框默认字体大小 */
+textarea,input{font-size: 28rpx;};
+
+/* 列式弹性盒子 */
+.flex-col {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: nowrap;
+  justify-content: flex-start;
+  align-items: center;
+  align-content: center;
+}
+/* 行式弹性盒子 */
+.flex-row {
+  display: flex;
+  flex-direction: column;
+  flex-wrap: nowrap;
+  justify-content: flex-start;
+  align-items: flex-start;
+  align-content: flex-start;
+}
+ 
+/* 弹性盒子弹性容器 */
+.flex-col>view.flex-grow{width:0;flex-grow: 1;}
+.flex-row>view.flex-grow{height:0;flex-grow: 1;}
+ 
+/* 弹性盒子允许换行 */
+.flex-col.flex-wrap{flex-wrap: wrap;}
+ 
+/* 弹性盒子居中对齐 */
+.flex-col.flex-center,.flex-row.flex-center{justify-content: center;}
+ 
+/* 列式弹性盒子两端对齐 */
+.flex-col.flex-space{justify-content: space-between;}
+
+/* 弹性盒子快速分栏 ,这里非常郁闷 uniapp 居然不支持 * 选择器 */
+.flex-col.flex-col-2>view{width: 50%;}
+.flex-col.flex-col-3>view{width: 33.33333%;}
+.flex-col.flex-col-4>view{width: 25%;}
+.flex-col.flex-col-5>view{width: 20%;}
+.flex-col.flex-col-6>view{width: 16.66666%;}
+
+/* 字体颜色 */
+.color-333 {color: #333;}
+.color-666 {color: #666;}
+.color-999 {color: #999;}
+.color-ccc {color: #ccc;}
+.color-fff {color: #fff;}
+.color-6dc{color: #6dca6d;}
+.color-d51{color: #d51917;}
+.color-09f{color: #0099ff;}
+ 
+/* 背景色*/
+.bg-fff{background-color: #ffffff;}
+ 
+/* 字体大小 */
+.size-10 {font-size: 20rpx;}
+.size-12 {font-size: 24rpx;}
+.size-14 {font-size: 28rpx;}
+.size-16 {font-size: 32rpx;}
+.size-18 {font-size: 36rpx;}
+.size-20 {font-size: 40rpx;}
+ 
+/* 字体加粗 */
+.font-b{font-weight: bold;}
+ 
+/* 对齐方式 */
+.align-c{text-align: center;}
+.align-l{text-align: left;}
+.align-r{text-align: right;}
+ 
+/* 遮罩 */
+.shade{
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  background-color: rgba(0,0,0,0.8);
+  z-index: 100;
+}
+ 
+/* 弹窗 */
+.shade-box{
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  margin: auto;
+  z-index: 101;
+  min-width: 200rpx;
+  min-height: 200rpx;
+}

+ 23 - 0
pages/talk/package.json

@@ -0,0 +1,23 @@
+{
+    "id": "helang-talk",
+    "name": "即时通讯聊天页面模版",
+    "displayName": "即时通讯聊天页面模版",
+    "version": "1.1.1",
+    "description": "实现基本的聊天窗口布局及实现 发送信息 和 下拉加载历史消息 功能",
+    "keywords": [
+        "即时通讯",
+        "聊天",
+        "加载历史消息"
+    ],
+    "dcloudext": {
+        "type": "pagetemplate-vue",
+        "sale": {
+            "regular": {
+                "price": "0.00"
+            },
+            "sourcecode": {
+                "price": "0.00"
+            }
+        }
+    }
+}

+ 343 - 0
pages/talk/pages/index/index.vue

@@ -0,0 +1,343 @@
+<template>
+	<view>
+		<view class="container" :style="pageHeight">
+			<view class="box-1">
+				<scroll-view scroll-y refresher-background="transparent" style="height: 100%;"
+					@refresherrefresh="refresherrefresh" :refresher-enabled="true" :scroll-with-animation="false"
+					:refresher-triggered="scrollView.refresherTriggered" :scroll-into-view="scrollView.intoView">
+					<view class="talk-list">
+						<view v-for="(item, index) in talkList" :key="item.id" :id="'msg-' + item.id">
+							<view class="item flex-col" :class="item.type == 1 ? 'push' : 'pull'">
+								<image :src="item.pic" mode="aspectFill" class="pic"></image>
+								<view class="content">
+									<template v-if="item.contentType === 'image'">
+										<image :src="item.content" mode="widthFix" style="width: 400rpx;"></image>
+									</template>
+									<template v-else>
+										{{ item.content }}
+									</template>
+								</view>
+							</view>
+						</view>
+
+					</view>
+				</scroll-view>
+			</view>
+			<view class="box-2">
+				<view class="flex-col">
+					<view style="margin-right: 20rpx;">
+						<uni-icons type="image" size="24" color="#42b983" @tap="handleImageClick"></uni-icons>
+					</view>
+					<view class="flex-grow">
+						<input type="text" class="content" v-model="content" placeholder="请输入聊天内容"
+							placeholder-style="color:#DDD;" :cursor-spacing="6">
+					</view>
+					<button class="send" @tap="handleSendClick">发送</button>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { getHistoryMsg } from "@/pages/talk/request/template-talk/history-msg.js";
+export default {
+	data() {
+		return {
+			// 滚动容器
+			scrollView: {
+				refresherTriggered: false,
+				intoView: "",
+				safeAreaHeight: 0
+			},
+			// 聊天列表数据
+			talkList: [],
+			// 请求参数
+			ajax: {
+				rows: 20,	//每页数量
+				page: 1,	//页码
+				flag: true,	// 请求开关
+			},
+			// 发送内容
+			content: '',
+		}
+	},
+	computed: {
+		// 页面高度
+		pageHeight() {
+			const safeAreaHeight = this.scrollView.safeAreaHeight;
+			console.log('safeAreaHeight', safeAreaHeight);
+
+			if (safeAreaHeight > 0) {
+				return `height: calc(${safeAreaHeight}px - var(--window-top));`
+			}
+			return "";
+		}
+	},
+	onLoad() {
+		// #ifdef H5
+		this.scrollView.safeAreaHeight = uni.getSystemInfoSync().safeArea.height;
+		// #endif
+
+		this.getMessage();
+	},
+	methods: {
+		// 下拉刷新
+		refresherrefresh(e) {
+			this.getMessage();
+			this.scrollView.refresherTriggered = true;
+		},
+		// 获取历史消息
+		getMessage() {
+			if (!this.ajax.flag) {
+				return;
+			}
+
+			// 此处用到 ES7 的 async/await 知识,为使代码更加优美。不懂的请自行学习。
+			let get = async () => {
+				this.ajax.flag = false;
+				let data = await getHistoryMsg({
+					page: this.ajax.page,
+					rows: this.ajax.rows
+				});
+				this.scrollView.refresherTriggered = false;
+
+				// 获取待滚动元素选择器,解决插入数据后,滚动条定位时使用。取当前消息数据的第一条信息元素
+				const selector = `msg-${data?.[0]?.id}`;;
+
+				// 将获取到的消息数据合并到消息数组中
+				this.talkList = this.talkList.concat(data);
+
+				// 数据挂载后执行,不懂的请自行阅读 Vue.js 文档对 Vue.nextTick 函数说明。
+				this.$nextTick(() => {
+					// 设置当前滚动的位置
+					this.scrollView.intoView = selector;
+
+					if (data.length < this.ajax.rows) {
+						// 当前消息数据条数小于请求要求条数时,则无更多消息,不再允许请求。
+						// 可在此处编写无更多消息数据时的逻辑
+					} else {
+						this.ajax.flag = true;
+						this.ajax.page++;
+					}
+
+				})
+			}
+			get();
+		},
+		// 发送信息
+		handleSendClick() {
+			if (!this.content) {
+				uni.showToast({
+					title: '请输入有效的内容',
+					icon: 'none'
+				})
+				return;
+			}
+			this.sendMessage(this.content, 'text');
+
+			// 清空内容框中的内容
+			this.content = '';
+		},
+		// 处理图片点击
+		handleImageClick() {
+			uni.chooseImage({
+				count: 1, //默认9
+				sizeType: ['original'],
+				sourceType: ['album', 'camera'],
+				success: (res) => {
+					const imageUrl = res.tempFilePaths[0];
+					this.sendMessage(imageUrl, 'image');
+				}
+			});
+		},
+		sendMessage(content, contentType = 'text') {
+			// 将当前发送信息 添加到消息列表。
+			let data = {
+				"id": new Date().getTime(),
+				content,
+				contentType,
+				"type": 1,
+				"pic": "/static/logo.png"
+			}
+			this.talkList.unshift(data);
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+@import "../../lib/global.scss";
+
+page {
+	background-color: #F3F3F3;
+	font-size: 28rpx;
+}
+
+.container {
+	height: calc(100vh - var(--window-top));
+	display: flex;
+	flex-direction: column;
+	flex-wrap: nowrap;
+	align-content: center;
+	justify-content: space-between;
+	align-items: stretch;
+}
+
+/* 加载数据提示 */
+.tips {
+	position: fixed;
+	left: 0;
+	top: var(--window-top);
+	width: 100%;
+	z-index: 9;
+	background-color: rgba(0, 0, 0, 0.15);
+	height: 72rpx;
+	line-height: 72rpx;
+	transform: translateY(-80rpx);
+	transition: transform 0.3s ease-in-out 0s;
+
+	&.show {
+		transform: translateY(0);
+	}
+}
+
+.box-1 {
+	width: 100%;
+	height: 0;
+	flex: 1 0 auto;
+	box-sizing: content-box;
+}
+
+.box-2 {
+	height: auto;
+	z-index: 2;
+	border-top: #e5e5e5 solid 1px;
+	box-sizing: content-box;
+	background-color: #F3F3F3;
+
+	/* 兼容iPhoneX */
+	padding-bottom: 0;
+	padding-bottom: constant(safe-area-inset-bottom);
+	padding-bottom: env(safe-area-inset-bottom);
+
+	>view {
+		padding: 0 20rpx;
+		height: 100rpx;
+	}
+
+	.content {
+		background-color: #fff;
+		height: 64rpx;
+		padding: 0 20rpx;
+		border-radius: 32rpx;
+		font-size: 28rpx;
+	}
+
+	.send {
+		background-color: #42b983;
+		color: #fff;
+		height: 64rpx;
+		margin-left: 20rpx;
+		border-radius: 32rpx;
+		padding: 0;
+		width: 120rpx;
+		line-height: 62rpx;
+
+		&:active {
+			background-color: #5fc496;
+		}
+	}
+}
+
+.talk-list {
+	padding-bottom: 20rpx;
+	display: flex;
+	flex-direction: column-reverse;
+	flex-wrap: nowrap;
+	align-content: flex-start;
+	justify-content: flex-end;
+	align-items: stretch;
+
+	// 添加弹性容器,让内容自动在顶部
+	&::before {
+		content: '.';
+		display: inline;
+		visibility: hidden;
+		line-height: 0;
+		font-size: 0;
+		flex: 1 0 auto;
+		height: 1px;
+	}
+
+
+	/* 消息项,基础类 */
+	.item {
+		padding: 20rpx 20rpx 0 20rpx;
+		align-items: flex-start;
+		align-content: flex-start;
+		color: #333;
+
+		.pic {
+			width: 92rpx;
+			height: 92rpx;
+			border-radius: 50%;
+			border: #fff solid 1px;
+		}
+
+		.content {
+			padding: 20rpx;
+			border-radius: 4px;
+			max-width: 500rpx;
+			word-break: break-all;
+			line-height: 52rpx;
+			position: relative;
+		}
+
+		/* 收到的消息 */
+		&.pull {
+			.content {
+				margin-left: 32rpx;
+				background-color: #fff;
+
+				&::after {
+					content: '';
+					display: block;
+					width: 0;
+					height: 0;
+					border-top: 16rpx solid transparent;
+					border-bottom: 16rpx solid transparent;
+					border-right: 20rpx solid #fff;
+					position: absolute;
+					top: 30rpx;
+					left: -18rpx;
+				}
+			}
+		}
+
+		/* 发出的消息 */
+		&.push {
+			/* 主轴为水平方向,起点在右端。使不修改DOM结构,也能改变元素排列顺序 */
+			flex-direction: row-reverse;
+
+			.content {
+				margin-right: 32rpx;
+				background-color: #a0e959;
+
+				&::after {
+					content: '';
+					display: block;
+					width: 0;
+					height: 0;
+					border-top: 16rpx solid transparent;
+					border-bottom: 16rpx solid transparent;
+					border-left: 20rpx solid #a0e959;
+					position: absolute;
+					top: 30rpx;
+					right: -18rpx;
+				}
+			}
+		}
+	}
+}
+</style>

+ 36 - 0
pages/talk/request/template-talk/history-msg.js

@@ -0,0 +1,36 @@
+export const getHistoryMsg = (params)=>{
+	const { page = 1,rows = 10 } = params ?? {};
+	let join = ()=>{
+		let arr = [];
+		
+		//通过当前页码及页数,模拟数据内容
+		let startIndex = (page-1) * rows;
+		let endIndex = startIndex + rows;
+		for(let i = startIndex; i < endIndex; i++){
+			arr.push({
+				"id":i,	// 消息的ID
+				"content":`这是历史记录的第${i+1}条消息,第${page}页`,	// 消息内容
+				"type":Math.random() > 0.5 ? 1 : 0	,// 此为消息类别,设 1 为发出去的消息,0 为收到对方的消息,
+				"pic":"/static/logo.png"	// 头像
+			})
+		}
+		
+		/*
+			颠倒数组中元素的顺序。将最新的数据排在本次接口返回数据的最后面。
+			后端接口按 消息的时间降序查找出当前页的数据后,再将本页数据按消息时间降序排序返回。
+			这是数据的重点,因为页面滚动条和上拉加载历史的问题。
+		 */
+		// arr.reverse();
+		
+		return arr;
+	}
+	
+	// 此处用到 ES6 的 Promise 知识,不懂的请自行学习。
+	return new Promise((done,fail)=>{
+		// 无数据请求接口,由 setTimeout 模拟,正式项目替换为 ajax 即可。
+		setTimeout(()=>{
+			let data = join();
+			done(data);
+		},1000);
+	})
+}

BIN
pages/talk/static/logo.png


+ 7 - 0
static/scss/global.scss

@@ -122,4 +122,11 @@
   display: flex;
   align-items: center;
   justify-content: center;
+}
+
+.flex-center-column {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
 }