小程序供需发布+图书管理修改

dev
linyongLynn 8 months ago
parent 71593c5df8
commit 60f15cd966

@ -58,7 +58,7 @@
const userId = options.userId;
const userName = options.userName;
const supplyDemandId = options.supplyDemandId;
const supplyDemandTitle = options.supplyDemandTitle ? decodeURIComponent(options.supplyDemandTitle) : '';
const supplyDemandTitle = options.supplyDemandTitle;
// ID
this.supplyDemandId = supplyDemandId;
@ -82,6 +82,7 @@
// 使API
this.$u.api.supplyDemandMessageList({
to_user_id: this.theirUserId,
supply_demand_id: supplyDemandId,
page: page,
per_page: this.pageSize

@ -13,13 +13,13 @@
<u-image :src="bookInfo.image" width="240rpx" height="320rpx" border-radius="12"></u-image>
</view>
<view class="book-info">
<text class="book-title">{{ bookInfo.title }}</text>
<text class="book-author">作者{{ bookInfo.author }}</text>
<text class="book-publisher">出版社{{ bookInfo.publisher }}</text>
<text class="book-year">出版年份{{ bookInfo.year }}</text>
<text class="book-isbn">ISBN{{ bookInfo.isbn }}</text>
<text class="book-title">{{ bookInfo.title || '暂无标题' }}</text>
<text class="book-author">作者{{ bookInfo.author || '暂无' }}</text>
<text class="book-publisher">出版社{{ bookInfo.publisher || '暂无' }}</text>
<text class="book-year">出版年份{{ bookInfo.year || '暂无' }}</text>
<text class="book-isbn">ISBN{{ bookInfo.isbn || '暂无' }}</text>
<view class="book-category">
<u-tag :text="bookInfo.category" type="primary" size="mini" shape="circle" mode="light" />
<u-tag :text="bookInfo.category || '未分类'" type="primary" size="mini" shape="circle" mode="light" />
</view>
</view>
</view>
@ -37,10 +37,6 @@
<text class="status-label">状态</text>
<u-tag :text="getStatusText(bookInfo.status)" :type="getStatusType(bookInfo.status)" size="mini" />
</view>
<view class="status-item">
<text class="status-label">创建时间</text>
<text class="status-value">{{ formatDate(bookInfo.created_at) }}</text>
</view>
</view>
</view>
@ -99,7 +95,6 @@
category: book.category,
description: book.description,
status: book.status,
created_at: book.created_at,
image: book.cover ? book.cover.url : ''
};
} else {
@ -139,13 +134,6 @@
3: 'error'
};
return typeMap[status] || 'info';
},
//
formatDate(dateStr) {
if (!dateStr) return '未知';
const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN');
}
}
}

@ -28,9 +28,9 @@
<u-image :src="book.image" width="180rpx" height="240rpx" border-radius="8"></u-image>
</view>
<view class="book-info">
<text class="book-title">{{ book.title }}</text>
<text class="book-author">{{ book.author }}</text>
<text class="book-publisher">{{ book.publisher }} · {{ book.year }}</text>
<text class="book-title">{{ book.title || '暂无标题' }}</text>
<text class="book-author">{{ book.author || '暂无作者' }}</text>
<text class="book-publisher">{{ (book.publisher || '暂无') }} · {{ book.year || '暂无' }}</text>
<view class="tags-container">
<u-tag v-for="(tag, index) in book.tags" :key="index" :text="tag.text" :type="tag.type"
size="mini" shape="circle" mode="light" />
@ -139,10 +139,10 @@
const newBooks = (bookData.data || []).map(book => {
return {
id: book.id,
title: book.title,
author: book.author,
publisher: book.publisher,
year: book.publish_year,
title: book.title || '暂无标题',
author: book.author || '暂无作者',
publisher: book.publisher || '暂无',
year: book.publish_year || '暂无',
image: book.cover ? book.cover.url : '',
tags: [
{
@ -150,7 +150,7 @@
type: 'primary'
},
{
text: `ISBN: ${book.isbn || '未知'}`,
text: `ISBN: ${book.isbn || '暂无'}`,
type: 'info'
}
]

@ -1,31 +1,104 @@
<template>
<view class="container" v-if="detail">
<view class="user-info-header">
<u-avatar :src="detail.user && detail.user.headimgurl || 'https://via.placeholder.com/80'" size="80"></u-avatar>
<view class="user-details">
<text class="user-name">{{ detail.user && detail.user.name || detail.user && detail.user.nickname || '匿名用户' }}</text>
<text class="post-time">{{ detail.created_at }}</text>
<scroll-view scroll-y="true" class="scroll-container">
<view class="user-info-header">
<u-avatar :src="detail.user && detail.user.headimgurl || 'https://via.placeholder.com/80'" size="80"></u-avatar>
<view class="user-details">
<text class="user-name">{{ detail.user && detail.user.name || detail.user && detail.user.nickname || '匿名用户' }}</text>
<text class="post-time">{{ detail.created_at }}</text>
</view>
<view class="stats">
<text class="type-badge" :class="detail.type === 1 ? 'supply' : 'demand'">{{ detail.type === 1 ? '供应' : '需求' }}</text>
<text class="views">{{ detail.contact_count }}人私信 {{ detail.view_count }}浏览</text>
</view>
</view>
<view class="stats">
<text class="type-badge" :class="detail.type === 1 ? 'supply' : 'demand'">{{ detail.type === 1 ? '供应' : '需求' }}</text>
<text class="views">{{ detail.contact_count }}人私信 {{ detail.view_count }}浏览</text>
<view class="content-card">
<text class="title">{{ detail.title }}</text>
<text class="description">{{ detail.content }}</text>
<!-- 图片展示区域 -->
<view class="images-container" v-if="detail.files && detail.files.length > 0">
<view class="images-title">
<u-icon name="photo-fill" color="#cba579" size="24"></u-icon>
<text class="images-title-text">相关图片 ({{ imageFiles.length }})</text>
</view>
<view class="images-grid" v-if="imageFiles.length > 0">
<view v-for="(file, index) in imageFiles" :key="file.id" class="image-item" @click="previewImage(file, index)">
<image :src="file.url" mode="aspectFill" class="content-image"></image>
<view class="image-overlay">
<u-icon name="eye-fill" color="#fff" size="20"></u-icon>
</view>
</view>
</view>
</view>
<view class="tags" v-if="detail.tag">
<text v-for="tag in detail.tag.split(',')" :key="tag" class="tag">{{ tag }}</text>
</view>
<!-- 过期提示 -->
<view v-if="isExpired" class="expired-badge">
<u-icon name="clock-fill" color="#909399" size="24"></u-icon>
<text class="expired-badge-text">已过期</text>
</view>
</view>
</view>
<view class="content-card">
<text class="title">{{ detail.title }}</text>
<text class="description">{{ detail.content }}</text>
<view class="tags" v-if="detail.tag">
<text v-for="tag in detail.tag.split(',')" :key="tag" class="tag">{{ tag }}</text>
<!-- 联系方式卡片 -->
<view class="contact-card" v-if="hasContactInfo">
<view class="contact-header">
<u-icon name="phone-fill" color="#cba579" size="32"></u-icon>
<text class="contact-title">联系方式</text>
</view>
<view class="contact-content" v-if="!needMessageToShowContact">
<!-- 联系人信息 -->
<view class="contact-item" v-if="detail.user">
<view class="contact-label">
<u-icon name="account-fill" color="#cba579" size="24"></u-icon>
<text class="label-text">联系人</text>
</view>
<text class="contact-value">{{ detail.user.name || detail.user.nickname || '匿名用户' }}</text>
</view>
<view class="contact-item" v-if="detail.wechat">
<view class="contact-label">
<u-icon name="chat-fill" color="#07c160" size="24"></u-icon>
<text class="label-text">微信</text>
</view>
<text class="contact-value">{{ detail.wechat }}</text>
</view>
<view class="contact-item" v-if="detail.mobile">
<view class="contact-label">
<u-icon name="phone-fill" color="#007aff" size="24"></u-icon>
<text class="label-text">电话</text>
</view>
<text class="contact-value">{{ detail.mobile }}</text>
</view>
<view class="contact-item" v-if="detail.email">
<view class="contact-label">
<u-icon name="email-fill" color="#ff6b35" size="24"></u-icon>
<text class="label-text">邮箱</text>
</view>
<text class="contact-value">{{ detail.email }}</text>
</view>
</view>
<view class="contact-content" v-else>
<view class="contact-placeholder">
<u-icon name="lock-fill" color="#c0c4cc" size="48"></u-icon>
<text class="placeholder-text">私信后自动公开联系方式</text>
</view>
</view>
</view>
</view>
<!-- 底部占位避免内容被footer遮挡 -->
<view class="bottom-placeholder"></view>
</scroll-view>
<view class="footer">
<view class="footer-action" @click="toggleCollect">
<u-icon :name="isCollected ? 'star-fill' : 'star'" size="40" :color="isCollected ? '#f29100' : '#606266'"></u-icon>
<text :style="{ color: isCollected ? '#f29100' : '#606266' }">收藏</text>
</view>
<u-button type="primary" shape="circle" class="message-btn" @click="goToChat"></u-button>
<u-button v-if="!isExpired" type="primary" shape="circle" class="message-btn" @click="goToChat"></u-button>
<view v-else class="expired-notice">
<text class="expired-text">已过期</text>
</view>
</view>
</view>
</template>
@ -47,6 +120,50 @@
isCollected: false
}
},
computed: {
hasContactInfo() {
if (!this.detail) return false;
// public_way
switch (this.detail.public_way) {
case 1: //
return this.detail.user || this.detail.wechat || this.detail.mobile || this.detail.email;
case 2: //
return this.detail.user || this.detail.wechat || this.detail.mobile || this.detail.email;
case 3: //
return false;
default:
return this.detail.user || this.detail.wechat || this.detail.mobile || this.detail.email;
}
},
//
needMessageToShowContact() {
// messages_count > 0
if (this.detail && this.detail.messages_count > 0) {
return false;
}
// public_way
return this.detail && this.detail.public_way === 2;
},
//
isExpired() {
if (!this.detail || !this.detail.expire_time) {
return false;
}
// 使moment
const currentTime = this.$moment();
const expireTime = this.$moment(this.detail.expire_time);
return currentTime.isAfter(expireTime);
},
//
imageFiles() {
if (!this.detail || !this.detail.files) return [];
return this.detail.files.filter(f => {
const ext = f.extension ? f.extension.toLowerCase() : '';
return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(ext);
});
}
},
onLoad(options) {
console.log("页面ID:", options.id);
this.fetchDetailData(options.id);
@ -75,9 +192,24 @@
uni.navigateTo({
url: `/packages/chat/chatWindow?userId=${this.detail.user.id}&userName=${userName}`
});
//
if (this.detail.public_way === 2 && this.detail.messages_count === 0) {
this.$u.toast('私信后联系方式将自动公开');
}
} else {
this.$u.toast('用户信息不完整');
}
},
//
previewImage(file, index) {
if (this.imageFiles.length === 0) return;
uni.previewImage({
urls: this.imageFiles.map(f => f.url),
current: file.url,
index: index
});
}
}
}
@ -86,7 +218,14 @@
<style lang="scss" scoped>
.container {
background: linear-gradient(to bottom, #e9f2fa, #f5f7fa);
min-height: 100vh;
height: 100vh;
display: flex;
flex-direction: column;
}
.scroll-container {
flex: 1;
height: calc(100vh - 120rpx); /* 减去footer的高度 */
}
.user-info-header {
@ -170,6 +309,80 @@
margin-bottom: 30rpx;
}
.images-container {
margin-top: 30rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
}
.images-title {
display: flex;
align-items: center;
margin-bottom: 20rpx;
padding-left: 10rpx;
}
.images-title-text {
font-size: 28rpx;
color: #333;
margin-left: 10rpx;
font-weight: 500;
}
.images-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160rpx, 1fr));
gap: 15rpx;
padding: 0 5rpx;
}
.image-item {
position: relative;
width: 100%;
height: 160rpx;
border-radius: 12rpx;
overflow: hidden;
background-color: #f5f5f5;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
cursor: pointer;
}
.image-item:active {
transform: scale(0.95);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
}
.image-item image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.image-item:active image {
transform: scale(1.05);
}
.image-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.3) 100%);
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.image-item:active .image-overlay {
opacity: 1;
}
.tags {
display: flex;
flex-wrap: wrap;
@ -185,6 +398,99 @@
margin-bottom: 10rpx;
}
.expired-badge {
display: flex;
align-items: center;
background-color: #f5f5f5;
color: #909399;
font-size: 24rpx;
padding: 10rpx 20rpx;
border-radius: 30rpx;
margin-top: 20rpx;
width: fit-content;
}
.expired-badge-text {
margin-left: 8rpx;
color: #909399;
}
.contact-card {
background-color: #fff;
margin: 30rpx 30rpx 0;
padding: 30rpx;
border-radius: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.contact-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 15rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.contact-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-left: 10rpx;
}
.contact-content {
display: flex;
flex-direction: column;
}
.contact-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1rpx solid #f8f8f8;
}
.contact-item:last-child {
border-bottom: none;
}
.contact-label {
display: flex;
align-items: center;
}
.label-text {
font-size: 28rpx;
color: #606266;
margin-left: 10rpx;
font-weight: 500;
}
.contact-value {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.contact-placeholder {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0;
color: #c0c4cc;
}
.placeholder-text {
margin-top: 15rpx;
font-size: 28rpx;
color: #c0c4cc;
}
.bottom-placeholder {
height: 140rpx; /* 为footer预留足够空间 */
}
.footer {
position: fixed;
bottom: 0;
@ -197,6 +503,8 @@
background-color: #fff;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
box-sizing: border-box;
z-index: 999;
height: 120rpx;
}
.footer-action {
@ -210,4 +518,21 @@
.message-btn {
width: 250rpx;
}
.expired-notice {
display: flex;
align-items: center;
justify-content: center;
width: 250rpx;
height: 80rpx;
background-color: #f5f5f5;
border-radius: 40rpx;
border: 1rpx solid #e4e7ed;
}
.expired-text {
font-size: 28rpx;
color: #909399;
font-weight: 500;
}
</style>

@ -1,7 +1,15 @@
<template>
<view class="container">
<view class="search-bar">
<u-search placeholder="请输入关键词" v-model="keyword" :show-action="false" @search="search" @input="onSearchInput"></u-search>
<view class="search-header">
<view class="my-posts-button" @click="toggleMyPosts">
<u-icon name="edit-pen" color="#fff" size="32"></u-icon>
<text class="my-posts-text">我发布的</text>
</view>
<view class="search-input-container">
<u-search placeholder="请输入关键词" v-model="keyword" :show-action="false" @search="search" @input="onSearchInput"></u-search>
</view>
</view>
</view>
<u-tabs :list="tabs" :is-scroll="false" :current="currentTab" @change="changeTab"></u-tabs>
<view class="list-container">
@ -171,6 +179,12 @@
uni.navigateTo({
url: `/packages/chat/chatWindow?userId=${item.user_id}&userName=${item.user.name}&supplyDemandId=${item.id}`
})
},
toggleMyPosts() {
//
uni.navigateTo({
url: '/packages/supply/my-posts'
})
}
}
}
@ -187,6 +201,38 @@
background-color: #fff;
}
.search-header {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 20rpx;
}
.my-posts-button {
display: flex;
align-items: center;
background: linear-gradient(to right, #e3ccb2, #cba579);
padding: 20rpx 30rpx;
border-radius: 60rpx;
box-shadow: 0 4rpx 12rpx rgba(203, 165, 121, 0.3);
transition: all 0.3s ease;
}
.my-posts-button:active {
transform: scale(0.95);
}
.my-posts-text {
color: #fff;
font-size: 28rpx;
font-weight: 500;
margin-left: 10rpx;
}
.search-input-container {
flex: 1;
}
.list-container {
background: linear-gradient(to bottom, #e9f2fa, #e9f2fa);
padding: 20rpx;

@ -0,0 +1,392 @@
<template>
<view class="container">
<!-- 状态筛选标签 -->
<u-tabs :list="statusTabs" :is-scroll="false" :current="currentStatusTab" @change="changeStatusTab"></u-tabs>
<view class="list-container">
<view v-if="loading && page === 1" class="loading-state">
<u-loading-page :loading="true"></u-loading-page>
</view>
<view v-else-if="list.length === 0 && !loading" class="empty-state">
<u-icon name="empty-data" size="120" color="#c0c4cc"></u-icon>
<text class="empty-text">您还没有发布任何供需信息</text>
<view class="empty-button" @click="goToPublish">
<text class="empty-button-text">立即发布</text>
</view>
</view>
<view v-else v-for="item in list" :key="item.id" class="list-item">
<view class="item-header">
<view :class="['type-badge', item.type === 1 ? 'supply' : 'demand']">{{ item.type === 1 ? '供应' : '需求' }}</view>
<view :class="['status-badge', getStatusClass(item.status)]">{{ getStatusText(item.status) }}</view>
<text class="time">{{ item.created_at }}</text>
</view>
<text class="item-title">{{ item.title }}</text>
<text class="description">{{ item.content }}</text>
<view class="tags" v-if="item.tag">
<text v-for="tag in item.tag.split(',')" :key="tag" class="tag">{{ tag }}</text>
</view>
<u-line color="#e8e8e8" margin="20rpx 0" />
<view class="item-footer">
<view class="stats">
<text class="stat-item">{{ item.view_count || 0 }}浏览</text>
<text class="stat-item">{{ item.contact_count || 0 }}私信</text>
</view>
<view class="actions">
<view class="view-button view-button-check" @click="goToDetail(item.id)">
<text class="button-text">查看</text>
</view>
<view v-if="item.status === 3 || item.status === 2" class="view-button view-button-edit" @click="goToEdit(item.id)">
<text class="button-text">编辑</text>
</view>
</view>
</view>
</view>
<u-loadmore :status="status" nomore-text="~" />
</view>
</view>
</template>
<script>
import uLoadmore from '@/uview-ui/components/u-loadmore/u-loadmore.vue';
import uLine from '@/uview-ui/components/u-line/u-line.vue';
import uLoadingPage from '@/uview-ui/components/u-loading-page/u-loading-page.vue';
import uTabs from '@/uview-ui/components/u-tabs/u-tabs.vue';
import { base } from '@/common/util.js';
export default {
components: {
uLoadmore,
uLine,
uLoadingPage,
uTabs
},
data() {
return {
base,
list: [],
page: 1,
pageSize: 10,
status: 'loadmore',
loading: false,
currentStatusTab: 0, //
statusTabs: [
{ name: '全部' },
{ name: '待审核' },
{ name: '审核通过' },
{ name: '已拒绝' },
{ name: '退回修改' },
{ name: '永久隐藏' }
]
}
},
onLoad() {
this.fetchMyPosts();
},
onReachBottom() {
if (this.status === 'loadmore' && !this.loading) {
this.page++;
this.fetchMyPosts();
}
},
methods: {
fetchMyPosts() {
if (this.loading) return;
this.loading = true;
const params = {
page: this.page,
pageSize: this.pageSize,
myself: 1 // 1
};
//
if (this.currentStatusTab === 0) {
// null
params.status = '-1'
} else {
//
const statusMap = {
1: 0, //
2: 1, //
3: 2, //
4: 3, // 退
5: 4 //
};
params.status = statusMap[this.currentStatusTab] || 0;
}
this.$u.api.supplyDemandList(params).then(res => {
const supplyDemands = res.supplyDemands;
const newList = supplyDemands.data || [];
if (this.page === 1) {
this.list = newList;
} else {
this.list = [...this.list, ...newList];
}
//
if (supplyDemands.current_page >= supplyDemands.last_page) {
this.status = 'nomore';
} else {
this.status = 'loadmore';
}
}).catch(err => {
console.error('获取我的发布列表失败:', err);
this.$u.toast('网络错误,请重试');
}).finally(() => {
this.loading = false;
});
},
goToDetail(id) {
uni.navigateTo({
url: `/packages/supply/detail?id=${id}`
})
},
goToEdit(id) {
//
uni.navigateTo({
url: `/packages/supply/publish?id=${id}`
})
},
goToPublish() {
uni.navigateTo({
url: '/packages/supply/publish'
})
},
changeStatusTab(index) {
this.currentStatusTab = index;
this.page = 1;
this.list = [];
this.status = 'loadmore';
this.fetchMyPosts();
},
getStatusClass(status) {
switch (status) {
case 0:
return 'pending';
case 1:
return 'approved';
case 2:
return 'denied';
case 3:
return 'rejected';
case 4:
return 'hidden';
default:
return '';
}
},
getStatusText(status) {
switch (status) {
case 0:
return '待审核';
case 1:
return '审核通过';
case 2:
return '已拒绝';
case 3:
return '退回修改';
case 4:
return '永久隐藏';
default:
return '未知';
}
}
}
}
</script>
<style lang="scss" scoped>
.container {
background-color: #f5f5f5;
min-height: 100vh;
}
.header {
background: linear-gradient(to right, #e3ccb2, #cba579);
padding: 40rpx 30rpx;
text-align: center;
}
.item-title {
font-size: 32rpx;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
color: #333;
}
.list-container {
background: linear-gradient(to bottom, #e9f2fa, #e9f2fa);
padding: 20rpx;
min-height: calc(100vh - 180rpx);
}
.list-item {
background-color: #fff;
border-radius: 20rpx;
padding: 25rpx;
margin-bottom: 20rpx;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15rpx;
}
.type-badge {
font-size: 24rpx;
padding: 8rpx 15rpx;
border-radius: 10rpx;
margin-right: 10rpx;
}
.supply {
background-color: #fff0e6;
color: #f29100;
}
.demand {
background-color: #e6f0ff;
color: #007aff;
}
.status-badge {
font-size: 24rpx;
padding: 8rpx 15rpx;
border-radius: 10rpx;
margin-right: 10rpx;
}
.status-badge.pending {
background-color: #fffbe6;
color: #f29100;
}
.status-badge.approved {
background-color: #e6ffe6;
color: #00c853;
}
.status-badge.rejected {
background-color: #ffe6e6;
color: #ff3b30;
}
.status-badge.hidden {
background-color: #f0f0f0;
color: #999;
}
.status-badge.denied {
background-color: #ffe6e6;
color: #d32f2f;
}
.time {
font-size: 24rpx;
color: #999;
}
.description {
font-size: 28rpx;
color: #666;
line-height: 1.6;
margin-bottom: 30rpx;
display: block;
max-height: 120rpx;
overflow: hidden;
}
.tags {
display: flex;
flex-wrap: wrap;
}
.tag {
background-color: #f5f5f5;
color: #666;
font-size: 24rpx;
padding: 8rpx 15rpx;
border-radius: 30rpx;
margin-right: 15rpx;
margin-bottom: 10rpx;
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.stats {
display: flex;
gap: 20rpx;
}
.stat-item {
font-size: 24rpx;
color: #999;
}
.actions {
display: flex;
align-items: center;
}
.view-button {
width: 150rpx;
height: 60rpx;
border-radius: 30rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 15rpx;
}
.view-button-check {
background: linear-gradient(to right, #e3ccb2, #cba579);
}
.view-button-edit {
background: linear-gradient(to right, #5d5ebc, #12099a);
}
.button-text {
color: white;
font-size: 26rpx;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 80rpx 0;
color: #c0c4cc;
}
.empty-text {
margin-top: 20rpx;
font-size: 32rpx;
color: #c0c4cc;
}
.empty-button {
margin-top: 40rpx;
width: 280rpx;
height: 80rpx;
background: linear-gradient(to right, #e3ccb2, #cba579);
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.empty-button-text {
color: white;
font-size: 32rpx;
}
</style>

@ -38,9 +38,32 @@
<view class="form-tip">建议添加相关行业标签, 方便其他校友找到你</view>
</u-form>
</view>
<view class="card">
<text class="section-title">图片上传</text>
<view class="image-upload">
<view class="image-list">
<view v-for="(image, index) in form.images" :key="index" class="image-item">
<image :src="image" mode="aspectFill" class="uploaded-image"></image>
<view class="delete-btn" @click="removeImage(index)">
<u-icon name="close" color="#fff" size="20"></u-icon>
</view>
</view>
<view v-if="form.images.length < 9" class="upload-btn" @click="chooseImage">
<u-icon name="camera-fill" color="#C9A36D" size="40"></u-icon>
<text class="upload-text">添加图片</text>
</view>
</view>
<text class="upload-tip">最多上传9张图片支持jpgpng格式</text>
</view>
</view>
<view class="card">
<text class="section-title">联系方式 *</text>
<text class="section-title">联系方式</text>
<u-form-item label="联系人" label-width="150" prop="contactName" :border-bottom="false">
<u-input v-model="form.contactName" placeholder="请输入联系人姓名" type="text"
:custom-style="inputStyle('contactName')" @focus="activeInput = 'contactName'" @blur="activeInput = null" />
</u-form-item>
<view class="contact-selector">
<view :class="['contact-button', form.contactType === 'wechat' ? 'active' : '']" @click="form.contactType = 'wechat'">
<u-icon name="chat-fill" :color="form.contactType === 'wechat' ? '#C9A36D' : '#909399'"></u-icon>
@ -57,22 +80,66 @@
</view>
<u-input v-model="form.contactValue" :placeholder="contactPlaceholder"
:custom-style="inputStyle('contact')" @focus="activeInput = 'contact'" @blur="activeInput = null" />
<text class="section-subtitle">公开模式</text>
<view class="privacy-selector">
<view :class="['privacy-button', form.publicWay === 1 ? 'active' : '']" @click="form.publicWay = 1">
<u-icon name="eye-fill" :color="form.publicWay === 1 ? '#C9A36D' : '#909399'"></u-icon>
<text class="privacy-text">直接公开</text>
</view>
<view :class="['privacy-button', form.publicWay === 2 ? 'active' : '']" @click="form.publicWay = 2">
<u-icon name="chat-fill" :color="form.publicWay === 2 ? '#C9A36D' : '#909399'"></u-icon>
<text class="privacy-text">私信后公开</text>
</view>
<view :class="['privacy-button', form.publicWay === 3 ? 'active' : '']" @click="form.publicWay = 3">
<u-icon name="lock-fill" :color="form.publicWay === 3 ? '#C9A36D' : '#909399'"></u-icon>
<text class="privacy-text">不公开</text>
</view>
</view>
<text class="privacy-notice">隐私保护: 您的联系方式仅对感兴趣的校友可见, 平台内置防骚扰机制, 保护您的隐私安全</text>
</view>
<view class="card">
<text class="section-title">时效性</text>
<view class="expiry-selector">
<view :class="['expiry-button', form.expiryType === 'longterm' ? 'active' : '']" @click="form.expiryType = 'longterm'">
<u-icon name="clock-fill" :color="form.expiryType === 'longterm' ? '#C9A36D' : '#909399'"></u-icon>
<text class="expiry-text">长期有效</text>
</view>
<view :class="['expiry-button', form.expiryType === 'specific' ? 'active' : '']" @click="form.expiryType = 'specific'">
<u-icon name="calendar-fill" :color="form.expiryType === 'specific' ? '#C9A36D' : '#909399'"></u-icon>
<text class="expiry-text">具体日期</text>
</view>
</view>
<view v-if="form.expiryType === 'specific'" class="date-picker">
<u-form-item label="到期日期" label-width="150" prop="expiryDate" :border-bottom="false">
<u-input v-model="form.expiryDate" placeholder="请选择到期日期" type="text" readonly
:custom-style="inputStyle('expiryDate')" @click="showDatePicker = true" />
</u-form-item>
</view>
</view>
<view class="footer-button">
<u-button type="primary" shape="circle" @click="submit">{{ form.type === 'supply' ? '' : '' }}</u-button>
</view>
<!-- 日期选择器 -->
<u-picker mode="time" v-model="showDatePicker" @confirm="onDateConfirm" :params="dateParams"></u-picker>
</view>
</template>
<script>
import uPicker from '@/uview-ui/components/u-picker/u-picker.vue';
export default {
components: {
uPicker
},
data() {
return {
activeInput: null,
tagInput: '',
header: {}, // header
form: {
type: 'supply', // supply or demand
title: '',
@ -80,7 +147,21 @@
tags: [],
contactType: 'wechat', // wechat, phone, email
contactValue: '',
contactName: '', //
publicWay: 1, // : 1-, 2-, 3-
expiryType: 'longterm', // : longterm, specific
expiryDate: '', //
images: [], //
},
showDatePicker: false,
dateParams: {
year: true,
month: true,
day: true,
hour: false,
minute: false,
second: false
}
};
},
computed:{
@ -93,6 +174,17 @@
return placeholders[this.form.contactType]
}
},
onLoad() {
//
const currentUser = this.$store.state.vuex_user;
if (currentUser && currentUser.name) {
this.form.contactName = currentUser.name;
}
// Authorization headercoursePay.vue
let token = uni.getStorageSync('stbc1_lifeData') ? uni.getStorageSync('stbc1_lifeData').vuex_token : '';
this.header.Authorization = `Bearer ${token}`;
},
methods: {
inputStyle(name) {
const style = {
@ -116,17 +208,129 @@
removeTag(index) {
this.form.tags.splice(index, 1);
},
submit() {
const params = {
title: this.form.title,
type: this.form.type === 'supply' ? 1 : 2,
content: this.form.description,
tag: this.form.tags.join(','),
wechat: this.form.contactType === 'wechat' ? this.form.contactValue : '',
mobile: this.form.contactType === 'phone' ? this.form.contactValue : '',
email: this.form.contactType === 'email' ? this.form.contactValue : ''
};
this.$u.api.supplyDemandSave(params).then(res => {
chooseImage() {
uni.chooseImage({
count: 9 - this.form.images.length, //
sizeType: ['original', 'compressed'], //
sourceType: ['album', 'camera'], //
success: (res) => {
this.form.images.push(...res.tempFilePaths);
}
});
},
removeImage(index) {
this.form.images.splice(index, 1);
},
onDateConfirm(e) {
const year = e.year;
const month = e.month.toString().padStart(2, '0');
const day = e.day.toString().padStart(2, '0');
this.form.expiryDate = `${year}-${month}-${day}`;
this.showDatePicker = false;
},
//
uploadImage(filePath) {
return new Promise((resolve, reject) => {
// 使uni.uploadFilecoursePay.vue
uni.uploadFile({
url: this.$u.http.config.baseUrl + '/api/mobile/upload-file',
filePath: filePath,
name: 'file',
header: this.header, // 使this.header
success: (uploadRes) => {
try {
const result = JSON.parse(uploadRes.data);
// coursePay.vueid
if (result && result.id) {
resolve(result);
} else if (result.code === 200 && result.data) {
resolve(result.data);
} else {
reject(new Error(result.message || '上传失败'));
}
} catch (e) {
reject(new Error('上传响应解析失败'));
}
},
fail: (error) => {
reject(error);
}
});
});
},
async submit() {
//
if (!this.form.title.trim()) {
this.$u.toast('请输入标题');
return;
}
if (!this.form.description.trim()) {
this.$u.toast('请输入详细描述');
return;
}
if (!this.form.contactName.trim()) {
this.$u.toast('请输入联系人');
return;
}
if (!this.form.contactValue.trim()) {
this.$u.toast('请输入联系方式');
return;
}
if (this.form.expiryType === 'specific' && !this.form.expiryDate) {
this.$u.toast('请选择到期日期');
return;
}
//
uni.showLoading({
title: '发布中...'
});
try {
//
const fileIds = [];
if (this.form.images.length > 0) {
for (let i = 0; i < this.form.images.length; i++) {
const imagePath = this.form.images[i];
try {
const uploadResult = await this.uploadImage(imagePath);
// coursePay.vue使result.id
if (uploadResult && uploadResult.id) {
fileIds.push(uploadResult.id);
} else {
throw new Error('未获取到文件ID');
}
} catch (error) {
console.error('图片上传失败:', error);
this.$u.toast(`${i + 1}张图片上传失败`);
return;
}
}
}
//
const params = {
title: this.form.title,
type: this.form.type === 'supply' ? 1 : 2,
content: this.form.description,
tag: this.form.tags.join(','),
wechat: this.form.contactType === 'wechat' ? this.form.contactValue : '',
mobile: this.form.contactType === 'phone' ? this.form.contactValue : '',
email: this.form.contactType === 'email' ? this.form.contactValue : '',
contact_name: this.form.contactName, // contact_name
public_way: this.form.publicWay, // public_way
file_ids: fileIds, // 使
};
// expire_time
if (this.form.expiryType === 'specific' && this.form.expiryDate) {
params.expire_time = this.form.expiryDate + ' 23:59:59'; //
}
//
const res = await this.$u.api.supplyDemandSave(params);
uni.hideLoading();
uni.showToast({
title: '发布成功',
icon: 'success',
@ -136,7 +340,11 @@
}, 1200);
}
});
});
} catch (error) {
uni.hideLoading();
console.error('发布失败:', error);
this.$u.toast('发布失败,请重试');
}
}
}
}
@ -165,7 +373,14 @@
margin-bottom: 30rpx;
}
.type-selector, .contact-selector {
.section-subtitle {
font-size: 28rpx;
color: #606266;
margin-top: 20rpx;
margin-bottom: 10rpx;
}
.type-selector, .contact-selector, .privacy-selector, .expiry-selector {
display: flex;
justify-content: space-between;
margin-bottom: 30rpx;
@ -189,7 +404,7 @@
}
}
.contact-button {
.contact-button, .privacy-button, .expiry-button {
width: 31%;
display: flex;
justify-content: center;
@ -201,13 +416,13 @@
&.active {
background-color: #fdf3e8;
border: 1rpx solid #e8d1b5;
.contact-text {
.contact-text, .privacy-text, .expiry-text {
color: #C9A36D;
}
}
}
.type-text, .contact-text {
.type-text, .contact-text, .privacy-text, .expiry-text {
font-size: 28rpx;
margin-left: 10rpx;
color: #606266;
@ -241,6 +456,80 @@
margin-right: 10rpx;
}
}
.image-upload {
margin-top: 20rpx;
}
.image-list {
display: flex;
flex-wrap: wrap;
margin-bottom: 10rpx;
}
.image-item {
position: relative;
width: 180rpx;
height: 180rpx;
margin-right: 15rpx;
margin-bottom: 15rpx;
border-radius: 12rpx;
overflow: hidden;
background-color: #f5f5f5;
.uploaded-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.delete-btn {
position: absolute;
top: 5rpx;
right: 5rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
width: 40rpx;
height: 40rpx;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
}
.upload-btn {
width: 180rpx;
height: 180rpx;
border: 1rpx dashed #C9A36D;
border-radius: 12rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #C9A36D;
font-size: 28rpx;
transition: all 0.3s;
&:active {
background-color: #fdf3e8;
}
}
.upload-text {
margin-top: 10rpx;
}
.upload-tip {
font-size: 24rpx;
color: #909399;
margin-top: 10rpx;
}
.date-picker {
margin-top: 20rpx;
padding-left: 150rpx;
}
.privacy-notice {
display: block;

@ -152,6 +152,11 @@
"style": {
"navigationBarTitleText": "供需详情"
}
},{
"path": "supply/my-posts",
"style": {
"navigationBarTitleText": "我发布的"
}
},{
"path": "chat/index",
"style": {

Loading…
Cancel
Save