完善内容(home)

This commit is contained in:
张文涛 2025-04-10 23:11:35 +08:00
parent 08948d0083
commit eaab5d5c69
14 changed files with 2294 additions and 839 deletions

View File

@ -56,7 +56,7 @@
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wx2cec08446ce214ee",
"appid" : "wxaec84a18ccf98226",
"setting" : {
"urlCheck" : false,
"es6" : true,

View File

@ -221,6 +221,24 @@
"navigationStyle": "custom",
"navigationBarTitleText": "物业催缴"
}
},
{
"path": "housing/list",
"style": {
"enablePullDownRefresh": false,
"navigationBarTextStyle": "black",
"navigationStyle": "custom",
"navigationBarTitleText": "房源管理"
}
},
{
"path": "housing/detail",
"style": {
"enablePullDownRefresh": false,
"navigationBarTextStyle": "black",
"navigationStyle": "custom",
"navigationBarTitleText": "房源详情"
}
}
]
}],

View File

@ -141,14 +141,14 @@
])
const gridList = ref([
{
image: uni.$globalData?.RESOURCE_URL + 'home/grid/gongdan.png',
image: uni.$globalData?.RESOURCE_URL + 'home/grid/xiaoxi.png',
title: '我的工单',
type: 'my_order'
},
{
image: uni.$globalData?.RESOURCE_URL + 'home/grid/fangke.png',
title: '访客邀请',
type: ''
image: uni.$globalData?.RESOURCE_URL + 'home/grid/yaoqing.png',
title: '邀请业主',
type: 'invite'
},
{
image: uni.$globalData?.RESOURCE_URL + 'home/grid/cuijiao.png',
@ -161,9 +161,9 @@
type: 'complaint'
},
{
image: uni.$globalData?.RESOURCE_URL + 'home/grid/yaoqing.png',
title: '邀请业主',
type: 'invite'
image:uni.$globalData?.RESOURCE_URL + 'home/grid/fangke.png' ,
title: '访客邀请',
type: ''
},
{
image: uni.$globalData?.RESOURCE_URL + 'home/grid/wenjuan.png',
@ -173,7 +173,7 @@
{
image: uni.$globalData?.RESOURCE_URL + 'home/grid/fangyuan.png',
title: '房源管理',
type: ''
type: 'housing'
},
// {
// image: uni.$globalData?.RESOURCE_URL + 'home/grid/chewei.png',
@ -181,7 +181,7 @@
// type: ''
// },
{
image: uni.$globalData?.RESOURCE_URL + 'home/grid/xiaoxi.png',
image:uni.$globalData?.RESOURCE_URL + 'home/grid/gongdan.png' ,
title: '商品核销',
type: 'verification'
}
@ -293,6 +293,12 @@
url: '/pagesB/call/index'
});
break;
case 'housing':
//
uni.navigateTo({
url: '/pagesB/housing/list'
});
break;
default:
break;
}

View File

@ -0,0 +1,535 @@
<template>
<view>
<u-popup :safeAreaInsetBottom='false' :show="show" @close="onClose" mode="center" :round="10">
<view class="share-poster">
<!-- 海报画布 -->
<canvas class="poster-canvas" canvas-id="posterCanvas"
:style="{width: posterWidth + 'px',height: posterHeight + 'px',}"></canvas>
<!-- 分享操作按钮 -->
<view class="share-actions">
<view class="action-item left" @click="shareToFriend">
微信分享
</view>
<view class="action-item right" @click="savePoster">
保存相册
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import {
RESOURCES_URL
} from '@/config/app'
/**
* @property {Object} goodsInfo - 商品信息
* @property {string} goodsInfo.title - 商品标题
* @property {number} goodsInfo.price - 商品价格
* @property {string} goodsInfo.image - 商品主图URL
* @property {string} goodsInfo.qrcode - 小程序码URL
* @property {boolean} show - 控制弹窗显示隐藏
* @event updateShare - 弹窗关闭时触发参数为false
*/
export default {
name: 'SharePoster',
props: {
goodsInfo: {
type: Object,
required: true,
default: () => ({
title: '',
price: 0,
image: '',
qrcode: '', // URL
}),
},
show: {
type: Boolean,
default: false,
},
},
data() {
return {
generating: false, //
posterWidth: 300, //
posterHeight: 450, //
loadingText: {
contentdown: '生成海报中...',
contentrefresh: '生成海报中...',
contentnomore: '生成完成',
},
imgUrl: '',
}
},
watch: {
// show
show(newVal) {
if (newVal) {
this.$nextTick(() => {
this.generatePoster()
})
}
},
goodsInfo: {
immediate: true,
deep: true,
handler(newVal) {
console.log('SharePoster goodsInfo changed:', newVal)
},
},
},
methods: {
/**
* 关闭弹窗
* 触发updateShare事件参数为false
*/
onClose() {
//
const ctx = uni.createCanvasContext('posterCanvas', this)
ctx.clearRect(0, 0, this.posterWidth, this.posterHeight)
ctx.draw() //
this.generating = false //
this.$emit('updateShare', false)
},
/**
* 生成分享海报
* 步骤
* 1. 创建画布上下文
* 2. 绘制白色背景
* 3. 绘制商品图片圆角
* 4. 绘制商品信息
* 5. 绘制价格
* 6. 绘制商品类型标签带圆角
* 7. 绘制小程序码带圆角
* 8. 绘制提示文字
* 9. 刷新画布显示
*/
async generatePoster() {
if (this.generating) return
this.generating = true
try {
const ctx = uni.createCanvasContext('posterCanvas', this)
//
ctx.clearRect(0, 0, this.posterWidth, this.posterHeight)
//
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, this.posterWidth, this.posterHeight)
// -
const imageUrl = this.goodsInfo.image || ''
if (imageUrl) {
//
await this.drawImage(ctx, imageUrl, 0, 0, this.posterWidth, this.posterHeight)
}
//
// ctx.fillStyle = 'rgba(255, 255, 255, 0.85)'
// ctx.fillRect(20, 80, this.posterWidth - 40, this.posterHeight - 100)
// logo - d
const share_title =
'https://hnxix-public.oss-cn-hangzhou.aliyuncs.com/driver/wx/img/share_title.png'
if (share_title) {
const qrSize = 50
const qrX = 15
const qrY = 20 //
await this.drawImage(ctx, share_title, qrX, qrY, 100, 24)
}
//
const gradient = ctx.createLinearGradient(15, 110, 15, 150)
gradient.addColorStop(0, '#1068FF'); //
gradient.addColorStop(0.48, '#2B497E'); // 48%
gradient.addColorStop(1, '#D51BB6'); //
ctx.shadowColor = 'rgba(0, 0, 0, 0.15)'
ctx.shadowBlur = 2
ctx.shadowOffsetX = 1
ctx.shadowOffsetY = 1
ctx.font = 'italic bold 20px sans-serif'
ctx.setFontSize(19)
ctx.setTextAlign('left')
ctx.fillStyle = gradient
ctx.fillText('王师傅', 15, 120)
//
ctx.font = 'italic bold 20px sans-serif'
ctx.setFontSize(19)
ctx.setTextAlign('left')
ctx.fillStyle = gradient
ctx.fillText('邀请您预约行程', 15, 150)
//
ctx.shadowColor = 'transparent'
ctx.shadowBlur = 0
ctx.shadowOffsetX = 0
ctx.shadowOffsetY = 0
// -
const qrcodeUrl = this.goodsInfo.qrcode
if (qrcodeUrl) {
const qrSize = 150
const qrX = (this.posterWidth - qrSize) / 2
const qrY = 240 //
await this.drawImage(ctx, qrcodeUrl, qrX, qrY, qrSize, qrSize)
}
// -
ctx.setFontSize(11)
ctx.setFillStyle('#666666')
ctx.setTextAlign('center')
ctx.fillText('微信扫码,预约行程', this.posterWidth / 2, 420)
ctx.draw(false, () => {
setTimeout(() => {
this.generating = false
}, 500)
})
} catch (error) {
console.error('生成海报失败:', error)
this.generating = false
uni.showToast({
title: '生成海报失败',
icon: 'none',
})
}
},
/**
* 计算等比例缩放后的尺寸和位置
* @param {number} imgWidth - 原图宽度
* @param {number} imgHeight - 原图高度
* @param {number} maxWidth - 容器最大宽度
* @param {number} maxHeight - 容器最大高度
* @returns {Object} - 返回计算后的位置和尺寸
*/
calculateImageRect(imgWidth, imgHeight, maxWidth, maxHeight) {
const imgRatio = imgWidth / imgHeight
const maxRatio = maxWidth / maxHeight
let finalWidth = maxWidth
let finalHeight = maxHeight
let x = 0
let y = 0
// ""
if (imgRatio > maxRatio) {
//
finalHeight = maxHeight
finalWidth = finalHeight * imgRatio
x = (maxWidth - finalWidth) / 2
} else {
//
finalWidth = maxWidth
finalHeight = finalWidth / imgRatio
y = (maxHeight - finalHeight) / 2
}
return {
x: Math.floor(x),
y: Math.floor(y),
width: Math.floor(finalWidth),
height: Math.floor(finalHeight)
}
},
/**
* 加载并绘制图片
* @param {CanvasContext} ctx - 画布上下文
* @param {string} src - 图片URL
* @param {number} x - 绘制位置x坐标
* @param {number} y - 绘制位置y坐标
* @param {number} width - 容器宽度
* @param {number} height - 容器高度
* @returns {Promise} 图片加载完成的Promise
*/
drawImage(ctx, src, x, y, width, height) {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: src,
success: (image) => {
// 使使
if (x === 0 && y === 0 && width === this.posterWidth && height === this
.posterHeight) {
// 使
const rect = this.calculateImageRect(image.width, image.height, width,
height)
ctx.drawImage(
image.path,
x + rect.x,
y + rect.y,
rect.width,
rect.height
);
} else {
//
ctx.drawImage(
image.path,
x,
y,
width,
height
);
}
resolve()
},
fail: (err) => {
console.error('加载图片失败:', err)
reject(err)
},
})
})
},
/**
* 绘制圆角矩形
* @param {CanvasContext} ctx - 画布上下文
* @param {number} x - 左上角x坐标
* @param {number} y - 左上角y坐标
* @param {number} w - 宽度
* @param {number} h - 高度
* @param {number} r - 圆角半径
*/
roundRect(ctx, x, y, w, h, r) {
if (w < 2 * r) r = w / 2
if (h < 2 * r) r = h / 2
ctx.beginPath()
ctx.moveTo(x + r, y)
ctx.arcTo(x + w, y, x + w, y + h, r)
ctx.arcTo(x + w, y + h, x, y + h, r)
ctx.arcTo(x, y + h, x, y, r)
ctx.arcTo(x, y, x + w, y, r)
ctx.closePath()
},
/**
* 文字自动换行
* @param {CanvasContext} ctx - 画布上下文
* @param {string} text - 文本内容
* @param {number} maxWidth - 最大宽度
* @returns {Array<string>} 文本行数组
*/
splitText(ctx, text, maxWidth) {
const chars = text.split('')
const lines = []
let tempLine = ''
chars.forEach((char) => {
if (ctx.measureText(tempLine + char).width <= maxWidth) {
tempLine += char
} else {
lines.push(tempLine)
tempLine = char
}
})
if (tempLine) {
lines.push(tempLine)
}
return lines
},
/**
* 分享给好友
*/
shareToFriend() {
uni.canvasToTempFilePath({
canvasId: 'posterCanvas',
success: (res) => {
uni.showShareImageMenu({
path: res.tempFilePath,
success: function() {
console.log('分享图片成功')
},
fail: function(err) {
console.log('分享图片失败:', err)
},
})
},
},
this
)
},
/**
* 保存到本地
*/
savePoster() {
// #ifdef MP-WEIXIN
//
this.checkAndRequestPermission();
// #endif
// #ifdef APP-PLUS || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO
//
this.saveImageToAlbum();
// #endif
// #ifdef H5
// H5
uni.showToast({
title: 'H5环境不支持保存到相册',
icon: 'none'
});
// #endif
},
/**
* 检查并请求权限
*/
checkAndRequestPermission() {
uni.getSetting({
success: (res) => {
if (!res.authSetting['scope.writePhotosAlbum']) {
uni.authorize({
scope: 'scope.writePhotosAlbum',
success: () => {
this.saveImageToAlbum();
},
fail: () => {
this.showAuthModal();
}
});
} else {
this.saveImageToAlbum();
}
},
fail: (err) => {
console.log('获取设置失败:', err);
uni.showToast({
title: '获取权限失败',
icon: 'none'
});
}
});
},
/**
* 显示授权引导弹窗
*/
showAuthModal() {
uni.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
confirmText: '去授权',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
uni.openSetting();
}
}
});
},
/**
* 实际保存图片的方法
*/
saveImageToAlbum() {
uni.canvasToTempFilePath({
canvasId: 'posterCanvas',
success: (res) => {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
uni.showToast({
title: '已保存至相册,去朋友圈分享',
icon: 'none',
});
},
fail: (err) => {
console.log('保存失败:', err);
//
if (err.errMsg.indexOf('auth deny') > -1) {
// #ifdef MP-WEIXIN
this.showAuthModal();
// #endif
} else {
uni.showToast({
title: '保存失败',
icon: 'none',
});
}
},
});
},
fail: (err) => {
console.log('生成临时文件失败:', err);
uni.showToast({
title: '生成图片失败',
icon: 'none',
});
}
}, this);
},
},
}
</script>
<style lang="scss" scoped>
@font-face {
font-family: 'YouSheBiaoTiHei';
src: url('https: //hnxix-public.oss-cn-hangzhou.aliyuncs.com/driver/wx/font/YouSheBiaoTiHei.ttf');
}
/* 海报容器 */
.share-poster {
outline: none;
// background-color: #fff;
border-radius: 20rpx;
overflow: hidden;
max-height: 60vh;
// overflow-y: auto;
// padding-bottom: 140rpx; //
/* 画布样式 */
.poster-canvas {
display: block;
margin: auto;
// background: #fff;
}
/* 分享按钮组 */
.share-actions {
position: fixed;
left: 0;
right: 0;
bottom: 140rpx;
display: flex;
justify-content: space-around;
align-items: center;
padding: 20rpx 40rpx;
.action-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 28rpx 40rpx;
border-radius: 40rpx;
}
.left {
background-color: #0ED05F;
color: #FFFFFF;
}
.right {
background-color: #FFFFFF;
color: #000000;
}
}
}
// ::v-deep .u-popup {
// background-color: rebeccapurple !important;
// }
// ::v-deep .u-popup__content {
// background-color: rebeccapurple !important;
// }
</style>

View File

@ -16,7 +16,7 @@
</wd-navbar>
<!-- 导航栏背景 -->
<view class="nav-bg-layer"></view>
<!-- 工单状态卡片 -->
<view class="ticket-card">
<view class="ticket-header">
@ -28,17 +28,18 @@
</view>
<view class="create-time">创建时间: {{ticketInfo.create_time}}</view>
</view>
<view class="ticket-type" :class="'type-'+ticketInfo.type">
<text v-if="ticketInfo.type == 'F2'">配送</text>
<text v-if="ticketInfo.type == 'F4'">量房</text>
<text v-if="ticketInfo.type == 'F6'">维修</text>
</view>
</view>
<view class="ticket-status">
<view class="status-icon" :class="'status-'+ticketInfo.status">
<text v-if="ticketInfo.status == 2 || ticketInfo.status == 1" class="ri-checkbox-circle-fill"></text>
<text v-if="ticketInfo.status == 2 || ticketInfo.status == 1"
class="ri-checkbox-circle-fill"></text>
<text v-else-if="ticketInfo.status == 3" class="ri-close-circle-fill"></text>
<text v-else class="ri-time-line"></text>
</view>
@ -58,25 +59,25 @@
</view>
</view>
</view>
<!-- 联系信息 -->
<view class="info-card">
<view class="card-title">
<text class="ri-user-3-line title-icon"></text>
<text>联系信息</text>
</view>
<view class="info-content">
<view class="info-item">
<text class="item-label">小区名称</text>
<text class="item-value">{{ticketInfo?.order?.village_name}}</text>
</view>
<view class="info-item">
<text class="item-label">联系人</text>
<text class="item-value">{{ticketInfo?.order?.name}}</text>
</view>
<view class="info-item">
<text class="item-label">联系电话</text>
<view class="phone-value">
@ -84,58 +85,72 @@
<text class="phone-number">{{ticketInfo?.order?.mobile}}</text>
</view>
</view>
<!-- <view class="info-item product-info">
<view v-if="ticketInfo?.type == 'F2' " class="info-item product-info">
<text class="item-label">商品信息</text>
<view class="product-detail">
<text class="product-name">燕京 8度U8 500ML*12/*2 送燕京9度菊花听500ML*12</text>
<view class="product-images">
<image class="product-image" src="../../static/swiper/1.png" mode="aspectFill"></image>
<image class="product-image" src="../../static/swiper/1.png" mode="aspectFill"></image>
<view class="product-list-full">
<!-- 商品列表 -->
<view v-for="(product, index) in ticketInfo?.product" :key="index" class="product-card-full">
<view class="product-main">
<image class="product-image" :src="product.picture" mode="aspectFill"></image>
<view class="product-details">
<text class="product-name">{{product.product_name}}</text>
<text class="product-spec">{{product.spec_name}}</text>
<view class="price-quantity">
<text class="price">¥{{product.price}}</text>
<text class="quantity">x{{product.num}}</text>
</view>
</view>
</view>
</view>
<!-- 商品总数和总价 -->
<view class="order-summary">
<text class="summary-count">{{ticketInfo?.product.length}}件商品</text>
<text class="summary-price">合计: ¥{{getTotalPrice()}}</text>
</view>
</view>
</view> -->
</view>
</view>
</view>
<!-- 报修信息 -->
<view v-if="ticketInfo?.type == 'F6'" class="info-card">
<view class="card-title">
<text class="ri-tools-line title-icon"></text>
<text>报修信息</text>
</view>
<view class="info-content">
<view class="info-item">
<text class="item-label">报修位置</text>
<text class="item-value">{{ticketInfo?.order?.region_name}}{{ticketInfo?.order?.cell_name}}{{ticketInfo?.order?.house_name}}{{ticketInfo?.order?.address}}</text>
<text
class="item-value">{{ticketInfo?.order?.region_name}}{{ticketInfo?.order?.cell_name}}{{ticketInfo?.order?.house_name}}{{ticketInfo?.order?.address}}</text>
</view>
<view class="info-item">
<text class="item-label">问题描述</text>
<text class="item-value">{{ticketInfo?.order?.content}}</text>
</view>
<view v-if="ticketInfo?.order?.images" class="info-item">
<text class="item-label">现场照片</text>
<view class="scene-images">
<image v-for="(item) in ticketInfo?.order?.images?.split(',')" :key="item"
class="scene-image"
:src="item"
mode="aspectFill">
</image>
<image v-for="(item) in ticketInfo?.order?.images?.split(',')" :key="item" class="scene-image"
:src="item" mode="aspectFill">
</image>
</view>
</view>
</view>
</view>
<!-- 工单记录 -->
<view class="info-card">
<view class="card-title">
<text class="ri-history-line title-icon"></text>
<text>工单记录</text>
</view>
<view class="timeline">
<view class="timeline-item">
<view class="timeline-dot"></view>
@ -144,15 +159,15 @@
<view class="timeline-text">创建工单</view>
</view>
</view>
<view v-if="ticketInfo.status >= 1" class="timeline-item">
<view class="timeline-dot"></view>
<view class="timeline-content">
<view class="timeline-time">2025-03-13 11:25:18</view>
<view class="timeline-text">工单已接单</view>
</view>
</view>
</view>
<view v-if="ticketInfo.status >= 2" class="timeline-item">
<view class="timeline-dot"></view>
<view class="timeline-content">
@ -162,7 +177,7 @@
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="footer-actions">
<view class="action-buttons">
@ -174,15 +189,16 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { useNavigation } from '@/hooks/useNavigation'
import {myTicketInfo} from '@/api/ticket'
import { useToast } from '@/uni_modules/wot-design-uni'
import { myTicketInfo } from '@/api/ticket'
import { useToast } from '@/uni_modules/wot-design-uni'
const Toast = useToast()
const loading = ref<boolean>(false)
const bntloading = ref<boolean>(false)
const ticketInfo = ref<any>({
ticket_no: '',
create_time: '',
@ -200,10 +216,48 @@
building_area: '',
status: 0,
images: '',
remark: ''
remark: '',
products: [] //
}
})
//
const productList = computed(() => {
// 使
if (ticketInfo.value?.order?.products && ticketInfo.value.order.products.length > 0) {
return ticketInfo.value.order.products
}
//
return [
{
id: 1,
name: '燕京 8度U8 500ML*12瓶/箱',
spec: '500ML*12瓶/箱',
quantity: 2,
price: 99,
image: '../../static/swiper/1.png'
},
{
id: 2,
name: '燕京9度菊花听500ML*12听',
spec: '500ML*12听',
quantity: 1,
price: 89,
image: '../../static/swiper/1.png'
}
]
})
//
const getTotalPrice = () => {
let total = 0
ticketInfo.value?.product.forEach(product => {
total += product.price * product.num
})
return total.toFixed(2)
}
// 使 composable
const {
hasMultiplePages, //
@ -217,7 +271,7 @@
loadInfo(q.ticket_id)
}
})
const loadInfo = async (ticket_id) => {
const res = await myTicketInfo({ ticket_id })
ticketInfo.value = res.data
@ -252,7 +306,6 @@
}
}
const handleAction = (action) => {
console.log(action);
}
@ -270,18 +323,19 @@
background-attachment: fixed;
padding-bottom: 180rpx;
}
.navbar-content {
display: flex;
align-items: center;
padding-left: 20rpx;
.back-icon, .home-icon {
.back-icon,
.home-icon {
font-size: 60rpx;
color: #333;
margin-right: 10rpx;
}
.page-title {
font-size: 36rpx;
font-weight: 500;
@ -305,7 +359,8 @@
}
/* 卡片通用样式 */
.ticket-card, .info-card {
.ticket-card,
.info-card {
width: 92%;
margin: 0 auto;
margin-bottom: 30rpx;
@ -313,18 +368,18 @@
background: #fff;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.05);
overflow: hidden;
&:active {
transform: scale(0.99);
transition: transform 0.2s;
}
}
/* 工单状态卡片 */
.ticket-card {
margin-top: 40rpx;
padding-bottom: 30rpx;
.ticket-header {
display: flex;
justify-content: space-between;
@ -332,38 +387,38 @@
padding: 20rpx;
background-color: #f8faff;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
.ticket-info {
.ticket-id {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.id-icon {
font-size: 32rpx;
color: #666;
margin-right: 8rpx;
}
.id-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.copy-icon {
font-size: 28rpx;
color: #009aff;
margin-left: 15rpx;
}
}
.create-time {
font-size: 26rpx;
color: #666;
}
}
.ticket-type {
padding: 6rpx 20rpx;
border-bottom-left-radius: 20rpx;
@ -371,57 +426,57 @@
font-weight: 500;
color: #37A5FF;
background-color: #e8f4ff;
&.type-F2 {
color: #ff9d00;
background-color: #fff6e9;
}
&.type-F6 {
color: #00b42a;
background-color: #e8ffea;
}
&.type-F4 {
color: #7e3ff2;
background-color: #f0e8ff;
}
}
}
.ticket-status {
display: flex;
align-items: center;
padding: 20rpx;
.status-icon {
margin-right: 20rpx;
font-size: 40rpx;
color: #009aff;
&.status-2 {
color: #00b42a;
}
&.status-3 {
color: #f42429;
}
&.status-0 {
color: #ff9d00;
}
}
.status-text {
display: flex;
flex-direction: column;
.status-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.status-desc {
font-size: 26rpx;
color: #999;
@ -429,11 +484,11 @@
}
}
}
/* 详情卡片样式 */
.info-card {
padding: 0 0 20rpx 0;
.card-title {
display: flex;
align-items: center;
@ -442,28 +497,28 @@
font-weight: 600;
color: #333;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
.title-icon {
margin-right: 10rpx;
color: #009aff;
}
}
.info-content {
padding: 20rpx 30rpx;
.info-item {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 25rpx;
.item-label {
width: 150rpx;
font-size: 26rpx;
color: #999;
}
.item-value {
width: 400rpx;
font-size: 26rpx;
@ -471,63 +526,134 @@
text-align: right;
word-break: break-all;
}
.phone-value {
display: flex;
align-items: center;
justify-content: flex-end;
.phone-icon {
color: #009aff;
margin-right: 8rpx;
}
.phone-number {
font-size: 26rpx;
color: #009aff;
font-weight: 500;
}
}
&.product-info {
align-items: flex-start;
.product-detail {
width: 400rpx;
display: flex;
flex-direction: column;
align-items: flex-end;
.product-name {
font-size: 26rpx;
color: #333;
text-align: right;
margin-bottom: 15rpx;
display: block;
margin-top: 30rpx;
.item-label {
display: block;
margin-bottom: 20rpx;
}
.product-list-full {
width: 100%;
background-color: #f9f9f9;
border-radius: 12rpx;
margin-bottom: 15rpx;
padding: 10rpx 0;
// border:solid 1px red;
.product-card-full {
width: 95%;
margin: 20rpx auto;
&:last-child {
margin-bottom: 0;
}
.product-main {
display: flex;
align-items: center;
width: 100%;
.product-image {
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
border: 1px solid rgba(0, 0, 0, 0.05);
background-color: #fff;
}
.product-details {
flex: 1;
padding-left: 20rpx;
.product-name {
font-size: 28rpx;
color: #333;
line-height: 1.4;
margin-bottom: 8rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.product-spec {
font-size: 24rpx;
color: #999;
}
.price-quantity {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15rpx;
.price {
font-size: 30rpx;
color: #FF6B00;
font-weight: 600;
}
.quantity {
font-size: 26rpx;
color: #666;
}
}
}
}
}
.product-images {
.order-summary {
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
.product-image {
width: 110rpx;
height: 110rpx;
border-radius: 10rpx;
margin-left: 20rpx;
margin-bottom: 10rpx;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
justify-content: space-between;
align-items: center;
width: 95%;
margin: 20rpx auto 10rpx;
// border-top: 1px solid #f0f0f0;
border-top: solid 1px #f0f0f0;
// padding: 10rpx 0;
padding-top: 10rpx;
.summary-count {
font-size: 26rpx;
color: #666;
}
.summary-price {
font-size: 30rpx;
color: #FF6B00;
font-weight: 600;
}
}
}
}
.scene-images {
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
width: 400rpx;
.scene-image {
width: 110rpx;
height: 110rpx;
@ -540,12 +666,12 @@
}
}
}
/* 时间线样式 */
.timeline {
padding: 10rpx 30rpx 10rpx 40rpx;
position: relative;
&::before {
content: '';
position: absolute;
@ -556,11 +682,11 @@
background-color: rgba(0, 154, 255, 0.2);
z-index: 1;
}
.timeline-item {
position: relative;
padding: 15rpx 0 15rpx 30rpx;
.timeline-dot {
position: absolute;
left: -12rpx;
@ -572,14 +698,14 @@
border: 4rpx solid rgba(0, 154, 255, 0.3);
z-index: 2;
}
.timeline-content {
.timeline-time {
font-size: 24rpx;
color: #999;
margin-bottom: 6rpx;
}
.timeline-text {
font-size: 28rpx;
color: #333;
@ -588,7 +714,7 @@
}
}
}
/* 底部按钮样式 */
.footer-actions {
position: fixed;
@ -601,22 +727,22 @@
background-color: #fff;
box-shadow: 0 -4rpx 10rpx rgba(0, 0, 0, 0.05);
z-index: 99;
.action-buttons {
display: flex;
justify-content: flex-end;
.action-btn {
min-width: 180rpx !important;
height: 80rpx !important;
border-radius: 40rpx !important;
font-size: 28rpx !important;
box-shadow: 0 6rpx 10rpx rgba(0, 0, 0, 0.1);
&.cancel-btn {
background: linear-gradient(to right, #ff6b6b, #ff4757) !important;
}
&.confirm-btn {
background: linear-gradient(to right, #5eafff, #4090ff) !important;
}

View File

@ -26,7 +26,7 @@
<text class="status-text">{{historyType=='1'?'入库成功':'核销成功'}}</text>
</view>
<text
class="status-time">{{codeInfo?.status == 2? codeInfo?.create_time: codeInfo?.distribute_time}}</text>
class="status-time">{{codeInfo?.status == 2? '正常入库': '正常核销'}}</text>
</view>
<view class="li-divider li-my-20"></view>
<view class="order-info li-px-30">
@ -107,15 +107,15 @@
<text
class="li-text-28 li-text-#333">{{codeInfo?.type==1?'普通订单':(codeInfo?.type==2?'秒杀订单':'拼团订单')}}</text>
</view>
<view class="detail-item li-flex li-justify-between li-mb-15">
<!-- <view class="detail-item li-flex li-justify-between li-mb-15">
<text class="li-text-28 li-text-#666">入库备注</text>
<text class="li-text-28 li-text-#333">正常入库</text>
</view>
</view> -->
</view>
</view>
<!-- 操作时间轴 -->
<view class="timeline-section li-w-92% li-mx-auto li-mt-30 li-rd-20 li-p-30 li-bg-white li-mb-40">
<!-- <view class="timeline-section li-w-92% li-mx-auto li-mt-30 li-rd-20 li-p-30 li-bg-white li-mb-40">
<view class="section-title li-mb-20">
<text class="ri-time-line li-text-38 li-text-#0070F0 li-mr-10"></text>
<text class="li-text-36 li-text-#333">操作记录</text>
@ -151,13 +151,13 @@
</view>
</view>
</view>
</view>
</view> -->
<!-- 底部按钮区域 -->
<view class="footer-actions li-w-92% li-mx-auto li-flex li-justify-between li-mb-30">
<!-- <view class="footer-actions li-w-92% li-mx-auto li-flex li-justify-between li-mb-30">
<wd-button custom-class="action-btn secondary" @click="printDetail">打印详情</wd-button>
<wd-button custom-class="action-btn primary" @click="shareDetail">分享详情</wd-button>
</view>
</view> -->
<zero-loading type="wobble" v-if="loading"></zero-loading>
<wd-toast />
</view>

File diff suppressed because it is too large Load Diff

View File

@ -771,8 +771,8 @@
.action-buttons {
padding: 30rpx;
padding-bottom: calc(30rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
// padding-bottom: calc(30rpx + constant(safe-area-inset-bottom));
// padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
background: #fff;
border-top: 2rpx solid rgba(0, 0, 0, 0.05);

View File

@ -16,10 +16,17 @@
<!-- #ifdef MP-WEIXIN -->
<template #right>
<view class="li-mr-180 li-flex li-items-center">
<view class="community-switch li-flex li-items-center" @click="showCommunityPicker = true">
<text class="li-text-30 li-text-primary li-mr-10">{{activeCommunity.name}}</text>
<text class="ri-arrow-down-s-line li-text-30"></text>
</view>
<!-- 小区选择器 -->
<!-- <wd-picker size="large" label-key="label" value-key="value" :columns="communityList" label="单列选项"
v-model="showCommunityPicker" @confirm="handleCommunityConfirm" v-model:value="selectedVillage" /> -->
<wd-picker size="large" label-key="label" value-key="value" :columns="communityList" label="单列选项"
v-model="showCommunityPicker" @confirm="handleCommunityConfirm" v-model:value="selectedVillage"
use-default-slot>
<view class="community-switch li-flex li-items-center" @click="showCommunityPicker = true">
<text class="li-text-28 li-text-primary li-mr-6">{{activeCommunity.name}}</text>
<text class="ri-arrow-down-s-line li-text-30"></text>
</view>
</wd-picker>
</view>
</template>
@ -27,10 +34,14 @@
<!-- #ifndef MP-WEIXIN -->
<template #right>
<view class="li-mr-25 li-flex li-items-center">
<view class="community-switch li-flex li-items-center" @click="showCommunityPicker = true">
<text class="li-text-30 li-text-primary li-mr-10">{{activeCommunity.name}}</text>
<text class="ri-arrow-down-s-line li-text-30"></text>
</view>
<wd-picker size="large" label-key="label" value-key="value" :columns="communityList" label="单列选项"
v-model="showCommunityPicker" @confirm="handleCommunityConfirm" v-model:value="selectedVillage"
use-default-slot>
<view class="community-switch li-flex li-items-center" @click="showCommunityPicker = true">
<text class="li-text-28 li-text-primary li-mr-6">{{activeCommunity.name}}</text>
<text class="ri-arrow-down-s-line li-text-30"></text>
</view>
</wd-picker>
</view>
</template>
<!-- #endif -->
@ -92,7 +103,8 @@
<text class="unit-text"
:class="activeUnitId === String(unit.value) ? 'unit-text-active' : ''">{{unit.label}}</text>
<view v-if="getUnitUnpaidCount(unit.value) > 0" class="unit-badge">
{{getUnitUnpaidCount(unit.value)}}</view>
{{getUnitUnpaidCount(unit.value)}}
</view>
</view>
</view>
</view>
@ -141,9 +153,7 @@
</view>
</view>
<!-- 小区选择器 -->
<!-- <wd-picker v-model="showCommunityPicker" :columns="communityList" title="选择小区"
v-model:value="selectedCommunity" @confirm="handleCommunityConfirm" label-key="name" value-key="id" /> -->
<!-- 房屋详情弹窗 -->
<wd-popup v-model="showHouseDetail" position="bottom">
@ -225,23 +235,19 @@
checkRouteStack
} = useNavigation();
//
const selectedVillage = ref('1')
const communityList = ref([
[{
id: '1',
name: '九仙花苑'
value: '1',
label: '阳光花园小区'
},
{
id: '2',
name: '阳光花园小区'
value: '2',
label: '翠湖庭院'
},
{
id: '3',
name: '翠湖庭院'
},
{
id: '4',
name: '金色家园'
value: '3',
label: '金色家园'
}
]
]);
@ -693,6 +699,7 @@
//
const handleCommunityConfirm = (selected) => {
console.log(selected, 'selected');
selectedCommunity.value = selected[0].id;
};
@ -769,7 +776,7 @@
.community-switch {
height: 60rpx;
padding: 10rpx 20rpx 0;
padding: 0rpx 25rpx;
border-radius: 30rpx;
background-color: rgba(62, 155, 255, 0.1);
}

View File

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

8
pagesB/housing/list.vue Normal file
View File

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

View File

@ -1,9 +1,441 @@
<template>
问卷详情
<view class="questionnaire-detail">
<!-- 自定义导航栏 -->
<wd-navbar :bordered="false"
custom-style="background: transparent !important; backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important;"
safeAreaInsetTop fixed placeholder>
<template #left>
<view class="li-ml-15 li-mt-10 li-flex li-items-center">
<text class="ri-arrow-left-s-line li-text-70" @click="navBack"></text>
<text class="li-text-42">问卷详情</text>
</view>
</template>
</wd-navbar>
<!-- 导航栏背景 -->
<view class="nav-bg-layer"></view>
<!-- 整页背景 -->
<view class="page-bg"></view>
<!-- 问卷基本信息卡片 -->
<view class="li-w-92% li-mx-auto li-mt-30">
<view class="info-card li-bg-white li-rd-20 li-p-30 li-shadow-sm">
<!-- 问卷标题 -->
<view class="li-flex li-items-center li-justify-between li-pb-20 li-bottom-border2">
<view class="li-flex li-items-center">
<text class="ri-questionnaire-line li-text-40 li-mr-15 li-text-#0070F0"></text>
<text class="li-text-34 li-font-bold">{{questionnaireDetail.title}}</text>
</view>
<wd-tag :color="getTypeColor(questionnaireDetail.type)" :bg-color="getTypeBgColor(questionnaireDetail.type)">
{{questionnaireDetail.type}}
</wd-tag>
</view>
<!-- 提交信息 -->
<view class="li-mt-20">
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">提交人</text>
<view class="li-flex li-items-center">
<text class="content-text li-text-28 li-text-#333">{{questionnaireDetail.user_name}}</text>
<view v-if="questionnaireDetail.is_anonymous" class="user-badge">匿名</view>
</view>
</view>
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">联系电话</text>
<text class="content-text li-text-28 li-text-#333">{{questionnaireDetail.contact_phone || '未提供'}}</text>
</view>
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">所属小区</text>
<text class="content-text li-text-28 li-text-#333">{{questionnaireDetail.village_name}}</text>
</view>
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">提交时间</text>
<text class="content-text li-text-28 li-text-#333">{{questionnaireDetail.submit_time}}</text>
</view>
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">问题数量</text>
<text class="content-text li-text-28 li-text-#333">{{questionnaireDetail.questions?.length || 0}}</text>
</view>
</view>
</view>
</view>
<!-- 问答内容区域 -->
<view class="li-w-92% li-mx-auto li-mt-30 li-pb-30">
<view class="content-card li-bg-white li-rd-20 li-p-30 li-shadow-sm">
<view class="li-flex li-items-center li-mb-20">
<text class="ri-question-answer-line li-text-40 li-mr-15 li-text-#0070F0"></text>
<text class="li-text-34 li-font-bold">问答内容</text>
</view>
<!-- 问答列表 -->
<view v-if="questionnaireDetail.questions && questionnaireDetail.questions.length > 0">
<view v-for="(question, index) in questionnaireDetail.questions" :key="index" class="question-item li-mb-30">
<view class="question-title li-mb-10">
<text class="question-index li-text-26 li-text-white bg-#0070F0">{{index + 1}}</text>
<text class="li-text-30 li-font-bold li-ml-15">{{question.title}}</text>
<text v-if="question.required" class="required-mark">*</text>
</view>
<!-- 问题类型标签 -->
<view class="question-type li-my-10">
<wd-tag size="small" color="#666" bg-color="#f5f5f5">{{getQuestionTypeName(question.type)}}</wd-tag>
</view>
<!-- 根据问题类型展示不同的答案内容 -->
<view class="answer-content li-p-20 li-bg-#f9f9f9 li-rd-10">
<!-- 单选题 -->
<view v-if="question.type === 'radio'" class="radio-answer">
<view v-for="(option, optionIndex) in question.options" :key="optionIndex"
class="option-item li-mb-10 li-py-10 li-px-20 li-rd-8"
:class="{'selected-option': question.answer === option.value}">
<text class="li-text-28">{{option.label}}</text>
</view>
</view>
<!-- 多选题 -->
<view v-else-if="question.type === 'checkbox'" class="checkbox-answer">
<view v-for="(option, optionIndex) in question.options" :key="optionIndex"
class="option-item li-mb-10 li-py-10 li-px-20 li-rd-8"
:class="{'selected-option': question.answer && question.answer.includes(option.value)}">
<text class="li-text-28">{{option.label}}</text>
</view>
</view>
<!-- 填空题 -->
<view v-else-if="question.type === 'text'" class="text-answer">
<text class="li-text-28">{{question.answer || '未填写'}}</text>
</view>
<!-- 评分题 -->
<view v-else-if="question.type === 'rating'" class="rating-answer">
<view class="rating-stars li-flex">
<text v-for="i in 5" :key="i"
:class="[
'ri-star-fill li-text-40 li-mr-15',
i <= question.answer ? 'li-text-#ff9900' : 'li-text-#dddddd'
]">
</text>
</view>
<text class="li-text-28 li-text-#666 li-mt-10">{{question.answer || 0}}</text>
</view>
<!-- 图片上传题 -->
<view v-else-if="question.type === 'image'" class="image-answer">
<view v-if="question.answer && question.answer.length" class="image-list li-flex li-flex-wrap">
<view v-for="(img, imgIndex) in question.answer" :key="imgIndex" class="image-item">
<image :src="img" mode="aspectFill" class="uploaded-image" @click="previewImage(question.answer, imgIndex)"></image>
</view>
</view>
<view v-else class="li-text-28 li-text-#999">未上传图片</view>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else class="empty-state li-py-60 li-flex-center li-flex-col">
<image src="https://img.yzcdn.cn/vant/empty-image-default.png" mode="aspectFit" class="empty-image"></image>
<text class="li-text-28 li-text-#999 li-mt-20">暂无问卷内容</text>
</view>
</view>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-mask">
<wd-loading color="#0070F0" />
</view>
<wd-toast />
</view>
</template>
<script>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { useToast } from '@/uni_modules/wot-design-uni';
const Toast = useToast();
//
const loading = ref<boolean>(false);
const questionnaireId = ref<string | number>('');
//
const questionnaireDetail = ref({
id: 2,
title: '小区环境改造意见征集',
village_name: '翠湖庭院',
type: '意见征集',
user_name: '李四',
contact_phone: '136****5678',
is_anonymous: true,
submit_time: '2024-06-12 09:45',
questions: [
{
id: 1,
title: '您对小区目前的环境满意度如何?',
type: 'radio',
required: true,
options: [
{ label: '非常满意', value: '5' },
{ label: '满意', value: '4' },
{ label: '一般', value: '3' },
{ label: '不满意', value: '2' },
{ label: '非常不满意', value: '1' }
],
answer: '3'
},
{
id: 2,
title: '您认为小区环境需要改进的地方有哪些?(可多选)',
type: 'checkbox',
required: true,
options: [
{ label: '绿化维护', value: '1' },
{ label: '健身设施', value: '2' },
{ label: '儿童游乐设施', value: '3' },
{ label: '休闲座椅', value: '4' },
{ label: '照明系统', value: '5' },
{ label: '停车位', value: '6' }
],
answer: ['1', '3', '5']
},
{
id: 3,
title: '请对小区环境改造提出您的具体建议',
type: 'text',
required: false,
answer: '希望能增加更多的绿植,并加强对现有绿化的养护。小区内的照明系统也需要升级,部分区域晚上太暗影响出行安全。'
},
{
id: 4,
title: '请对小区物业服务进行评分',
type: 'rating',
required: true,
answer: 4
},
{
id: 5,
title: '如有小区环境问题,请上传照片',
type: 'image',
required: false,
answer: [
'https://img.yzcdn.cn/vant/cat.jpeg',
'https://img.yzcdn.cn/vant/leaf.jpg'
]
}
]
});
//
const getTypeColor = (type) => {
const colorMap = {
'满意度调查': '#0070F0',
'意见征集': '#ff9900',
'投票选举': '#07c160',
'需求调研': '#9254de',
'活动报名': '#ff4d4f'
};
return colorMap[type] || '#666666';
};
//
const getTypeBgColor = (type) => {
const bgColorMap = {
'满意度调查': '#e8f4ff',
'意见征集': '#fff6e9',
'投票选举': '#e8fff1',
'需求调研': '#f5f0ff',
'活动报名': '#ffeded'
};
return bgColorMap[type] || '#f5f5f5';
};
//
const getQuestionTypeName = (type) => {
const typeMap = {
'radio': '单选题',
'checkbox': '多选题',
'text': '填空题',
'rating': '评分题',
'image': '图片上传'
};
return typeMap[type] || '未知类型';
};
//
const previewImage = (images, index) => {
uni.previewImage({
urls: images,
current: index
});
};
//
const navBack = () => {
uni.navigateBack();
};
//
const loadQuestionnaireDetail = async () => {
if (!questionnaireId.value) return;
loading.value = true;
try {
// API
await new Promise(resolve => setTimeout(resolve, 1000));
// API
// questionnaireDetail.value = res.data;
loading.value = false;
} catch (error) {
console.error('加载问卷详情失败', error);
Toast.fail('加载失败,请重试');
loading.value = false;
}
};
onLoad((options) => {
if (options.id) {
questionnaireId.value = options.id;
loadQuestionnaireDetail();
}
});
</script>
<style>
<style lang="scss">
page {
background-color: #F7F8FA;
}
.questionnaire-detail {
min-height: 100vh;
position: relative;
z-index: 0;
padding-bottom: 40rpx;
}
.nav-bg-layer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: calc(var(--status-bar-height) + 88rpx);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.9) 100%);
z-index: 10;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.page-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg, rgba(247, 248, 250, 0.9) 0%, rgba(247, 248, 250, 1) 100%);
z-index: -2;
}
.info-card, .content-card {
transition: all 0.3s;
position: relative;
z-index: 1;
}
.li-bottom-border2 {
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.user-badge {
display: inline-block;
font-size: 22rpx;
background-color: #f5f5f5;
color: #999;
padding: 2rpx 12rpx;
border-radius: 16rpx;
margin-left: 16rpx;
}
.question-item {
.question-title {
display: flex;
align-items: center;
.question-index {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36rpx;
height: 36rpx;
border-radius: 18rpx;
font-size: 22rpx;
}
.required-mark {
color: #ff4d4f;
margin-left: 10rpx;
}
}
.answer-content {
.option-item {
transition: all 0.2s;
background-color: #f2f2f2;
&.selected-option {
background-color: #e8f4ff;
color: #0070F0;
font-weight: 500;
}
}
.image-list {
margin-top: 16rpx;
.image-item {
width: 180rpx;
height: 180rpx;
margin-right: 16rpx;
margin-bottom: 16rpx;
.uploaded-image {
width: 100%;
height: 100%;
border-radius: 8rpx;
}
}
}
}
}
.empty-state {
.empty-image {
width: 200rpx;
height: 200rpx;
}
}
.loading-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}
::v-deep .wd-navbar__placeholder {
height: calc(var(--status-bar-height) + 88rpx) !important;
padding-top: constant(safe-area-inset-top) !important;
padding-top: env(safe-area-inset-top) !important;
}
</style>

View File

@ -10,34 +10,33 @@
@click="toPages({type:'nav'})"></text>
<text v-if="!hasMultiplePages" class="ri-home-5-line li-text-55 li-mb-8 li-mr-10"
@click="toPages({type:'home'})"></text>
<text class="li-text-42">问卷管理</text>
<text class="li-text-42">问卷记录</text>
</view>
</template>
<!-- #ifdef MP-WEIXIN -->
<template #right>
<view class="li-flex-center li-mr-200" @click="createQuestionnaire">
<text class="ri-add-line li-text-52"></text>
<view class="li-flex-center li-mr-200 li-pt-6" @click="showFilter">
<text class="ri-filter-line li-text-48"></text>
</view>
</template>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<template #right>
<view class="li-flex-center li-mr-25" @click="createQuestionnaire">
<text class="ri-add-line li-text-52"></text>
<view class="li-flex-center li-mr-25 li-pt-6" @click="showFilter">
<text class="ri-filter-line li-text-48"></text>
</view>
</template>
<!-- #endif -->
</wd-navbar>
<!-- 导航栏背景 -->
<view class="nav-bg-layer"></view>
<!-- 整页背景 -->
<view class="page-bg"></view>
<!-- 筛选区域 -->
<view class="filter-section li-w-92% li-mx-auto li-mt-30">
<!-- 问卷状态筛选 -->
<view class="li-flex li-items-center li-flex-wrap">
<text v-for="(item, index) in statusList" :key="index"
@ -47,12 +46,12 @@
</text>
</view>
</view>
<!-- 小区选择器 -->
<view class="li-px-15">
<wd-picker size="large" v-model="showVillagePicker" :columns="pickerVillageList" title="选择小区"
v-model:value="selectedVillage" @confirm="handleVillageConfirm" label-key="label" value-key="value" />
</view>
<!-- 小区选择器 -->
<view class="li-px-15">
<wd-picker size="large" v-model="showVillagePicker" :columns="pickerVillageList" title="选择小区"
v-model:value="selectedVillage" @confirm="handleVillageConfirm" label-key="label" value-key="value" />
</view>
<!-- 问卷列表 -->
<view class="li-w-92% li-mx-auto li-mt-30 li-pb-30">
@ -76,6 +75,14 @@
<!-- 问卷信息 -->
<view class="li-mt-20">
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">提交人</text>
<view class="li-flex li-items-center">
<text class="content-text li-text-28 li-text-#333">{{item.user_name}}</text>
<view class="user-badge" v-if="item.is_anonymous">匿名</view>
</view>
</view>
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">所属小区</text>
<text class="content-text li-text-28 li-text-#333">{{item.village_name}}</text>
@ -87,25 +94,18 @@
</view>
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">参与人数</text>
<text class="content-text li-text-28 li-text-#333">{{item.participant_count}}</text>
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">提交时间</text>
<text class="content-text li-text-28 li-text-#333">{{item.submit_time}}</text>
</view>
<!-- 修改有效期和操作按钮的布局 -->
<view class="li-flex li-flex-col">
<view class="li-flex li-items-center li-mb-15">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">有效期</text>
<text class="content-text li-text-28 li-text-#333">{{item.start_time}} ~
{{item.end_time}}</text>
<view class="li-flex li-items-center li-justify-between">
<view class="li-flex li-items-center">
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">问题数量</text>
<text class="content-text li-text-28 li-text-#333">{{item.question_count}}</text>
</view>
<view class="li-flex li-items-center li-justify-between" @click.stop>
<view></view>
<view class="li-flex li-items-center ">
<wd-button type="primary" size="small" plain custom-class="action-btn"
@click="handleEdit(item)">编辑</wd-button>
<wd-button type="danger" size="small" plain custom-class="action-btn"
@click="handleDelete(item)">删除</wd-button>
</view>
<view class="action-wrapper">
<wd-button type="primary" size="small" custom-class="view-btn"
@click.stop="toPages({type:'detail', value:item})">查看详情</wd-button>
</view>
</view>
</view>
@ -116,7 +116,7 @@
<view v-else class="empty-state li-mt-100">
<image src="https://img.yzcdn.cn/vant/empty-image-default.png" mode="aspectFit" class="empty-image">
</image>
<text class="li-text-28 li-text-#999 li-mt-20">暂无问卷数据</text>
<text class="li-text-28 li-text-#999 li-mt-20">暂无问卷记录</text>
</view>
<!-- 加载状态 -->
@ -130,6 +130,71 @@
-- 没有更多数据了 --
</view>
</view>
<!-- 筛选弹窗 -->
<wd-popup v-model="showFilterPopup" position="right" custom-class="filter-popup">
<view class="filter-popup-content">
<view class="filter-popup-header li-flex li-justify-between li-items-center li-p-30 ">
<text class="li-text-36 li-font-bold">筛选类型</text>
<text class="ri-close-line li-text-40" @click="showFilterPopup = false"></text>
</view>
<view class="filter-popup-body li-p-30">
<view class="filter-section-title li-mb-20">提交时间</view>
<view class="date-range li-mb-30">
<view class="li-flex li-items-center li-justify-between li-mb-20">
<wd-button size="small" custom-class="date-btn"
:class="{'date-btn-active': dateRange === 'today'}"
@click="setDateRange('today')">今天</wd-button>
<wd-button size="small" custom-class="date-btn"
:class="{'date-btn-active': dateRange === 'yesterday'}"
@click="setDateRange('yesterday')">昨天</wd-button>
<wd-button size="small" custom-class="date-btn"
:class="{'date-btn-active': dateRange === 'week'}"
@click="setDateRange('week')">近7天</wd-button>
<wd-button size="small" custom-class="date-btn"
:class="{'date-btn-active': dateRange === 'month'}"
@click="setDateRange('month')">近30天</wd-button>
</view>
<view class="li-flex li-items-center li-justify-between">
<wd-datetime-picker v-model="showStartPicker" title="开始日期" v-model:value="startDate"
type="date" @confirm="handleDateChange">
<wd-input size="large" placeholder="开始日期" class="date-input"
:value="formatDate(startDate)" disabled></wd-input>
</wd-datetime-picker>
<text class="li-text-30 li-mx-20"></text>
<wd-datetime-picker v-model="showEndPicker" title="结束日期" v-model:value="endDate" type="date"
@confirm="handleDateChange">
<wd-input size="large" placeholder="结束日期" class="date-input"
:value="formatDate(endDate)" disabled></wd-input>
</wd-datetime-picker>
</view>
</view>
<view class="filter-section-title li-mb-20">问卷类型</view>
<view class="type-filter li-flex li-flex-wrap li-mb-30">
<wd-tag v-for="(type, index) in typeList" :key="index"
:color="selectedTypes.includes(type.value) ? '#0070F0' : '#666'"
:bg-color="selectedTypes.includes(type.value) ? '#e8f4ff' : '#f5f5f5'"
custom-class="type-tag" @click="toggleTypeSelection(type.value)">
{{type.label}}
</wd-tag>
</view>
<view class="filter-section-title li-mb-20">其他筛选</view>
<view class="other-filters li-mb-30">
<view class="li-flex li-items-center li-justify-between li-mb-20">
<text class="li-text-30">只看匿名提交</text>
<wd-switch v-model="filterAnonymous" active-color="#0070F0" />
</view>
</view>
</view>
<view class="filter-popup-footer li-p-30 li-flex li-justify-between">
<wd-button size="large" custom-class="reset-btn" @click="resetFilters">重置</wd-button>
<wd-button size="large" type="primary" custom-class="confirm-btn"
@click="applyFilters">确定</wd-button>
</view>
</view>
</wd-popup>
</view>
</template>
@ -148,9 +213,6 @@
checkRouteStack
} = useNavigation();
//
const hasPermission = ref(true); //
//
const selectedVillage = ref('ALL'); //
const activeStatus = ref('ALL');
@ -159,6 +221,16 @@
const page = ref(1);
const pageSize = ref(10);
//
const showFilterPopup = ref(false);
const dateRange = ref('');
const startDate = ref('');
const endDate = ref('');
const showStartPicker = ref(false);
const showEndPicker = ref(false);
const selectedTypes = ref([]);
const filterAnonymous = ref(false);
//
const villageList = ref([
{ value: '1', label: '阳光花园小区' },
@ -166,12 +238,20 @@
{ value: '3', label: '金色家园' }
]);
//
const typeList = ref([
{ value: '1', label: '满意度调查' },
{ value: '2', label: '意见征集' },
{ value: '3', label: '投票选举' },
{ value: '4', label: '需求调研' },
{ value: '5', label: '活动报名' }
]);
//
const statusList = ref([
{ label: '全部', value: 'ALL' },
{ label: '未开始', value: 0 },
{ label: '进行中', value: 1 },
{ label: '已结束', value: 2 }
{ label: '已查看', value: 1 },
{ label: '未查看', value: 0 }
]);
//
@ -181,59 +261,63 @@
title: '2024年度物业服务满意度调查',
village_name: '阳光花园小区',
type: '满意度调查',
participant_count: 156,
start_time: '2024-06-01',
end_time: '2024-06-30',
status: 1
user_name: '张三',
is_anonymous: false,
submit_time: '2024-06-15 14:23',
question_count: 15,
status: 1 //
},
{
id: 2,
title: '小区环境改造意见征集',
village_name: '阳光花园小区',
village_name: '翠湖庭院',
type: '意见征集',
participant_count: 89,
start_time: '2024-07-01',
end_time: '2024-07-15',
status: 0
user_name: '李四',
is_anonymous: true,
submit_time: '2024-06-12 09:45',
question_count: 8,
status: 0 //
},
{
id: 3,
title: '业主委员会换届选举投票',
village_name: '阳光花园小区',
village_name: '金色家园',
type: '投票选举',
participant_count: 245,
start_time: '2024-05-01',
end_time: '2024-05-15',
status: 2
user_name: '王五',
is_anonymous: false,
submit_time: '2024-06-10 18:30',
question_count: 12,
status: 1 //
},
{
id: 3,
title: '业主委员会换届选举投票',
id: 4,
title: '小区健身设施需求调研',
village_name: '阳光花园小区',
type: '投票选举',
participant_count: 245,
start_time: '2024-05-01',
end_time: '2024-05-15',
status: 2
type: '需求调研',
user_name: '赵六',
is_anonymous: false,
submit_time: '2024-06-08 11:20',
question_count: 10,
status: 1 //
},
{
id: 3,
title: '业主委员会换届选举投票',
village_name: '阳光花园小区',
type: '投票选举',
participant_count: 245,
start_time: '2024-05-01',
end_time: '2024-05-15',
status: 2
id: 5,
title: '端午节活动满意度调查',
village_name: '翠湖庭院',
type: '满意度调查',
user_name: '匿名用户',
is_anonymous: true,
submit_time: '2024-06-05 16:40',
question_count: 6,
status: 0 //
}
]);
//
const getStatusColor = (status) => {
const colorMap = {
0: '#ff9d00', //
1: '#0070F0', //
2: '#999999' //
0: '#ff9d00', //
1: '#07c160' //
};
return colorMap[status] || '#666666';
};
@ -241,9 +325,8 @@
//
const getStatusBgColor = (status) => {
const bgColorMap = {
0: '#fff6e9', //
1: '#e8f4ff', //
2: '#f5f5f5' //
0: '#fff6e9', //
1: '#e8fff1' //
};
return bgColorMap[status] || '#f5f5f5';
};
@ -251,9 +334,8 @@
//
const getStatusText = (status) => {
const textMap = {
0: '未开始',
1: '进行中',
2: '已结束'
0: '未查看',
1: '已查看'
};
return textMap[status] || '未知状态';
};
@ -287,43 +369,12 @@
}
loading.value = false;
} catch (error) {
console.error('加载问卷列表失败', error);
console.error('加载问卷记录失败', error);
Toast.fail('加载失败,请重试');
loading.value = false;
}
};
//
const createQuestionnaire = () => {
uni.navigateTo({
url: '/pagesB/questionnaire/edit'
});
};
//
const handleEdit = (item) => {
uni.navigateTo({
url: `/pagesB/questionnaire/edit?id=${item.id}`
});
};
//
const handleDelete = (item) => {
uni.showModal({
title: '提示',
content: '确定要删除该问卷吗?',
success: async (res) => {
if (res.confirm) {
Toast.loading('删除中...');
// API
await new Promise(resolve => setTimeout(resolve, 800));
Toast.success('删除成功');
loadQuestionnaireList();
}
}
});
};
//
const toPages = (item) => {
switch (item.type) {
@ -338,6 +389,11 @@
});
break;
case 'detail':
//
const record = questionnaireList.value.find(q => q.id === item.value.id);
if (record && record.status === 0) {
record.status = 1;
}
uni.navigateTo({
url: `/pagesB/questionnaire/detail?id=${item.value.id}`
});
@ -372,7 +428,7 @@
loadQuestionnaireList();
});
//
//
const showVillagePicker = ref(false);
const pickerVillageList = ref([
[
@ -383,17 +439,84 @@
]
]);
//
const getVillageName = (value) => {
const village = villageList.value.find(item => item.value === value);
return village ? village.label : '未知小区';
};
const handleVillageConfirm = (selectedItem) => {
selectedVillage.value = selectedItem[0].value;
resetList();
loadQuestionnaireList();
};
//
const showFilter = () => {
showFilterPopup.value = true;
};
const setDateRange = (range) => {
dateRange.value = range;
const now = new Date();
switch (range) {
case 'today':
startDate.value = formatDate(now);
endDate.value = formatDate(now);
break;
case 'yesterday':
const yesterday = new Date(now);
yesterday.setDate(now.getDate() - 1);
startDate.value = formatDate(yesterday);
endDate.value = formatDate(yesterday);
break;
case 'week':
const weekStart = new Date(now);
weekStart.setDate(now.getDate() - 7);
startDate.value = formatDate(weekStart);
endDate.value = formatDate(now);
break;
case 'month':
const monthStart = new Date(now);
monthStart.setDate(now.getDate() - 30);
startDate.value = formatDate(monthStart);
endDate.value = formatDate(now);
break;
}
};
const formatDate = (date) => {
if (!date) return '';
if (typeof date === 'string') return date;
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const handleDateChange = () => {
dateRange.value = '';
};
const toggleTypeSelection = (typeValue) => {
const index = selectedTypes.value.indexOf(typeValue);
if (index > -1) {
selectedTypes.value.splice(index, 1);
} else {
selectedTypes.value.push(typeValue);
}
};
const resetFilters = () => {
dateRange.value = '';
startDate.value = '';
endDate.value = '';
selectedTypes.value = [];
filterAnonymous.value = false;
};
const applyFilters = () => {
showFilterPopup.value = false;
resetList();
loadQuestionnaireList();
Toast.success('筛选条件已应用');
};
</script>
<style lang="scss">
@ -418,7 +541,7 @@
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.page-bg {
position: fixed;
top: 0;
@ -432,7 +555,12 @@
.filter-section {
padding: 20rpx 0 0;
position: sticky;
top: calc(var(--status-bar-height) + 88rpx);
/* #ifdef MP-WEIXIN */
top: calc(var(--status-bar-height) + 110rpx);
/* #endif */
/* #ifndef MP-WEIXIN */
top: calc(var(--status-bar-height) + 85rpx);
/* #endif */
z-index: 9;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(10px);
@ -499,14 +627,24 @@
width: 240rpx;
height: 240rpx;
}
}
.action-btn {
width: 100rpx !important;
margin-left: 15rpx !important;
.view-btn {
width: 160rpx !important;
font-size: 26rpx !important;
height: 60rpx !important;
border-radius: 30rpx !important;
}
.user-badge {
display: inline-block;
font-size: 22rpx;
background-color: #f5f5f5;
color: #999;
padding: 2rpx 12rpx;
border-radius: 16rpx;
margin-left: 16rpx;
}
::v-deep .wd-navbar__placeholder {
height: calc(var(--status-bar-height) + 88rpx) !important;
@ -517,7 +655,7 @@
::v-deep .wd-select {
background-color: transparent !important;
margin-bottom: 0 !important;
.wd-select__value {
color: #333 !important;
}
@ -531,6 +669,78 @@
color: #666 !important;
}
.filter-popup {
width: 80%;
height: 100%;
.filter-popup-content {
display: flex;
flex-direction: column;
height: 100%;
}
.filter-popup-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
/* #ifdef MP-WEIXIN */
margin-top: 200rpx;
/* #endif */
/* #ifndef MP-WEIXIN */
margin-top: 100rpx;
/* #endif */
}
.filter-popup-body {
flex: 1;
overflow-y: auto;
}
.filter-popup-footer {
border-top: 1px solid rgba(0, 0, 0, 0.05);
.reset-btn {
width: 45% !important;
background-color: #f5f5f5 !important;
color: #666 !important;
}
.confirm-btn {
width: 45% !important;
}
}
}
.filter-section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.date-btn {
width: 22% !important;
height: 70rpx !important;
background-color: #f5f5f5 !important;
color: #666 !important;
border: none !important;
&-active {
background-color: #e8f4ff !important;
color: #0070F0 !important;
font-weight: 500;
}
}
.date-input {
width: 45% !important;
background-color: #f5f5f5 !important;
}
.type-tag {
margin: 0 16rpx 16rpx 0 !important;
padding: 16rpx 24rpx !important;
border-radius: 8rpx !important;
font-size: 26rpx !important;
}
.village-filter {
.select-box {
background: rgba(255, 255, 255, 0.9);

View File

@ -1,100 +1,32 @@
.li-bg-white{background-color:rgb(255,255,255)}
.li-flex{display:flex}
.li-items-center{align-items:center}
.li-justify-between{justify-content:space-between}
.li-mb-10{margin-bottom:10rpx}
.li-mb-15{margin-bottom:15rpx}
.li-mb-20{margin-bottom:20rpx}
.li-mb-30{margin-bottom:30rpx}
.li-mb-40{margin-bottom:40rpx}
.li-mb-8{margin-bottom:8rpx}
.li-ml-15{margin-left:15rpx}
.li-ml-20{margin-left:20rpx}
.li-mr-10{margin-right:10rpx}
.li-mt-10{margin-top:10rpx}
.li-mt-20{margin-top:20rpx}
.li-mt-30{margin-top:30rpx}
.li-mx-auto{margin-left:auto;margin-right:auto}
.li-my-20{margin-top:20rpx;margin-bottom:20rpx}
.li-p-30{padding:30rpx}
.li-px-30{padding-left:30rpx;padding-right:30rpx}
.li-py-30{padding-top:30rpx;padding-bottom:30rpx}
.li-rd-20{border-radius:20rpx}
.li-text-0070F0-color{color:rgb(0,112,240)}
.li-text-07c160-color{color:rgb(7,193,96)}
.li-text-26{font-size:26rpx}
.li-text-28{font-size:28rpx}
.li-text-30{font-size:30rpx}
.li-text-32{font-size:32rpx}
.li-text-333-color{color:rgb(51,51,51)}
.li-text-36{font-size:36rpx}
.li-text-38{font-size:38rpx}
.li-text-42{font-size:42rpx}
.li-text-55{font-size:55rpx}
.li-text-666-color{color:rgb(102,102,102)}
.li-text-70{font-size:70rpx}
.li-text-999-color{color:rgb(153,153,153)}
.li-w-full-92{width:92%}
.li-flex-center{display:flex;align-items:center;justify-content:center}
.li-flex-col{flex-direction:column}
.li-items-end{align-items:end}
.li-justify-center{justify-content:center}
.li-ml-10{margin-left:10rpx}
.li-mr-25{margin-right:25rpx}
.li-mr-4{margin-right:4rpx}
.li-mt-100{margin-top:100rpx}
.li-mt-100-important{margin-top:100rpx !important}
.li-mt-15{margin-top:15rpx}
.li-mt-4{margin-top:4rpx}
.li-mt-40{margin-top:40rpx}
.li-mt-50{margin-top:50rpx}
.li-mt-8{margin-top:8rpx}
.li-pb-10{padding-bottom:10rpx}
.li-px-40{padding-left:40rpx;padding-right:40rpx}
.li-py-25{padding-top:25rpx;padding-bottom:25rpx}
.li-py-40{padding-top:40rpx;padding-bottom:40rpx}
.li-text-009aff-color{color:rgb(0,154,255)}
.li-text-24{font-size:24rpx}
.li-text-34{font-size:34rpx}
.li-text-44{font-size:44rpx}
.li-text-48{font-size:48rpx}
.li-text-90{font-size:90rpx}
.li-text-center{text-align:center}
.li-text-white{color:rgb(255,255,255)}
.li-w-500{width:500rpx}
.li-w-full-90{width:90%}
.items-center{align-items:center}
.justify-center{justify-content:center}
.justify-between{justify-content:space-between}
.li-font-bold{font-weight:bold}
.li-h-130{height:130rpx}
.li-h-160{height:160rpx}
.li-h-220{height:220rpx}
.li-h-58{height:58rpx}
.li-justify-around{justify-content:space-around}
.li-ml-22{margin-left:22rpx}
.li-ml-25{margin-left:25rpx}
.li-ml-35{margin-left:35rpx}
.li-mr-5{margin-right:5rpx}
.li-mt-12{margin-top:12rpx}
.li-mt-14{margin-top:14rpx}
.li-pt-15{padding-top:15rpx}
.li-px-25{padding-left:25rpx;padding-right:25rpx}
.li-px-50{padding-left:50rpx;padding-right:50rpx}
.li-rd-10{border-radius:10rpx}
.li-rd-15{border-radius:15rpx}
.li-text-010B3E-color{color:rgb(1,11,62)}
.li-text-22{font-size:22rpx}
.li-text-343333-color{color:rgb(52,51,51)}
.li-text-43{font-size:43rpx}
.li-text-AFB2B8-color{color:rgb(175,178,184)}
.li-text-B1B0B0-color{color:rgb(177,176,176)}
.li-text-F42429-color{color:rgb(244,36,41)}
.li-w-130{width:130rpx}
.li-w-310{width:310rpx}
.li-w-58{width:58rpx}
.li-w-full-70{width:70%}
.li-w-full-88{width:88%}
.pt-10{padding-top:10rpx}
.li-mt-25{margin-top:25rpx}
.bg-0070F0{background-color:rgb(0,112,240)}
.li-my-10{margin-top:10rpx;margin-bottom:10rpx}
.li-p-20{padding:20rpx}
.li-px-20{padding-left:20rpx;padding-right:20rpx}
.li-py-10{padding-top:10rpx;padding-bottom:10rpx}
.li-rd-8{border-radius:8rpx}
.li-text-dddddd-color{color:rgb(221,221,221)}
.li-text-ff9900-color{color:rgb(255,153,0)}
.li-pt-6{padding-top:6rpx}
.li-px-15{padding-left:15rpx;padding-right:15rpx}
.li-opacity-80{undefined:0.8}
.li-pb-40{padding-bottom:40rpx}
.li-py-100{padding-top:100rpx;padding-bottom:100rpx}
.li-py-15{padding-top:15rpx;padding-bottom:15rpx}
.li-rd-30{border-radius:30rpx}
.li-text-100{font-size:100rpx}
.li-text-3e9bff-color{color:rgb(62,155,255)}
.li-text-50{font-size:50rpx}
.li-text-52c41a-color{color:rgb(82,196,26)}
.li-text-60{font-size:60rpx}
.li-text-ccc-color{color:rgb(204,204,204)}
.li-text-ff4d4f-color{color:rgb(255,77,79)}