refactor: 重构数据库配置为SQLite开发环境并移除冗余文档

This commit is contained in:
2025-09-21 15:16:48 +08:00
parent d207610009
commit 3c8648a635
259 changed files with 88239 additions and 8379 deletions

View File

@@ -0,0 +1,225 @@
<template>
<view id="app">
<!-- 牛只交易小程序入口 -->
</view>
</template>
<script>
export default {
name: 'App',
onLaunch: function() {
console.log('牛只交易小程序启动');
this.initApp();
},
onShow: function() {
console.log('牛只交易小程序显示');
},
onHide: function() {
console.log('牛只交易小程序隐藏');
},
methods: {
// 初始化应用
initApp() {
// 检查更新
this.checkUpdate();
// 初始化全局数据
this.initGlobalData();
// 检查登录状态
this.checkLoginStatus();
},
// 检查小程序更新
checkUpdate() {
// #ifdef MP-WEIXIN
if (uni.canIUse('getUpdateManager')) {
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate((res) => {
if (res.hasUpdate) {
console.log('发现新版本');
}
});
updateManager.onUpdateReady(() => {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success: (res) => {
if (res.confirm) {
updateManager.applyUpdate();
}
}
});
});
updateManager.onUpdateFailed(() => {
uni.showModal({
title: '更新失败',
content: '新版本下载失败,请检查网络后重试',
showCancel: false
});
});
}
// #endif
},
// 初始化全局数据
initGlobalData() {
getApp().globalData = {
userInfo: null,
token: '',
baseUrl: 'https://api.cattle-trading.com',
version: '1.0.0'
};
},
// 检查登录状态
checkLoginStatus() {
const token = uni.getStorageSync('token');
if (token) {
getApp().globalData.token = token;
this.validateToken(token);
}
},
// 验证token
async validateToken(token) {
try {
const res = await uni.request({
url: getApp().globalData.baseUrl + '/auth/validate',
method: 'POST',
header: {
'Authorization': 'Bearer ' + token
}
});
if (res.data.code === 200) {
getApp().globalData.userInfo = res.data.data;
} else {
uni.removeStorageSync('token');
getApp().globalData.token = '';
}
} catch (error) {
console.error('验证token失败', error);
}
}
}
};
</script>
<style lang="scss">
@import '@/uni_modules/uni-scss/index.scss';
page {
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}
.container {
padding: 20rpx;
}
.page-container {
min-height: 100vh;
background-color: #f5f5f5;
}
.card {
background: #ffffff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.btn-primary {
background: linear-gradient(135deg, #FF6B35, #FF8E53);
color: #ffffff;
border: none;
border-radius: 12rpx;
padding: 24rpx 48rpx;
font-size: 32rpx;
font-weight: 500;
}
.btn-secondary {
background: #ffffff;
color: #FF6B35;
border: 2rpx solid #FF6B35;
border-radius: 12rpx;
padding: 22rpx 46rpx;
font-size: 32rpx;
}
.text-primary {
color: #FF6B35;
}
.text-secondary {
color: #666666;
}
.text-muted {
color: #999999;
}
.flex {
display: flex;
}
.flex-column {
flex-direction: column;
}
.flex-center {
align-items: center;
justify-content: center;
}
.flex-between {
justify-content: space-between;
}
.flex-around {
justify-content: space-around;
}
.flex-1 {
flex: 1;
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ellipsis-2 {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -1,80 +1,302 @@
// 牛只交易小程序
App({
onLaunch() {
// 小程序初始化
console.log('牛肉商城小程序初始化');
// 检查登录状态
this.checkLoginStatus();
},
onShow() {
// 小程序显示
console.log('牛肉商城小程序显示');
},
onHide() {
// 小程序隐藏
console.log('牛肉商城小程序隐藏');
},
onError(msg) {
// 错误处理
console.log('小程序发生错误:', msg);
},
globalData: {
userInfo: null,
token: null,
baseUrl: 'http://localhost:8000/api'
apiBase: 'https://api.xlxumu.com',
version: '1.0.0'
},
// 检查登录状态
checkLoginStatus() {
try {
const token = wx.getStorageSync('token');
if (token) {
this.globalData.token = token;
// 验证token有效性
this.verifyToken(token);
}
} catch (e) {
console.error('检查登录状态失败:', e);
onLaunch() {
console.log('牛只交易小程序启动');
// 检查更新
this.checkForUpdate();
// 初始化用户信息
this.initUserInfo();
// 设置网络状态监听
this.setupNetworkListener();
},
onShow() {
console.log('牛只交易小程序显示');
},
onHide() {
console.log('牛只交易小程序隐藏');
},
onError(msg) {
console.error('小程序错误:', msg);
},
// 检查小程序更新
checkForUpdate() {
if (wx.canIUse('getUpdateManager')) {
const updateManager = wx.getUpdateManager();
updateManager.onCheckForUpdate((res) => {
if (res.hasUpdate) {
console.log('发现新版本');
}
});
updateManager.onUpdateReady(() => {
wx.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success: (res) => {
if (res.confirm) {
updateManager.applyUpdate();
}
}
});
});
updateManager.onUpdateFailed(() => {
console.error('新版本下载失败');
});
}
},
// 验证token
verifyToken(token) {
wx.request({
url: `${this.globalData.baseUrl}/auth/verify`,
method: 'GET',
header: {
'Authorization': `Bearer ${token}`
},
success: (res) => {
if (res.data.valid) {
this.globalData.userInfo = res.data.user;
} else {
// token无效清除本地存储
wx.removeStorageSync('token');
this.globalData.token = null;
this.globalData.userInfo = null;
}
},
fail: (err) => {
console.error('验证token失败', err);
// 初始化用户信息
initUserInfo() {
try {
const token = wx.getStorageSync('token');
const userInfo = wx.getStorageSync('userInfo');
if (token && userInfo) {
this.globalData.token = token;
this.globalData.userInfo = userInfo;
}
} catch (error) {
console.error('初始化用户信息失败:', error);
}
},
// 设置网络状态监听
setupNetworkListener() {
wx.onNetworkStatusChange((res) => {
if (!res.isConnected) {
this.showError('网络连接已断开');
}
});
},
// 登录方法
login(userInfo) {
this.globalData.userInfo = userInfo;
// 微信登录
async wxLogin() {
try {
// 检查登录状态
const loginRes = await this.checkSession();
if (loginRes) {
return this.globalData.userInfo;
}
} catch (error) {
console.log('登录状态已过期,重新登录');
}
try {
// 获取登录code
const { code } = await this.promisify(wx.login)();
// 获取用户信息
const userProfile = await this.getUserProfile();
// 发送登录请求
const response = await this.request({
url: '/auth/wechat/login',
method: 'POST',
data: {
code,
encrypted_data: userProfile.encryptedData,
iv: userProfile.iv
}
});
const { token, user } = response.data;
// 保存用户信息
this.globalData.token = token;
this.globalData.userInfo = user;
wx.setStorageSync('token', token);
wx.setStorageSync('userInfo', user);
return user;
} catch (error) {
console.error('微信登录失败:', error);
throw error;
}
},
// 登出方法
logout() {
this.globalData.userInfo = null;
// 检查登录状态
checkSession() {
return new Promise((resolve, reject) => {
wx.checkSession({
success: () => {
if (this.globalData.userInfo) {
resolve(this.globalData.userInfo);
} else {
reject(new Error('用户信息不存在'));
}
},
fail: reject
});
});
},
// 获取用户信息
getUserProfile() {
return new Promise((resolve, reject) => {
wx.getUserProfile({
desc: '用于完善用户资料',
success: resolve,
fail: reject
});
});
},
// 网络请求封装
async request(options) {
const {
url,
method = 'GET',
data = {},
header = {}
} = options;
// 添加认证头
if (this.globalData.token) {
header.Authorization = `Bearer ${this.globalData.token}`;
}
try {
const response = await this.promisify(wx.request)({
url: this.globalData.apiBase + url,
method,
data,
header: {
'Content-Type': 'application/json',
...header
}
});
const { statusCode, data: responseData } = response;
if (statusCode === 200) {
if (responseData.code === 0) {
return responseData;
} else {
throw new Error(responseData.message || '请求失败');
}
} else if (statusCode === 401) {
// 登录过期,清除用户信息
this.clearUserInfo();
wx.navigateTo({
url: '/pages/auth/login'
});
throw new Error('登录已过期,请重新登录');
} else {
throw new Error(`请求失败:${statusCode}`);
}
} catch (error) {
console.error('请求错误:', error);
throw error;
}
},
// 清除用户信息
clearUserInfo() {
this.globalData.token = null;
this.globalData.userInfo = null;
wx.removeStorageSync('token');
wx.removeStorageSync('userInfo');
},
// Promise化微信API
promisify(fn) {
return (options = {}) => {
return new Promise((resolve, reject) => {
fn({
...options,
success: resolve,
fail: reject
});
});
};
},
// 显示加载提示
showLoading(title = '加载中...') {
wx.showLoading({
title,
mask: true
});
},
// 隐藏加载提示
hideLoading() {
wx.hideLoading();
},
// 显示成功提示
showSuccess(title) {
wx.showToast({
title,
icon: 'success',
duration: 2000
});
},
// 显示错误提示
showError(title) {
wx.showToast({
title,
icon: 'none',
duration: 3000
});
},
// 显示确认对话框
showConfirm(options) {
return new Promise((resolve) => {
wx.showModal({
title: options.title || '提示',
content: options.content,
confirmText: options.confirmText || '确定',
cancelText: options.cancelText || '取消',
success: (res) => {
resolve(res.confirm);
}
});
});
},
// 格式化价格
formatPrice(price) {
if (!price) return '面议';
return `¥${parseFloat(price).toLocaleString()}`;
},
// 格式化时间
formatTime(timestamp) {
const date = new Date(timestamp);
const now = new Date();
const diff = now - date;
if (diff < 60000) { // 1分钟内
return '刚刚';
} else if (diff < 3600000) { // 1小时内
return `${Math.floor(diff / 60000)}分钟前`;
} else if (diff < 86400000) { // 1天内
return `${Math.floor(diff / 3600000)}小时前`;
} else if (diff < 2592000000) { // 30天内
return `${Math.floor(diff / 86400000)}天前`;
} else {
return date.toLocaleDateString();
}
}
})
});

View File

@@ -1,14 +1,84 @@
{
"pages": [
"pages/index/index",
"pages/logs/logs"
"pages/auth/login",
"pages/market/list",
"pages/market/detail",
"pages/market/publish",
"pages/market/search",
"pages/order/list",
"pages/order/detail",
"pages/profile/index",
"pages/profile/edit",
"pages/profile/verification",
"pages/message/index",
"pages/message/detail",
"pages/common/webview"
],
"tabBar": {
"color": "#666666",
"selectedColor": "#2E8B57",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "images/tabbar/home.png",
"selectedIconPath": "images/tabbar/home-active.png"
},
{
"pagePath": "pages/market/list",
"text": "市场",
"iconPath": "images/tabbar/market.png",
"selectedIconPath": "images/tabbar/market-active.png"
},
{
"pagePath": "pages/order/list",
"text": "订单",
"iconPath": "images/tabbar/order.png",
"selectedIconPath": "images/tabbar/order-active.png"
},
{
"pagePath": "pages/message/index",
"text": "消息",
"iconPath": "images/tabbar/message.png",
"selectedIconPath": "images/tabbar/message-active.png"
},
{
"pagePath": "pages/profile/index",
"text": "我的",
"iconPath": "images/tabbar/profile.png",
"selectedIconPath": "images/tabbar/profile-active.png"
}
]
},
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "锡林郭勒盟智慧养殖",
"navigationBarTextStyle": "black"
"navigationBarBackgroundColor": "#2E8B57",
"navigationBarTitleText": "牛只交易平台",
"navigationBarTextStyle": "white",
"backgroundColor": "#f5f5f5",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": false,
"permission": {
"scope.userLocation": {
"desc": "您的位置信息将用于展示附近的牛只交易信息"
}
},
"requiredBackgroundModes": ["location"],
"plugins": {},
"preloadRule": {
"pages/market/list": {
"network": "all",
"packages": ["market"]
}
},
"lazyCodeLoading": "requiredComponents"
}

View File

@@ -0,0 +1,54 @@
{
"name": "cattle-trading",
"appid": "wx2345678901bcdefg",
"description": "安全便捷的牲畜交易平台",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"mp-weixin": {
"appid": "wx2345678901bcdefg",
"setting": {
"urlCheck": false,
"es6": true,
"enhance": true,
"postcss": true,
"minified": true,
"newFeature": false,
"coverView": true,
"nodeModules": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"scopeDataCheck": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": true,
"enableEngineNative": false,
"useIsolateContext": true,
"minifyWXSS": true,
"disableUseStrict": false,
"minifyWXML": true
},
"usingComponents": true,
"permission": {
"scope.userLocation": {
"desc": "用于定位交易地点和物流配送"
},
"scope.camera": {
"desc": "需要使用摄像头拍摄牲畜照片"
},
"scope.album": {
"desc": "需要访问相册选择牲畜照片"
}
},
"requiredPrivateInfos": [
"getLocation",
"chooseLocation",
"chooseImage",
"chooseVideo"
]
},
"vueVersion": "3"
}

View File

@@ -0,0 +1,100 @@
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "牲畜交易",
"enablePullDownRefresh": true
}
},
{
"path": "pages/market/list",
"style": {
"navigationBarTitleText": "交易市场",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
}
},
{
"path": "pages/market/detail",
"style": {
"navigationBarTitleText": "交易详情"
}
},
{
"path": "pages/market/analysis",
"style": {
"navigationBarTitleText": "市场分析",
"enablePullDownRefresh": true
}
},
{
"path": "pages/publish/publish",
"style": {
"navigationBarTitleText": "发布交易"
}
},
{
"path": "pages/orders/list",
"style": {
"navigationBarTitleText": "我的订单",
"enablePullDownRefresh": true
}
},
{
"path": "pages/orders/detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "pages/profile/index",
"style": {
"navigationBarTitleText": "个人中心"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "牲畜交易",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#1890ff",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/market/list",
"iconPath": "static/tabbar/market.png",
"selectedIconPath": "static/tabbar/market-active.png",
"text": "市场"
},
{
"pagePath": "pages/publish/publish",
"iconPath": "static/tabbar/publish.png",
"selectedIconPath": "static/tabbar/publish-active.png",
"text": "发布"
},
{
"pagePath": "pages/orders/list",
"iconPath": "static/tabbar/orders.png",
"selectedIconPath": "static/tabbar/orders-active.png",
"text": "订单"
},
{
"pagePath": "pages/profile/index",
"iconPath": "static/tabbar/profile.png",
"selectedIconPath": "static/tabbar/profile-active.png",
"text": "我的"
}
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,251 @@
// 牛只交易首页
const app = getApp();
Page({
data: {
userInfo: null,
bannerList: [],
categoryList: [],
hotProducts: [],
recentProducts: [],
marketStats: {
totalProducts: 0,
todayDeals: 0,
avgPrice: 0
},
loading: true,
refreshing: false
},
onLoad() {
this.checkLogin();
},
onShow() {
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo
});
this.loadData();
}
},
onPullDownRefresh() {
this.setData({ refreshing: true });
this.loadData().finally(() => {
this.setData({ refreshing: false });
wx.stopPullDownRefresh();
});
},
// 检查登录状态
checkLogin() {
if (!app.globalData.userInfo) {
wx.navigateTo({
url: '/pages/auth/login'
});
return;
}
this.setData({
userInfo: app.globalData.userInfo
});
this.loadData();
},
// 加载数据
async loadData() {
try {
this.setData({ loading: true });
await Promise.all([
this.loadBanners(),
this.loadCategories(),
this.loadHotProducts(),
this.loadRecentProducts(),
this.loadMarketStats()
]);
} catch (error) {
console.error('加载数据失败:', error);
app.showError('加载数据失败');
} finally {
this.setData({ loading: false });
}
},
// 加载轮播图
async loadBanners() {
try {
const res = await app.request({
url: '/banners',
method: 'GET',
data: { type: 'home' }
});
this.setData({
bannerList: res.data.list || []
});
} catch (error) {
console.error('加载轮播图失败:', error);
}
},
// 加载分类
async loadCategories() {
try {
const res = await app.request({
url: '/categories',
method: 'GET'
});
this.setData({
categoryList: res.data.list || []
});
} catch (error) {
console.error('加载分类失败:', error);
}
},
// 加载热门商品
async loadHotProducts() {
try {
const res = await app.request({
url: '/products',
method: 'GET',
data: {
sort: 'hot',
limit: 6
}
});
this.setData({
hotProducts: res.data.list || []
});
} catch (error) {
console.error('加载热门商品失败:', error);
}
},
// 加载最新商品
async loadRecentProducts() {
try {
const res = await app.request({
url: '/products',
method: 'GET',
data: {
sort: 'latest',
limit: 10
}
});
this.setData({
recentProducts: res.data.list || []
});
} catch (error) {
console.error('加载最新商品失败:', error);
}
},
// 加载市场统计
async loadMarketStats() {
try {
const res = await app.request({
url: '/market/stats',
method: 'GET'
});
this.setData({
marketStats: res.data || {}
});
} catch (error) {
console.error('加载市场统计失败:', error);
}
},
// 轮播图点击
onBannerTap(e) {
const { banner } = e.currentTarget.dataset;
if (banner.link_type === 'product') {
wx.navigateTo({
url: `/pages/market/detail?id=${banner.link_value}`
});
} else if (banner.link_type === 'category') {
wx.navigateTo({
url: `/pages/market/list?category=${banner.link_value}`
});
} else if (banner.link_type === 'url') {
wx.navigateTo({
url: `/pages/common/webview?url=${encodeURIComponent(banner.link_value)}`
});
}
},
// 分类点击
onCategoryTap(e) {
const { category } = e.currentTarget.dataset;
wx.navigateTo({
url: `/pages/market/list?category=${category.id}`
});
},
// 商品点击
onProductTap(e) {
const { product } = e.currentTarget.dataset;
wx.navigateTo({
url: `/pages/market/detail?id=${product.id}`
});
},
// 搜索
onSearch() {
wx.navigateTo({
url: '/pages/market/search'
});
},
// 发布商品
onPublish() {
wx.navigateTo({
url: '/pages/market/publish'
});
},
// 查看更多热门
onViewMoreHot() {
wx.navigateTo({
url: '/pages/market/list?sort=hot'
});
},
// 查看更多最新
onViewMoreRecent() {
wx.navigateTo({
url: '/pages/market/list?sort=latest'
});
},
// 查看全部商品
onViewAllProducts() {
wx.switchTab({
url: '/pages/market/list'
});
},
// 格式化价格
formatPrice(price) {
return app.formatPrice(price);
},
// 格式化时间
formatTime(timestamp) {
return app.formatTime(timestamp);
},
// 格式化数量
formatNumber(num) {
if (num >= 10000) {
return (num / 10000).toFixed(1) + '万';
}
return num.toString();
}
});

View File

@@ -0,0 +1,642 @@
<template>
<view class="container">
<!-- 顶部搜索栏 -->
<view class="search-bar">
<view class="search-input">
<uni-icons type="search" size="20" color="#999"></uni-icons>
<input
type="text"
placeholder="搜索牛只品种、价格等"
v-model="searchKeyword"
@confirm="handleSearch"
/>
</view>
<view class="filter-btn" @click="showFilter = true">
<uni-icons type="tune" size="20" color="#007AFF"></uni-icons>
</view>
</view>
<!-- 分类导航 -->
<view class="category-nav">
<scroll-view scroll-x="true" class="category-scroll">
<view class="category-list">
<view
class="category-item"
:class="{ active: activeCategory === item.id }"
v-for="item in categories"
:key="item.id"
@click="selectCategory(item.id)"
>
{{ item.name }}
</view>
</view>
</scroll-view>
</view>
<!-- 轮播图 -->
<view class="banner-section">
<swiper
class="banner-swiper"
indicator-dots="true"
autoplay="true"
interval="3000"
duration="500"
>
<swiper-item v-for="(banner, index) in banners" :key="index">
<image :src="banner.image" class="banner-image" mode="aspectFill"></image>
</swiper-item>
</swiper>
</view>
<!-- 快捷功能 -->
<view class="quick-actions">
<view class="action-item" @click="navigateTo('/pages/publish/publish')">
<view class="action-icon publish">
<uni-icons type="plus" size="24" color="#fff"></uni-icons>
</view>
<text class="action-text">发布交易</text>
</view>
<view class="action-item" @click="navigateTo('/pages/market/market')">
<view class="action-icon market">
<uni-icons type="shop" size="24" color="#fff"></uni-icons>
</view>
<text class="action-text">交易市场</text>
</view>
<view class="action-item" @click="navigateTo('/pages/orders/orders')">
<view class="action-icon orders">
<uni-icons type="list" size="24" color="#fff"></uni-icons>
</view>
<text class="action-text">我的订单</text>
</view>
<view class="action-item" @click="navigateTo('/pages/profile/profile')">
<view class="action-icon profile">
<uni-icons type="person" size="24" color="#fff"></uni-icons>
</view>
<text class="action-text">个人中心</text>
</view>
</view>
<!-- 推荐交易 -->
<view class="recommended-section">
<view class="section-header">
<text class="section-title">推荐交易</text>
<text class="more-btn" @click="navigateTo('/pages/market/market')">更多 ></text>
</view>
<view class="cattle-list">
<view
class="cattle-item"
v-for="cattle in recommendedCattle"
:key="cattle.id"
@click="navigateTo(`/pages/detail/detail?id=${cattle.id}`)"
>
<image :src="cattle.image" class="cattle-image" mode="aspectFill"></image>
<view class="cattle-info">
<text class="cattle-breed">{{ cattle.breed }}</text>
<text class="cattle-age">{{ cattle.age }}月龄</text>
<text class="cattle-weight">{{ cattle.weight }}kg</text>
<view class="cattle-price">
<text class="price-label">价格</text>
<text class="price-value">¥{{ cattle.price }}</text>
</view>
<view class="cattle-location">
<uni-icons type="location" size="12" color="#999"></uni-icons>
<text class="location-text">{{ cattle.location }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 筛选弹窗 -->
<uni-popup ref="filterPopup" type="bottom" :mask-click="false">
<view class="filter-popup">
<view class="popup-header">
<text class="popup-title">筛选条件</text>
<text class="popup-close" @click="showFilter = false">关闭</text>
</view>
<view class="filter-content">
<!-- 品种筛选 -->
<view class="filter-group">
<text class="filter-label">品种</text>
<view class="filter-options">
<text
class="filter-option"
:class="{ active: selectedBreed === breed }"
v-for="breed in breeds"
:key="breed"
@click="selectedBreed = breed"
>
{{ breed }}
</text>
</view>
</view>
<!-- 价格筛选 -->
<view class="filter-group">
<text class="filter-label">价格范围</text>
<view class="price-range">
<input
type="number"
placeholder="最低价"
v-model="priceRange.min"
class="price-input"
/>
<text class="price-separator">-</text>
<input
type="number"
placeholder="最高价"
v-model="priceRange.max"
class="price-input"
/>
</view>
</view>
<!-- 年龄筛选 -->
<view class="filter-group">
<text class="filter-label">年龄</text>
<view class="filter-options">
<text
class="filter-option"
:class="{ active: selectedAge === age }"
v-for="age in ageRanges"
:key="age"
@click="selectedAge = age"
>
{{ age }}
</text>
</view>
</view>
</view>
<view class="popup-footer">
<button class="reset-btn" @click="resetFilter">重置</button>
<button class="confirm-btn" @click="applyFilter">确定</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
export default {
name: 'Index',
data() {
return {
bannerList: [
{
id: 1,
title: '优质肉牛交易',
description: '精选优质肉牛,品质保证',
image: '/static/images/banner1.jpg',
url: '/pages/market/list?category=meat'
},
{
id: 2,
title: '奶牛专区',
description: '高产奶牛,产奶量高',
image: '/static/images/banner2.jpg',
url: '/pages/market/list?category=dairy'
},
{
id: 3,
title: '种牛繁育',
description: '优良种牛,繁育首选',
image: '/static/images/banner3.jpg',
url: '/pages/market/list?category=breeding'
}
],
categoryList: [
{
id: 1,
name: '肉牛',
icon: '/static/images/category-meat.png',
type: 'meat'
},
{
id: 2,
name: '奶牛',
icon: '/static/images/category-dairy.png',
type: 'dairy'
},
{
id: 3,
name: '种牛',
icon: '/static/images/category-breeding.png',
type: 'breeding'
},
{
id: 4,
name: '小牛',
icon: '/static/images/category-calf.png',
type: 'calf'
}
],
hotList: [],
latestList: [],
loadStatus: 'loading',
loadText: {
contentdown: '上拉显示更多',
contentrefresh: '正在加载...',
contentnomore: '没有更多数据了'
}
}
},
onLoad() {
this.initPage()
},
onShow() {
this.refreshData()
},
onPullDownRefresh() {
this.refreshData().finally(() => {
uni.stopPullDownRefresh()
})
},
methods: {
// 初始化页面
async initPage() {
try {
await this.loadData()
} catch (error) {
console.error('初始化页面失败:', error)
}
},
// 加载数据
async loadData() {
this.loadStatus = 'loading'
try {
await Promise.all([
this.loadHotProducts(),
this.loadLatestProducts()
])
this.loadStatus = 'noMore'
} catch (error) {
console.error('加载数据失败:', error)
this.loadStatus = 'noMore'
uni.showToast({
title: '加载数据失败',
icon: 'none'
})
}
},
// 刷新数据
async refreshData() {
return this.loadData()
},
// 加载热门商品
async loadHotProducts() {
try {
const res = await this.$request({
url: '/products/hot',
method: 'GET',
data: {
limit: 10
}
})
if (res.data && res.data.list) {
this.hotList = res.data.list
}
} catch (error) {
console.error('加载热门商品失败:', error)
}
},
// 加载最新商品
async loadLatestProducts() {
try {
const res = await this.$request({
url: '/products/latest',
method: 'GET',
data: {
limit: 10
}
})
if (res.data && res.data.list) {
this.latestList = res.data.list
}
} catch (error) {
console.error('加载最新商品失败:', error)
}
},
// 轮播图点击
onBannerClick(banner) {
if (banner.url) {
uni.navigateTo({
url: banner.url
})
}
},
// 分类点击
onCategoryClick(category) {
uni.navigateTo({
url: `/pages/market/list?category=${category.type}`
})
},
// 搜索点击
onSearchClick() {
uni.navigateTo({
url: '/pages/search/index'
})
},
// 商品点击
onProductClick(product) {
uni.navigateTo({
url: `/pages/market/detail?id=${product.id}`
})
},
// 查看更多
onViewMore(type) {
let url = '/pages/market/list'
if (type === 'hot') {
url += '?sort=hot'
} else if (type === 'latest') {
url += '?sort=latest'
}
uni.switchTab({
url: '/pages/market/list'
})
}
}
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f5f5f5;
}
/* 轮播图 */
.banner-section {
margin-bottom: 30rpx;
}
.banner-swiper {
height: 400rpx;
border-radius: 0 0 20rpx 20rpx;
overflow: hidden;
}
.banner-image {
width: 100%;
height: 100%;
}
.banner-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));
padding: 40rpx 30rpx 30rpx;
.banner-title {
display: block;
font-size: 36rpx;
font-weight: bold;
color: #ffffff;
margin-bottom: 10rpx;
}
.banner-desc {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
}
}
/* 分类导航 */
.category-section {
margin: 0 30rpx 30rpx;
}
.category-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20rpx;
}
.category-item {
background: #ffffff;
border-radius: 16rpx;
padding: 30rpx 20rpx;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
.category-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 16rpx;
.icon-image {
width: 100%;
height: 100%;
}
}
.category-name {
font-size: 28rpx;
color: #333333;
text-align: center;
}
}
/* 搜索栏 */
.search-section {
margin: 0 30rpx 30rpx;
}
.search-bar {
background: #ffffff;
border-radius: 50rpx;
padding: 20rpx 30rpx;
display: flex;
align-items: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
.search-icon {
font-size: 32rpx;
margin-right: 20rpx;
}
.search-placeholder {
flex: 1;
font-size: 28rpx;
color: #999999;
}
}
/* 推荐区域 */
.recommend-section {
margin-bottom: 30rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx 20rpx;
.section-title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
}
.more-text {
font-size: 28rpx;
color: #FF6B35;
}
}
.recommend-scroll {
white-space: nowrap;
}
.recommend-list {
display: flex;
padding: 0 30rpx;
gap: 20rpx;
}
.recommend-item {
width: 280rpx;
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
flex-shrink: 0;
.product-image {
width: 100%;
height: 200rpx;
}
.product-info {
padding: 20rpx;
.product-title {
display: block;
font-size: 28rpx;
font-weight: 500;
color: #333333;
margin-bottom: 10rpx;
@extend .ellipsis;
}
.product-price {
display: block;
font-size: 32rpx;
font-weight: bold;
color: #FF6B35;
margin-bottom: 10rpx;
}
.product-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
.tag {
background: #f0f0f0;
color: #666666;
font-size: 22rpx;
padding: 4rpx 8rpx;
border-radius: 8rpx;
}
}
}
}
/* 最新发布 */
.latest-section {
margin: 0 30rpx 30rpx;
}
.latest-list {
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
}
.latest-item {
display: flex;
padding: 20rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.item-image {
width: 160rpx;
height: 120rpx;
border-radius: 12rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.item-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.item-title {
font-size: 32rpx;
font-weight: 500;
color: #333333;
margin-bottom: 8rpx;
@extend .ellipsis;
}
.item-desc {
font-size: 28rpx;
color: #666666;
margin-bottom: 8rpx;
@extend .ellipsis;
}
.item-meta {
display: flex;
justify-content: space-between;
align-items: center;
.item-price {
font-size: 32rpx;
font-weight: bold;
color: #FF6B35;
}
.item-location {
font-size: 24rpx;
color: #999999;
}
.item-time {
font-size: 24rpx;
color: #999999;
}
}
}
}
</style>

View File

@@ -0,0 +1,147 @@
<!--牛只交易首页-->
<view class="page-container">
<!-- 顶部搜索栏 -->
<view class="header-search">
<view class="search-bar" bindtap="onSearch">
<image class="search-icon" src="/images/icons/search.png" mode="aspectFit"></image>
<text class="search-placeholder">搜索牛只品种、价格...</text>
</view>
<button class="publish-btn" bindtap="onPublish">发布</button>
</view>
<!-- 轮播图 -->
<swiper
class="banner-swiper"
indicator-dots
autoplay
interval="5000"
duration="500"
wx:if="{{bannerList.length > 0}}"
>
<swiper-item
wx:for="{{bannerList}}"
wx:key="id"
data-banner="{{item}}"
bindtap="onBannerTap"
>
<image class="banner-image" src="{{item.image}}" mode="aspectFill"></image>
</swiper-item>
</swiper>
<!-- 市场统计 -->
<view class="market-stats">
<view class="stats-item">
<text class="stats-number">{{formatNumber(marketStats.totalProducts)}}</text>
<text class="stats-label">在售商品</text>
</view>
<view class="stats-item">
<text class="stats-number">{{formatNumber(marketStats.todayDeals)}}</text>
<text class="stats-label">今日成交</text>
</view>
<view class="stats-item">
<text class="stats-number">{{formatPrice(marketStats.avgPrice)}}</text>
<text class="stats-label">平均价格</text>
</view>
</view>
<!-- 分类导航 -->
<view class="category-nav" wx:if="{{categoryList.length > 0}}">
<scroll-view class="category-scroll" scroll-x>
<view class="category-list">
<view
class="category-item"
wx:for="{{categoryList}}"
wx:key="id"
data-category="{{item}}"
bindtap="onCategoryTap"
>
<image class="category-icon" src="{{item.icon}}" mode="aspectFit"></image>
<text class="category-name">{{item.name}}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 热门商品 -->
<view class="section" wx:if="{{hotProducts.length > 0}}">
<view class="section-header">
<text class="section-title">热门推荐</text>
<text class="section-more" bindtap="onViewMoreHot">查看更多</text>
</view>
<view class="hot-products">
<view class="product-grid">
<view
class="product-item"
wx:for="{{hotProducts}}"
wx:key="id"
data-product="{{item}}"
bindtap="onProductTap"
>
<image class="product-image" src="{{item.cover_image}}" mode="aspectFill"></image>
<view class="product-info">
<text class="product-title">{{item.title}}</text>
<text class="product-breed">{{item.breed}}</text>
<view class="product-price-row">
<text class="product-price">{{formatPrice(item.price)}}</text>
<text class="product-location">{{item.location}}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 最新商品 -->
<view class="section" wx:if="{{recentProducts.length > 0}}">
<view class="section-header">
<text class="section-title">最新发布</text>
<text class="section-more" bindtap="onViewMoreRecent">查看更多</text>
</view>
<view class="recent-products">
<view
class="recent-item"
wx:for="{{recentProducts}}"
wx:key="id"
data-product="{{item}}"
bindtap="onProductTap"
>
<image class="recent-image" src="{{item.cover_image}}" mode="aspectFill"></image>
<view class="recent-info">
<text class="recent-title">{{item.title}}</text>
<text class="recent-breed">{{item.breed}} · {{item.age}}月龄</text>
<view class="recent-meta">
<text class="recent-price">{{formatPrice(item.price)}}</text>
<text class="recent-time">{{formatTime(item.created_at)}}</text>
</view>
<text class="recent-location">{{item.location}}</text>
</view>
</view>
</view>
</view>
<!-- 快捷操作 -->
<view class="quick-actions">
<button class="action-btn primary" bindtap="onViewAllProducts">
<image src="/images/icons/market.png" mode="aspectFit"></image>
<text>浏览市场</text>
</button>
<button class="action-btn secondary" bindtap="onPublish">
<image src="/images/icons/publish.png" mode="aspectFit"></image>
<text>发布商品</text>
</button>
</view>
<!-- 加载状态 -->
<view class="loading" wx:if="{{loading}}">
<view class="loading-spinner"></view>
<text>加载中...</text>
</view>
<!-- 空状态 -->
<view class="empty" wx:if="{{!loading && hotProducts.length === 0 && recentProducts.length === 0}}">
<image class="empty-icon" src="/images/empty-market.png" mode="aspectFit"></image>
<text class="empty-text">暂无商品</text>
<text class="empty-desc">成为第一个发布商品的用户</text>
<button class="btn btn-primary mt-3" bindtap="onPublish">发布商品</button>
</view>
</view>

View File

@@ -0,0 +1,419 @@
/* 牛只交易首页样式 */
.page-container {
min-height: 100vh;
background: #f5f5f5;
}
/* 顶部搜索栏 */
.header-search {
display: flex;
align-items: center;
padding: 20rpx 24rpx;
background: #2E8B57;
gap: 16rpx;
}
.search-bar {
flex: 1;
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.9);
border-radius: 24rpx;
padding: 0 20rpx;
height: 72rpx;
}
.search-icon {
width: 28rpx;
height: 28rpx;
margin-right: 12rpx;
}
.search-placeholder {
flex: 1;
font-size: 28rpx;
color: #999;
}
.publish-btn {
background: rgba(255, 255, 255, 0.2);
color: #fff;
border: 1rpx solid rgba(255, 255, 255, 0.3);
border-radius: 20rpx;
padding: 16rpx 24rpx;
font-size: 26rpx;
font-weight: 500;
}
/* 轮播图 */
.banner-swiper {
height: 320rpx;
margin: 0 24rpx 20rpx;
border-radius: 16rpx;
overflow: hidden;
}
.banner-image {
width: 100%;
height: 100%;
}
/* 市场统计 */
.market-stats {
display: flex;
background: #fff;
margin: 0 24rpx 20rpx;
border-radius: 16rpx;
padding: 32rpx 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
}
.stats-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.stats-number {
font-size: 36rpx;
font-weight: 700;
color: #2E8B57;
margin-bottom: 8rpx;
}
.stats-label {
font-size: 24rpx;
color: #666;
}
/* 分类导航 */
.category-nav {
margin: 0 0 20rpx;
}
.category-scroll {
white-space: nowrap;
}
.category-list {
display: inline-flex;
padding: 0 24rpx;
gap: 32rpx;
}
.category-item {
display: flex;
flex-direction: column;
align-items: center;
min-width: 120rpx;
}
.category-icon {
width: 80rpx;
height: 80rpx;
background: #fff;
border-radius: 50%;
padding: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
margin-bottom: 12rpx;
}
.category-name {
font-size: 24rpx;
color: #333;
text-align: center;
}
/* 区块样式 */
.section {
margin-bottom: 32rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 24rpx 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.section-more {
font-size: 24rpx;
color: #2E8B57;
}
/* 热门商品 */
.hot-products {
padding: 0 24rpx;
}
.product-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
}
.product-item {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
}
.product-item:active {
transform: scale(0.98);
}
.product-image {
width: 100%;
height: 240rpx;
}
.product-info {
padding: 16rpx;
}
.product-title {
display: block;
font-size: 26rpx;
font-weight: 500;
color: #333;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-breed {
display: block;
font-size: 22rpx;
color: #666;
margin-bottom: 12rpx;
}
.product-price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
font-size: 28rpx;
font-weight: 600;
color: #e74c3c;
}
.product-location {
font-size: 20rpx;
color: #999;
}
/* 最新商品 */
.recent-products {
padding: 0 24rpx;
}
.recent-item {
display: flex;
background: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
}
.recent-item:active {
transform: scale(0.98);
}
.recent-image {
width: 160rpx;
height: 120rpx;
border-radius: 8rpx;
margin-right: 20rpx;
}
.recent-info {
flex: 1;
display: flex;
flex-direction: column;
}
.recent-title {
font-size: 28rpx;
font-weight: 500;
color: #333;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.recent-breed {
font-size: 24rpx;
color: #666;
margin-bottom: 12rpx;
}
.recent-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
}
.recent-price {
font-size: 28rpx;
font-weight: 600;
color: #e74c3c;
}
.recent-time {
font-size: 20rpx;
color: #999;
}
.recent-location {
font-size: 22rpx;
color: #999;
}
/* 快捷操作 */
.quick-actions {
display: flex;
gap: 16rpx;
padding: 0 24rpx 40rpx;
}
.action-btn {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 32rpx 20rpx;
border-radius: 16rpx;
border: none;
font-size: 26rpx;
font-weight: 500;
}
.action-btn.primary {
background: #2E8B57;
color: #fff;
}
.action-btn.secondary {
background: #fff;
color: #333;
border: 1rpx solid #e9ecef;
}
.action-btn image {
width: 48rpx;
height: 48rpx;
margin-bottom: 12rpx;
}
/* 加载状态 */
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
color: #666;
}
.loading-spinner {
width: 48rpx;
height: 48rpx;
border: 3rpx solid #f0f0f0;
border-top: 3rpx solid #2E8B57;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 24rpx;
}
/* 空状态 */
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
text-align: center;
}
.empty-icon {
width: 160rpx;
height: 160rpx;
margin-bottom: 32rpx;
opacity: 0.6;
}
.empty-text {
font-size: 32rpx;
font-weight: 500;
color: #666;
margin-bottom: 16rpx;
}
.empty-desc {
font-size: 24rpx;
color: #999;
line-height: 1.5;
margin-bottom: 32rpx;
}
.btn {
padding: 20rpx 40rpx;
border-radius: 24rpx;
font-size: 28rpx;
font-weight: 500;
border: none;
}
.btn-primary {
background: #2E8B57;
color: #fff;
}
.mt-3 {
margin-top: 32rpx;
}
/* 动画 */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 响应式适配 */
@media (max-width: 750rpx) {
.product-grid {
grid-template-columns: 1fr;
}
.recent-item {
flex-direction: column;
}
.recent-image {
width: 100%;
height: 200rpx;
margin-right: 0;
margin-bottom: 16rpx;
}
.quick-actions {
flex-direction: column;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,693 @@
<template>
<view class="container">
<!-- 顶部搜索和筛选 -->
<view class="header-section">
<view class="search-bar">
<view class="search-input">
<uni-icons type="search" size="20" color="#999"></uni-icons>
<input
type="text"
placeholder="搜索牛只品种、价格等"
v-model="searchKeyword"
@confirm="handleSearch"
/>
</view>
</view>
<view class="filter-bar">
<view class="filter-item" @click="showSortPopup = true">
<text class="filter-text">{{ sortText }}</text>
<uni-icons type="arrowdown" size="16" color="#666"></uni-icons>
</view>
<view class="filter-item" @click="showFilterPopup = true">
<text class="filter-text">筛选</text>
<uni-icons type="tune" size="16" color="#666"></uni-icons>
</view>
<view class="filter-item" @click="toggleViewMode">
<uni-icons :type="viewMode === 'grid' ? 'list' : 'grid'" size="16" color="#666"></uni-icons>
</view>
</view>
</view>
<!-- 牛只列表 -->
<view class="cattle-list" :class="viewMode">
<view
class="cattle-item"
v-for="cattle in cattleList"
:key="cattle.id"
@click="navigateToDetail(cattle.id)"
>
<image :src="cattle.image" class="cattle-image" mode="aspectFill"></image>
<view class="cattle-info">
<view class="cattle-header">
<text class="cattle-breed">{{ cattle.breed }}</text>
<view class="cattle-status" :class="cattle.status">
{{ getStatusText(cattle.status) }}
</view>
</view>
<view class="cattle-details">
<view class="detail-row">
<text class="detail-label">年龄</text>
<text class="detail-value">{{ cattle.age }}月龄</text>
</view>
<view class="detail-row">
<text class="detail-label">重量</text>
<text class="detail-value">{{ cattle.weight }}kg</text>
</view>
<view class="detail-row">
<text class="detail-label">性别</text>
<text class="detail-value">{{ cattle.gender }}</text>
</view>
</view>
<view class="cattle-price">
<text class="price-label">价格</text>
<text class="price-value">¥{{ cattle.price }}</text>
</view>
<view class="cattle-footer">
<view class="location-info">
<uni-icons type="location" size="12" color="#999"></uni-icons>
<text class="location-text">{{ cattle.location }}</text>
</view>
<text class="publish-time">{{ formatTime(cattle.publishTime) }}</text>
</view>
<view class="cattle-actions" v-if="viewMode === 'list'">
<button class="action-btn contact" @click.stop="contactSeller(cattle)">
联系卖家
</button>
<button class="action-btn favorite" @click.stop="toggleFavorite(cattle)">
{{ cattle.isFavorite ? '已收藏' : '收藏' }}
</button>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<uni-load-more :status="loadStatus" :content-text="loadText"></uni-load-more>
<!-- 排序弹窗 -->
<uni-popup ref="sortPopup" type="bottom" :mask-click="true">
<view class="sort-popup">
<view class="popup-header">
<text class="popup-title">排序方式</text>
</view>
<view class="sort-options">
<view
class="sort-option"
:class="{ active: currentSort === option.value }"
v-for="option in sortOptions"
:key="option.value"
@click="selectSort(option.value)"
>
<text class="option-text">{{ option.label }}</text>
<uni-icons
v-if="currentSort === option.value"
type="checkmarkempty"
size="20"
color="#007AFF"
></uni-icons>
</view>
</view>
</view>
</uni-popup>
<!-- 筛选弹窗 -->
<uni-popup ref="filterPopup" type="bottom" :mask-click="false">
<view class="filter-popup">
<view class="popup-header">
<text class="popup-title">筛选条件</text>
<text class="popup-close" @click="showFilterPopup = false">关闭</text>
</view>
<scroll-view class="filter-content" scroll-y="true">
<!-- 品种筛选 -->
<view class="filter-group">
<text class="filter-label">品种</text>
<view class="filter-options">
<text
class="filter-option"
:class="{ active: filterParams.breed === breed }"
v-for="breed in breedOptions"
:key="breed"
@click="selectBreed(breed)"
>
{{ breed }}
</text>
</view>
</view>
<!-- 价格筛选 -->
<view class="filter-group">
<text class="filter-label">价格范围</text>
<view class="price-range">
<input
type="number"
placeholder="最低价"
v-model="filterParams.priceMin"
class="price-input"
/>
<text class="price-separator">-</text>
<input
type="number"
placeholder="最高价"
v-model="filterParams.priceMax"
class="price-input"
/>
</view>
</view>
<!-- 年龄筛选 -->
<view class="filter-group">
<text class="filter-label">年龄</text>
<view class="filter-options">
<text
class="filter-option"
:class="{ active: filterParams.age === age }"
v-for="age in ageOptions"
:key="age"
@click="selectAge(age)"
>
{{ age }}
</text>
</view>
</view>
<!-- 重量筛选 -->
<view class="filter-group">
<text class="filter-label">重量</text>
<view class="filter-options">
<text
class="filter-option"
:class="{ active: filterParams.weight === weight }"
v-for="weight in weightOptions"
:key="weight"
@click="selectWeight(weight)"
>
{{ weight }}
</text>
</view>
</view>
<!-- 地区筛选 -->
<view class="filter-group">
<text class="filter-label">地区</text>
<view class="filter-options">
<text
class="filter-option"
:class="{ active: filterParams.location === location }"
v-for="location in locationOptions"
:key="location"
@click="selectLocation(location)"
>
{{ location }}
</text>
</view>
</view>
</scroll-view>
<view class="popup-footer">
<button class="reset-btn" @click="resetFilter">重置</button>
<button class="confirm-btn" @click="applyFilter">确定</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import { ref, reactive, onMounted, computed } from 'vue'
import { useRequest } from '@/common/utils/request'
import { showToast, showLoading, hideLoading } from '@/common/utils/uni-helper'
export default {
name: 'MarketPage',
setup() {
const request = useRequest()
// 响应式数据
const searchKeyword = ref('')
const activeCategory = ref(0)
const productList = ref([])
const loading = ref(false)
const refreshing = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const showFilter = ref(false)
// 分类数据
const categories = ref([
{ id: 0, name: '全部' },
{ id: 1, name: '肉牛' },
{ id: 2, name: '奶牛' },
{ id: 3, name: '种牛' },
{ id: 4, name: '小牛' }
])
// 筛选数据
const filterData = reactive({
minPrice: '',
maxPrice: '',
breeds: [],
region: null
})
// 品种选项
const breedOptions = ref([
{ id: 1, name: '西门塔尔' },
{ id: 2, name: '安格斯' },
{ id: 3, name: '夏洛莱' },
{ id: 4, name: '利木赞' },
{ id: 5, name: '荷斯坦' }
])
// 地区选项
const regionOptions = ref([
{ id: 1, name: '北京市' },
{ id: 2, name: '上海市' },
{ id: 3, name: '广东省' },
{ id: 4, name: '山东省' },
{ id: 5, name: '河南省' }
])
// 获取商品列表
const getProductList = async (isRefresh = false) => {
if (loading.value && !isRefresh) return
try {
loading.value = true
const params = {
page: isRefresh ? 1 : currentPage.value,
pageSize: 10,
keyword: searchKeyword.value,
category: activeCategory.value,
...filterData
}
const response = await request.get('/api/cattle/list', params)
if (isRefresh) {
productList.value = response.data.list
currentPage.value = 1
} else {
productList.value.push(...response.data.list)
}
hasMore.value = response.data.hasMore
currentPage.value++
} catch (error) {
showToast('获取数据失败')
} finally {
loading.value = false
refreshing.value = false
}
}
// 搜索
const onSearch = () => {
currentPage.value = 1
getProductList(true)
}
// 选择分类
const selectCategory = (categoryId) => {
activeCategory.value = categoryId
currentPage.value = 1
getProductList(true)
}
// 下拉刷新
const onRefresh = () => {
refreshing.value = true
getProductList(true)
}
// 加载更多
const loadMore = () => {
if (hasMore.value && !loading.value) {
getProductList()
}
}
// 切换品种选择
const toggleBreed = (breedId) => {
const index = filterData.breeds.indexOf(breedId)
if (index > -1) {
filterData.breeds.splice(index, 1)
} else {
filterData.breeds.push(breedId)
}
}
// 地区选择
const onRegionChange = (e) => {
filterData.region = e.detail.value
}
// 重置筛选
const resetFilter = () => {
filterData.minPrice = ''
filterData.maxPrice = ''
filterData.breeds = []
filterData.region = null
}
// 应用筛选
const applyFilter = () => {
showFilter.value = false
currentPage.value = 1
getProductList(true)
}
// 跳转到详情页
const goToDetail = (productId) => {
uni.navigateTo({
url: `/pages/detail/detail?id=${productId}`
})
}
// 跳转到发布页
const goToPublish = () => {
uni.navigateTo({
url: '/pages/publish/publish'
})
}
// 页面加载
onMounted(() => {
getProductList(true)
})
return {
searchKeyword,
activeCategory,
productList,
loading,
refreshing,
hasMore,
showFilter,
categories,
filterData,
breedOptions,
regionOptions,
onSearch,
selectCategory,
onRefresh,
loadMore,
toggleBreed,
onRegionChange,
resetFilter,
applyFilter,
goToDetail,
goToPublish
}
}
}
</script>
<style lang="scss" scoped>
.market-page {
height: 100vh;
display: flex;
flex-direction: column;
background-color: $bg-secondary;
}
.search-bar {
display: flex;
align-items: center;
padding: $spacing-md;
background-color: $white;
gap: $spacing-sm;
.search-input {
flex: 1;
display: flex;
align-items: center;
background-color: $bg-light;
border-radius: $border-radius-lg;
padding: $spacing-sm $spacing-md;
gap: $spacing-sm;
input {
flex: 1;
font-size: $font-size-base;
}
}
.filter-btn {
padding: $spacing-sm;
background-color: $primary-color;
border-radius: $border-radius-md;
color: $white;
}
}
.category-tabs {
background-color: $white;
white-space: nowrap;
padding: $spacing-sm $spacing-md;
.category-item {
display: inline-block;
padding: $spacing-sm $spacing-md;
margin-right: $spacing-sm;
background-color: $bg-light;
border-radius: $border-radius-full;
font-size: $font-size-sm;
color: $text-secondary;
&.active {
background-color: $primary-color;
color: $white;
}
}
}
.product-list {
flex: 1;
padding: $spacing-md;
}
.product-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $spacing-md;
}
.product-card {
background-color: $white;
border-radius: $border-radius-lg;
overflow: hidden;
box-shadow: $shadow-sm;
.product-image {
position: relative;
height: 120px;
image {
width: 100%;
height: 100%;
}
.hot-tag {
position: absolute;
top: $spacing-sm;
left: $spacing-sm;
background-color: $error-color;
color: $white;
padding: 2px $spacing-sm;
border-radius: $border-radius-sm;
font-size: $font-size-xs;
}
}
.product-info {
padding: $spacing-md;
.product-title {
font-size: $font-size-base;
font-weight: $font-weight-medium;
color: $text-primary;
@include text-ellipsis(1);
margin-bottom: $spacing-xs;
}
.product-desc {
font-size: $font-size-sm;
color: $text-secondary;
@include text-ellipsis(2);
margin-bottom: $spacing-sm;
}
.product-tags {
margin-bottom: $spacing-sm;
.tag {
display: inline-block;
background-color: $bg-light;
color: $text-muted;
padding: 2px $spacing-xs;
border-radius: $border-radius-sm;
font-size: $font-size-xs;
margin-right: $spacing-xs;
}
}
.product-footer {
display: flex;
justify-content: space-between;
align-items: center;
.price {
.currency {
font-size: $font-size-sm;
color: $error-color;
}
.amount {
font-size: $font-size-lg;
font-weight: $font-weight-bold;
color: $error-color;
}
.unit {
font-size: $font-size-sm;
color: $text-muted;
}
}
.location {
display: flex;
align-items: center;
gap: 2px;
font-size: $font-size-xs;
color: $text-muted;
}
}
}
}
.loading-more {
display: flex;
align-items: center;
justify-content: center;
padding: $spacing-lg;
gap: $spacing-sm;
color: $text-muted;
font-size: $font-size-sm;
}
.no-more {
text-align: center;
padding: $spacing-lg;
color: $text-muted;
font-size: $font-size-sm;
}
.filter-popup {
background-color: $white;
border-radius: $border-radius-lg $border-radius-lg 0 0;
max-height: 70vh;
.filter-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: $spacing-lg;
border-bottom: 1px solid $border-color;
.filter-actions {
display: flex;
gap: $spacing-lg;
text {
color: $primary-color;
font-size: $font-size-base;
}
}
}
.filter-content {
padding: $spacing-lg;
max-height: 50vh;
overflow-y: auto;
}
.filter-section {
margin-bottom: $spacing-xl;
.section-title {
font-size: $font-size-base;
font-weight: $font-weight-medium;
color: $text-primary;
margin-bottom: $spacing-md;
}
.price-range {
display: flex;
align-items: center;
gap: $spacing-sm;
.price-input {
flex: 1;
padding: $spacing-sm;
border: 1px solid $border-color;
border-radius: $border-radius-md;
text-align: center;
}
.separator {
color: $text-muted;
}
}
.breed-options {
display: flex;
flex-wrap: wrap;
gap: $spacing-sm;
.breed-item {
padding: $spacing-sm $spacing-md;
background-color: $bg-light;
border-radius: $border-radius-full;
font-size: $font-size-sm;
color: $text-secondary;
&.active {
background-color: $primary-color;
color: $white;
}
}
}
.picker-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: $spacing-md;
background-color: $bg-light;
border-radius: $border-radius-md;
color: $text-primary;
}
}
}
.fab-button {
position: fixed;
right: $spacing-lg;
bottom: 100px;
width: 56px;
height: 56px;
background-color: $primary-color;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: $shadow-lg;
z-index: 999;
}
</style>

View File

@@ -0,0 +1,604 @@
<template>
<view class="profile-page">
<!-- 用户信息卡片 -->
<view class="user-card">
<view class="user-info">
<image
:src="userInfo.avatar || '/static/images/default-avatar.png'"
class="user-avatar"
@click="chooseAvatar"
/>
<view class="user-details">
<view class="user-name">{{ userInfo.nickname || '未设置昵称' }}</view>
<view class="user-phone">{{ userInfo.phone || '未绑定手机' }}</view>
<view class="user-location">
<text class="location-icon">📍</text>
<text>{{ userInfo.location || '未设置地区' }}</text>
</view>
</view>
<view class="edit-btn" @click="editProfile">
<text class="edit-icon"></text>
</view>
</view>
<!-- 统计信息 -->
<view class="stats-row">
<view class="stat-item" @click="goToMyPublish">
<view class="stat-number">{{ stats.publishCount }}</view>
<view class="stat-label">发布</view>
</view>
<view class="stat-item" @click="goToMyFavorites">
<view class="stat-number">{{ stats.favoriteCount }}</view>
<view class="stat-label">收藏</view>
</view>
<view class="stat-item" @click="goToMyOrders">
<view class="stat-number">{{ stats.orderCount }}</view>
<view class="stat-label">交易</view>
</view>
<view class="stat-item" @click="goToMyFollows">
<view class="stat-number">{{ stats.followCount }}</view>
<view class="stat-label">关注</view>
</view>
</view>
</view>
<!-- 快捷功能 -->
<view class="quick-actions">
<view class="action-item" @click="goToPublish">
<view class="action-icon">📝</view>
<view class="action-text">发布牛只</view>
</view>
<view class="action-item" @click="goToSearch">
<view class="action-icon">🔍</view>
<view class="action-text">找牛只</view>
</view>
<view class="action-item" @click="goToMessages">
<view class="action-icon">💬</view>
<view class="action-text">消息</view>
<view v-if="unreadCount > 0" class="badge">{{ unreadCount }}</view>
</view>
<view class="action-item" @click="goToWallet">
<view class="action-icon">💰</view>
<view class="action-text">钱包</view>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-section">
<view class="menu-title">我的服务</view>
<view class="menu-list">
<view class="menu-item" @click="goToMyPublish">
<view class="menu-left">
<text class="menu-icon">📋</text>
<text class="menu-text">我的发布</text>
</view>
<view class="menu-right">
<text class="menu-arrow">></text>
</view>
</view>
<view class="menu-item" @click="goToMyOrders">
<view class="menu-left">
<text class="menu-icon">📦</text>
<text class="menu-text">我的交易</text>
</view>
<view class="menu-right">
<text class="menu-arrow">></text>
</view>
</view>
<view class="menu-item" @click="goToMyFavorites">
<view class="menu-left">
<text class="menu-icon"></text>
<text class="menu-text">我的收藏</text>
</view>
<view class="menu-right">
<text class="menu-arrow">></text>
</view>
</view>
<view class="menu-item" @click="goToMyFollows">
<view class="menu-left">
<text class="menu-icon">👥</text>
<text class="menu-text">我的关注</text>
</view>
<view class="menu-right">
<text class="menu-arrow">></text>
</view>
</view>
</view>
</view>
<view class="menu-section">
<view class="menu-title">工具服务</view>
<view class="menu-list">
<view class="menu-item" @click="goToCalculator">
<view class="menu-left">
<text class="menu-icon">🧮</text>
<text class="menu-text">价格计算器</text>
</view>
<view class="menu-right">
<text class="menu-arrow">></text>
</view>
</view>
<view class="menu-item" @click="goToMarketPrice">
<view class="menu-left">
<text class="menu-icon">📈</text>
<text class="menu-text">市场行情</text>
</view>
<view class="menu-right">
<text class="menu-arrow">></text>
</view>
</view>
<view class="menu-item" @click="goToNews">
<view class="menu-left">
<text class="menu-icon">📰</text>
<text class="menu-text">行业资讯</text>
</view>
<view class="menu-right">
<text class="menu-arrow">></text>
</view>
</view>
</view>
</view>
<view class="menu-section">
<view class="menu-title">设置</view>
<view class="menu-list">
<view class="menu-item" @click="goToSettings">
<view class="menu-left">
<text class="menu-icon"></text>
<text class="menu-text">设置</text>
</view>
<view class="menu-right">
<text class="menu-arrow">></text>
</view>
</view>
<view class="menu-item" @click="goToHelp">
<view class="menu-left">
<text class="menu-icon"></text>
<text class="menu-text">帮助与反馈</text>
</view>
<view class="menu-right">
<text class="menu-arrow">></text>
</view>
</view>
<view class="menu-item" @click="goToAbout">
<view class="menu-left">
<text class="menu-icon"></text>
<text class="menu-text">关于我们</text>
</view>
<view class="menu-right">
<text class="menu-arrow">></text>
</view>
</view>
</view>
</view>
<!-- 退出登录 -->
<view class="logout-section">
<button class="logout-btn" @click="logout">退出登录</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
userInfo: {
avatar: '',
nickname: '牛老板',
phone: '138****8888',
location: '山东省 济南市'
},
stats: {
publishCount: 12,
favoriteCount: 8,
orderCount: 5,
followCount: 23
},
unreadCount: 3
}
},
methods: {
// 选择头像
chooseAvatar() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.userInfo.avatar = res.tempFilePaths[0]
// 这里应该上传到服务器
this.uploadAvatar(res.tempFilePaths[0])
}
})
},
// 上传头像
uploadAvatar(filePath) {
uni.showLoading({ title: '上传中...' })
// 模拟上传
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '头像更新成功', icon: 'success' })
}, 1500)
},
// 编辑个人资料
editProfile() {
uni.navigateTo({
url: '/pages/profile/edit-profile'
})
},
// 我的发布
goToMyPublish() {
uni.navigateTo({
url: '/pages/profile/my-publish'
})
},
// 我的收藏
goToMyFavorites() {
uni.navigateTo({
url: '/pages/profile/my-favorites'
})
},
// 我的交易
goToMyOrders() {
uni.navigateTo({
url: '/pages/profile/my-orders'
})
},
// 我的关注
goToMyFollows() {
uni.navigateTo({
url: '/pages/profile/my-follows'
})
},
// 发布牛只
goToPublish() {
uni.navigateTo({
url: '/pages/publish/publish'
})
},
// 搜索
goToSearch() {
uni.switchTab({
url: '/pages/market/market'
})
},
// 消息
goToMessages() {
uni.navigateTo({
url: '/pages/messages/messages'
})
},
// 钱包
goToWallet() {
uni.navigateTo({
url: '/pages/wallet/wallet'
})
},
// 价格计算器
goToCalculator() {
uni.navigateTo({
url: '/pages/tools/calculator'
})
},
// 市场行情
goToMarketPrice() {
uni.navigateTo({
url: '/pages/tools/market-price'
})
},
// 行业资讯
goToNews() {
uni.navigateTo({
url: '/pages/news/news'
})
},
// 设置
goToSettings() {
uni.navigateTo({
url: '/pages/settings/settings'
})
},
// 帮助与反馈
goToHelp() {
uni.navigateTo({
url: '/pages/help/help'
})
},
// 关于我们
goToAbout() {
uni.navigateTo({
url: '/pages/about/about'
})
},
// 退出登录
logout() {
uni.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
// 清除用户信息
uni.removeStorageSync('userInfo')
uni.removeStorageSync('token')
// 跳转到登录页
uni.reLaunch({
url: '/pages/login/login'
})
}
}
})
},
// 加载用户信息
loadUserInfo() {
// 模拟从服务器获取用户信息
const userInfo = uni.getStorageSync('userInfo')
if (userInfo) {
this.userInfo = { ...this.userInfo, ...userInfo }
}
},
// 加载统计数据
loadStats() {
// 模拟从服务器获取统计数据
// 这里可以调用API获取真实数据
}
},
onShow() {
this.loadUserInfo()
this.loadStats()
}
}
</script>
<style scoped>
.profile-page {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 40rpx;
}
/* 用户信息卡片 */
.user-card {
background-color: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 30rpx;
}
.user-info {
display: flex;
align-items: center;
margin-bottom: 30rpx;
}
.user-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
margin-right: 30rpx;
object-fit: cover;
}
.user-details {
flex: 1;
}
.user-name {
font-size: 36rpx;
font-weight: 600;
color: #333;
margin-bottom: 10rpx;
}
.user-phone {
font-size: 28rpx;
color: #666;
margin-bottom: 10rpx;
}
.user-location {
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
}
.location-icon {
margin-right: 8rpx;
}
.edit-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
border-radius: 30rpx;
}
.edit-icon {
font-size: 24rpx;
}
/* 统计信息 */
.stats-row {
display: flex;
justify-content: space-around;
padding-top: 30rpx;
border-top: 1rpx solid #eee;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10rpx;
}
.stat-number {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.stat-label {
font-size: 24rpx;
color: #666;
}
/* 快捷功能 */
.quick-actions {
display: flex;
justify-content: space-around;
background-color: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 30rpx 20rpx;
}
.action-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 15rpx;
}
.action-icon {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
border-radius: 40rpx;
font-size: 32rpx;
}
.action-text {
font-size: 24rpx;
color: #333;
}
.badge {
position: absolute;
top: -5rpx;
right: 15rpx;
min-width: 32rpx;
height: 32rpx;
background-color: #ff4757;
color: #fff;
border-radius: 16rpx;
font-size: 20rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8rpx;
}
/* 菜单部分 */
.menu-section {
margin: 20rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
}
.menu-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
padding: 30rpx 30rpx 20rpx;
}
.menu-list {
padding: 0 30rpx;
}
.menu-item {
display: flex;
align-items: center;
justify-content: space-between;
height: 100rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-left {
display: flex;
align-items: center;
gap: 20rpx;
}
.menu-icon {
font-size: 32rpx;
}
.menu-text {
font-size: 28rpx;
color: #333;
}
.menu-right {
display: flex;
align-items: center;
}
.menu-arrow {
font-size: 24rpx;
color: #999;
}
/* 退出登录 */
.logout-section {
margin: 40rpx 20rpx 20rpx;
}
.logout-btn {
width: 100%;
height: 80rpx;
background-color: #fff;
color: #ff4757;
border: 1rpx solid #ff4757;
border-radius: 40rpx;
font-size: 28rpx;
}
.logout-btn:active {
background-color: #ff4757;
color: #fff;
}
</style>

View File

@@ -0,0 +1,728 @@
<template>
<view class="publish-page">
<!-- 顶部导航 -->
<view class="nav-bar">
<view class="nav-left" @click="goBack">
<text class="nav-icon"></text>
</view>
<view class="nav-title">发布牛只</view>
<view class="nav-right"></view>
</view>
<!-- 表单内容 -->
<view class="form-container">
<!-- 图片上传 -->
<view class="form-section">
<view class="section-title">牛只照片</view>
<view class="image-upload">
<view class="image-list">
<view
v-for="(image, index) in formData.images"
:key="index"
class="image-item"
>
<image :src="image" class="uploaded-image" @click="previewImage(index)" />
<view class="delete-btn" @click="deleteImage(index)">×</view>
</view>
<view
v-if="formData.images.length < 9"
class="upload-btn"
@click="chooseImage"
>
<text class="upload-icon">+</text>
<text class="upload-text">添加照片</text>
</view>
</view>
<view class="upload-tip">最多上传9张照片第一张为封面</view>
</view>
</view>
<!-- 基本信息 -->
<view class="form-section">
<view class="section-title">基本信息</view>
<view class="form-item">
<view class="item-label">牛只编号 <text class="required">*</text></view>
<input
class="form-input"
v-model="formData.cattleId"
placeholder="请输入牛只编号"
maxlength="20"
/>
</view>
<view class="form-item">
<view class="item-label">品种 <text class="required">*</text></view>
<picker
mode="selector"
:range="breedOptions"
range-key="label"
:value="formData.breedIndex"
@change="handleBreedChange"
>
<view class="picker-input">
{{ formData.breed || '请选择品种' }}
<text class="picker-arrow">></text>
</view>
</picker>
</view>
<view class="form-item">
<view class="item-label">性别 <text class="required">*</text></view>
<picker
mode="selector"
:range="genderOptions"
range-key="label"
:value="formData.genderIndex"
@change="handleGenderChange"
>
<view class="picker-input">
{{ formData.gender || '请选择性别' }}
<text class="picker-arrow">></text>
</view>
</picker>
</view>
<view class="form-item">
<view class="item-label">年龄 <text class="required">*</text></view>
<view class="age-input">
<input
class="form-input"
type="number"
v-model="formData.age"
placeholder="请输入年龄"
/>
<text class="age-unit">个月</text>
</view>
</view>
<view class="form-item">
<view class="item-label">体重 <text class="required">*</text></view>
<view class="weight-input">
<input
class="form-input"
type="digit"
v-model="formData.weight"
placeholder="请输入体重"
/>
<text class="weight-unit">kg</text>
</view>
</view>
</view>
<!-- 健康状况 -->
<view class="form-section">
<view class="section-title">健康状况</view>
<view class="form-item">
<view class="item-label">健康状态 <text class="required">*</text></view>
<picker
mode="selector"
:range="healthOptions"
range-key="label"
:value="formData.healthIndex"
@change="handleHealthChange"
>
<view class="picker-input">
{{ formData.healthStatus || '请选择健康状态' }}
<text class="picker-arrow">></text>
</view>
</picker>
</view>
<view class="form-item">
<view class="item-label">疫苗接种</view>
<view class="checkbox-group">
<label
class="checkbox-item"
v-for="vaccine in vaccineOptions"
:key="vaccine.value"
>
<checkbox
:value="vaccine.value"
:checked="formData.vaccination.includes(vaccine.value)"
@change="handleVaccineChange"
/>
<text class="checkbox-label">{{ vaccine.label }}</text>
</label>
</view>
</view>
</view>
<!-- 交易信息 -->
<view class="form-section">
<view class="section-title">交易信息</view>
<view class="form-item">
<view class="item-label">出售价格 <text class="required">*</text></view>
<view class="price-input">
<text class="price-symbol">¥</text>
<input
class="form-input"
type="digit"
v-model="formData.price"
placeholder="请输入价格"
/>
</view>
</view>
<view class="form-item">
<view class="item-label">价格类型 <text class="required">*</text></view>
<picker
mode="selector"
:range="priceTypeOptions"
range-key="label"
:value="formData.priceTypeIndex"
@change="handlePriceTypeChange"
>
<view class="picker-input">
{{ formData.priceType || '请选择价格类型' }}
<text class="picker-arrow">></text>
</view>
</picker>
</view>
<view class="form-item">
<view class="item-label">交易方式 <text class="required">*</text></view>
<view class="checkbox-group">
<label
class="checkbox-item"
v-for="trade in tradeTypeOptions"
:key="trade.value"
>
<checkbox
:value="trade.value"
:checked="formData.tradeType.includes(trade.value)"
@change="handleTradeTypeChange"
/>
<text class="checkbox-label">{{ trade.label }}</text>
</label>
</view>
</view>
<view class="form-item">
<view class="item-label">所在地区 <text class="required">*</text></view>
<picker
mode="region"
:value="formData.region"
@change="handleRegionChange"
>
<view class="picker-input">
{{ formData.location || '请选择地区' }}
<text class="picker-arrow">></text>
</view>
</picker>
</view>
<view class="form-item">
<view class="item-label">详细描述</view>
<textarea
class="form-textarea"
v-model="formData.description"
placeholder="请详细描述牛只情况,包括饲养环境、饲料情况等"
maxlength="500"
/>
<view class="char-count">{{ formData.description.length }}/500</view>
</view>
</view>
<!-- 联系信息 -->
<view class="form-section">
<view class="section-title">联系信息</view>
<view class="form-item">
<view class="item-label">联系人 <text class="required">*</text></view>
<input
class="form-input"
v-model="formData.contactName"
placeholder="请输入联系人姓名"
maxlength="20"
/>
</view>
<view class="form-item">
<view class="item-label">联系电话 <text class="required">*</text></view>
<input
class="form-input"
type="number"
v-model="formData.contactPhone"
placeholder="请输入联系电话"
maxlength="11"
/>
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-bar">
<button class="save-draft-btn" @click="saveDraft">保存草稿</button>
<button class="publish-btn" @click="publishCattle">立即发布</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
formData: {
images: [],
cattleId: '',
breed: '',
breedIndex: 0,
gender: '',
genderIndex: 0,
age: '',
weight: '',
healthStatus: '',
healthIndex: 0,
vaccination: [],
price: '',
priceType: '',
priceTypeIndex: 0,
tradeType: [],
region: [],
location: '',
description: '',
contactName: '',
contactPhone: ''
},
breedOptions: [
{ label: '西门塔尔牛', value: 'simmental' },
{ label: '安格斯牛', value: 'angus' },
{ label: '夏洛莱牛', value: 'charolais' },
{ label: '利木赞牛', value: 'limousin' },
{ label: '海福特牛', value: 'hereford' },
{ label: '鲁西黄牛', value: 'luxi' },
{ label: '秦川牛', value: 'qinchuan' },
{ label: '南阳牛', value: 'nanyang' }
],
genderOptions: [
{ label: '公牛', value: 'male' },
{ label: '母牛', value: 'female' },
{ label: '阉牛', value: 'castrated' }
],
healthOptions: [
{ label: '健康', value: 'healthy' },
{ label: '良好', value: 'good' },
{ label: '一般', value: 'normal' },
{ label: '需要关注', value: 'attention' }
],
vaccineOptions: [
{ label: '口蹄疫疫苗', value: 'fmd' },
{ label: '牛瘟疫苗', value: 'rinderpest' },
{ label: '布鲁氏菌病疫苗', value: 'brucellosis' },
{ label: '结核病疫苗', value: 'tuberculosis' }
],
priceTypeOptions: [
{ label: '按头计价', value: 'per_head' },
{ label: '按重量计价', value: 'per_weight' }
],
tradeTypeOptions: [
{ label: '现场交易', value: 'offline' },
{ label: '线上交易', value: 'online' },
{ label: '支持配送', value: 'delivery' }
]
}
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack()
},
// 选择图片
chooseImage() {
const remainCount = 9 - this.formData.images.length
uni.chooseImage({
count: remainCount,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.formData.images.push(...res.tempFilePaths)
}
})
},
// 预览图片
previewImage(index) {
uni.previewImage({
current: index,
urls: this.formData.images
})
},
// 删除图片
deleteImage(index) {
this.formData.images.splice(index, 1)
},
// 品种选择
handleBreedChange(e) {
const index = e.detail.value
this.formData.breedIndex = index
this.formData.breed = this.breedOptions[index].label
},
// 性别选择
handleGenderChange(e) {
const index = e.detail.value
this.formData.genderIndex = index
this.formData.gender = this.genderOptions[index].label
},
// 健康状态选择
handleHealthChange(e) {
const index = e.detail.value
this.formData.healthIndex = index
this.formData.healthStatus = this.healthOptions[index].label
},
// 疫苗选择
handleVaccineChange(e) {
this.formData.vaccination = e.detail.value
},
// 价格类型选择
handlePriceTypeChange(e) {
const index = e.detail.value
this.formData.priceTypeIndex = index
this.formData.priceType = this.priceTypeOptions[index].label
},
// 交易方式选择
handleTradeTypeChange(e) {
this.formData.tradeType = e.detail.value
},
// 地区选择
handleRegionChange(e) {
this.formData.region = e.detail.value
this.formData.location = e.detail.value.join(' ')
},
// 表单验证
validateForm() {
if (!this.formData.cattleId) {
uni.showToast({ title: '请输入牛只编号', icon: 'none' })
return false
}
if (!this.formData.breed) {
uni.showToast({ title: '请选择品种', icon: 'none' })
return false
}
if (!this.formData.gender) {
uni.showToast({ title: '请选择性别', icon: 'none' })
return false
}
if (!this.formData.age) {
uni.showToast({ title: '请输入年龄', icon: 'none' })
return false
}
if (!this.formData.weight) {
uni.showToast({ title: '请输入体重', icon: 'none' })
return false
}
if (!this.formData.price) {
uni.showToast({ title: '请输入价格', icon: 'none' })
return false
}
if (!this.formData.contactName) {
uni.showToast({ title: '请输入联系人', icon: 'none' })
return false
}
if (!this.formData.contactPhone) {
uni.showToast({ title: '请输入联系电话', icon: 'none' })
return false
}
return true
},
// 保存草稿
saveDraft() {
uni.setStorageSync('cattle_draft', this.formData)
uni.showToast({ title: '草稿已保存', icon: 'success' })
},
// 发布牛只
publishCattle() {
if (!this.validateForm()) return
uni.showLoading({ title: '发布中...' })
// 模拟API调用
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '发布成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
}, 2000)
}
},
onLoad() {
// 加载草稿
const draft = uni.getStorageSync('cattle_draft')
if (draft) {
this.formData = { ...this.formData, ...draft }
}
}
}
</script>
<style scoped>
.publish-page {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
/* 导航栏 */
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 30rpx;
background-color: #fff;
border-bottom: 1rpx solid #eee;
}
.nav-left {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-icon {
font-size: 36rpx;
color: #333;
}
.nav-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.nav-right {
width: 60rpx;
}
/* 表单容器 */
.form-container {
padding: 20rpx;
}
.form-section {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 30rpx;
}
/* 图片上传 */
.image-upload {
width: 100%;
}
.image-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.image-item {
position: relative;
width: 200rpx;
height: 200rpx;
}
.uploaded-image {
width: 100%;
height: 100%;
border-radius: 12rpx;
object-fit: cover;
}
.delete-btn {
position: absolute;
top: -10rpx;
right: -10rpx;
width: 40rpx;
height: 40rpx;
background-color: #ff4757;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
}
.upload-btn {
width: 200rpx;
height: 200rpx;
border: 2rpx dashed #ddd;
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #999;
}
.upload-icon {
font-size: 48rpx;
margin-bottom: 10rpx;
}
.upload-text {
font-size: 24rpx;
}
.upload-tip {
font-size: 24rpx;
color: #999;
margin-top: 20rpx;
}
/* 表单项 */
.form-item {
margin-bottom: 30rpx;
}
.item-label {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
}
.required {
color: #ff4757;
}
.form-input {
width: 100%;
height: 80rpx;
padding: 0 20rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
background-color: #fff;
}
.picker-input {
display: flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
padding: 0 20rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
background-color: #fff;
color: #333;
}
.picker-arrow {
color: #999;
font-size: 24rpx;
}
.age-input, .weight-input, .price-input {
display: flex;
align-items: center;
gap: 20rpx;
}
.age-unit, .weight-unit {
font-size: 28rpx;
color: #666;
}
.price-symbol {
font-size: 32rpx;
color: #333;
font-weight: 600;
}
.form-textarea {
width: 100%;
min-height: 200rpx;
padding: 20rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
background-color: #fff;
resize: none;
}
.char-count {
text-align: right;
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
/* 复选框组 */
.checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 30rpx;
}
.checkbox-item {
display: flex;
align-items: center;
gap: 10rpx;
}
.checkbox-label {
font-size: 28rpx;
color: #333;
}
/* 底部按钮 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
gap: 20rpx;
padding: 20rpx 30rpx;
background-color: #fff;
border-top: 1rpx solid #eee;
}
.save-draft-btn {
flex: 1;
height: 80rpx;
background-color: #f5f5f5;
color: #666;
border: none;
border-radius: 40rpx;
font-size: 28rpx;
}
.publish-btn {
flex: 2;
height: 80rpx;
background-color: #007AFF;
color: #fff;
border: none;
border-radius: 40rpx;
font-size: 28rpx;
}
</style>