mysq 5 months ago
parent 83c59016da
commit 99a4baaf02

@ -0,0 +1,607 @@
Page({
data: {
uploadText: '请拍照或从相册选择照片',
imagePath: '',
isIdentified: false,
itemName: '',
itemColor: '',
itemFeature: '',
itemBrand: '',
itemPhone: '',
itemLocation: '',
phoneError: false,
isFormValid: false,
apiBaseUrl: 'http://10.11.72.16:8000/api', // 使用你的IP地址
isUploading: false,
isSubmitting: false,
lastIdentifyTime: 0, // 上次识别时间
identifyCooldown: 1000, // 识别冷却时间1秒
},
// ========== 添加缺失的方法 ==========
testConnection() {
console.log('🔗 检查网络连接...');
wx.getNetworkType({
success: (res) => {
console.log('📶 网络类型:', res.networkType);
if (res.networkType === 'none') {
wx.showToast({
title: '请检查网络连接',
icon: 'none',
duration: 2000
});
}
}
});
},
// 在 identification.js 的拍照和选择图片方法中,调整图片质量
takePhoto() {
const that = this;
wx.chooseImage({
count: 1,
sizeType: ['original'], // 提高识别率
sourceType: ['camera'],
success(res) {
const tempFilePath = res.tempFilePaths[0];
that.setData({
imagePath: tempFilePath,
uploadText: '正在识别中...'
});
that.identifyItem(tempFilePath);
},
fail(err) {
console.log('拍照失败:', err);
wx.showToast({
title: '拍照失败',
icon: 'none'
});
}
});
},
//上传照片
chooseFromAlbum() {
const that = this;
wx.chooseImage({
count: 1,
sizeType: ['original'], // 改为原图
sourceType: ['album'],
success(res) {
const tempFilePath = res.tempFilePaths[0];
that.setData({
imagePath: tempFilePath,
uploadText: '正在识别中...'
});
that.identifyItem(tempFilePath);
},
fail(err) {
console.log('选择图片失败:', err);
wx.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
},
// AI识别物品
identifyItem(imagePath) {
const that = this;
const now = Date.now();
// 检查冷却时间
if (now - this.data.lastIdentifyTime < this.data.identifyCooldown) {
wx.showToast({
title: '操作过于频繁,请稍后重试',
icon: 'none'
});
return;
}
this.setData({
lastIdentifyTime: now,
isUploading: true,
uploadText: '正在上传图片...'
});
wx.showLoading({
title: 'AI识别中...',
mask: true
});
this.setData({
isUploading: true,
uploadText: '正在上传图片...'
});
console.log('🔄 开始识别流程');
console.log('📷 图片路径:', imagePath);
console.log('🌐 API地址:', `${that.data.apiBaseUrl}/identify/`);
wx.uploadFile({
url: `${that.data.apiBaseUrl}/identify/`,
filePath: imagePath,
name: 'image',
header: {
'content-type': 'multipart/form-data'
},
timeout: 20000,
success(res) {
console.log('📨 服务器响应状态:', res.statusCode);
console.log('📄 响应原始数据:', res.data);
wx.hideLoading();
that.setData({
isUploading: false
});
try {
const result = JSON.parse(res.data);
console.log('✅ 解析后的结果:', result);
// 开发者可见显示AI描述和原始结果
if (result.ai_description) {
console.log('🤖 AI描述:', result.ai_description);
}
if (result.raw_results) {
console.log('🔍 AI原始结果:', result.raw_results);
}
if (result.success) {
const aiData = result.data;
console.log('📊 表单数据:', aiData);
// 填充表单字段
that.setData({
itemName: aiData.name,
itemColor: aiData.color,
itemFeature: aiData.feature,
itemBrand: aiData.brand,
isIdentified: true,
uploadText: '识别完成'
});
that.checkFormValidity();
// 显示识别成功提示
wx.showToast({
title: '识别完成',
icon: 'success',
duration: 1500
});
// 开发者模式下显示AI描述可选
if (result.ai_description && that.isDeveloperMode()) {
wx.showModal({
title: 'AI识别详情(开发者)',
content: result.ai_description,
showCancel: false
});
}
} else {
console.error('❌ AI识别失败:', result.message);
that.setData({
uploadText: '识别失败'
});
wx.showToast({
title: result.message || '识别失败',
icon: 'none',
duration: 3000
});
}
} catch (e) {
console.error('❌ JSON解析失败:', e);
that.setData({
uploadText: '解析失败'
});
wx.showToast({
title: '响应格式错误',
icon: 'none'
});
}
},
fail(err) {
wx.hideLoading();
that.setData({
isUploading: false,
uploadText: '请求失败'
});
console.error('❌ 网络请求失败:', err);
wx.showModal({
title: '网络异常',
content: `无法连接到识别服务,请检查网络连接\n错误: ${err.errMsg}`,
showCancel: false
});
}
});
},
// 判断是否为开发者模式(可选)
isDeveloperMode() {
// 这里可以根据需要实现开发者模式判断
// 例如:读取本地存储的设置或特定条件
return true; // 暂时返回true方便调试
},
// 在onLoad中添加开发者模式提示
onLoad() {
console.log('🎯 智能拾物登记页面加载');
console.log('🔧 开发者模式已启用可在控制台查看AI识别详情');
// 测试连接
this.testConnection();
},
// 备用数据
useFallbackData() {
const commonItems = [
{ name: '智能手机', color: '黑色', feature: '全面屏设计,多功能摄像头', brand: '请选择品牌' },
{ name: '钱包', color: '棕色', feature: '皮质材质,多卡位设计', brand: '请填写品牌' },
{ name: '钥匙串', color: '银色', feature: '金属材质,多把钥匙', brand: '请填写品牌' }
];
const randomItem = commonItems[Math.floor(Math.random() * commonItems.length)];
this.setData({
itemName: randomItem.name,
itemColor: randomItem.color,
itemFeature: randomItem.feature,
itemBrand: randomItem.brand,
isIdentified: true
});
this.checkFormValidity();
},
// 重新选择图片
rechooseImage() {
const that = this;
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album'],
success(res) {
const tempFilePath = res.tempFilePaths[0];
that.setData({
imagePath: tempFilePath
});
that.identifyItem(tempFilePath);
}
});
},
// 重新拍摄
retakePhoto() {
const that = this;
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['camera'],
success(res) {
const tempFilePath = res.tempFilePaths[0];
that.setData({
imagePath: tempFilePath
});
that.identifyItem(tempFilePath);
}
});
},
// 选择位置
chooseLocation() {
const that = this;
wx.chooseLocation({
success(res) {
if (res.address || res.name) {
that.setData({
itemLocation: res.address || res.name
}, () => {
that.checkFormValidity();
});
}
}
});
},
// 检查表单有效性
checkFormValidity() {
const {
itemName,
itemColor,
itemFeature,
itemBrand,
itemPhone,
itemLocation,
imagePath,
phoneError
} = this.data;
const phoneRegex = /^1[3-9]\d{9}$/;
const isValid =
itemName.trim() !== '' &&
itemColor.trim() !== '' &&
itemFeature.trim() !== '' &&
itemBrand.trim() !== '' &&
itemPhone.trim() !== '' &&
phoneRegex.test(itemPhone) &&
itemLocation.trim() !== '' &&
imagePath !== '' &&
!phoneError;
this.setData({
isFormValid: isValid
});
},
// 输入事件处理
onItemNameInput(e) {
this.setData({
itemName: e.detail.value
}, this.checkFormValidity);
},
onItemColorInput(e) {
this.setData({
itemColor: e.detail.value
}, this.checkFormValidity);
},
onItemFeatureInput(e) {
this.setData({
itemFeature: e.detail.value
}, this.checkFormValidity);
},
onItemBrandInput(e) {
this.setData({
itemBrand: e.detail.value
}, this.checkFormValidity);
},
onItemPhoneInput(e) {
const phone = e.detail.value;
const phoneRegex = /^1[3-9]\d{9}$/;
const isValid = phoneRegex.test(phone);
this.setData({
itemPhone: phone,
phoneError: !isValid && phone.length > 0
}, this.checkFormValidity);
if (phone.length === 11 && !isValid) {
wx.showToast({
title: '手机号格式错误',
icon: 'none'
});
}
},
onItemLocationInput(e) {
this.setData({
itemLocation: e.detail.value
}, this.checkFormValidity);
},
// 提交表单
submitForm() {
const that = this;
const { isFormValid, apiBaseUrl } = this.data;
if (!isFormValid) {
wx.showToast({
title: '请填写完整所有信息',
icon: 'none'
});
return;
}
wx.showLoading({
title: '提交中...',
mask: true
});
const formData = {
object: this.data.itemName.trim(),
color: this.data.itemColor.trim(),
feature: this.data.itemFeature.trim(),
brand: this.data.itemBrand.trim(),
telephone: this.data.itemPhone.trim(),
location: this.data.itemLocation.trim()
// imagePath: this.data.imagePath - 已移除
};
// 同时提交到后端和保存到本地
wx.request({
url: `${apiBaseUrl}/submit/`,
method: 'POST',
data: formData,
success(res) {
wx.hideLoading();
if (res.data.success) {
// 生成前端数据
const itemNumber = res.data.number || 'CAUC' + Date.now().toString().slice(-8);
const itemIcon = that.generateItemIcon(that.data.itemName);
// 创建物品对象
const newItem = {
name: that.data.itemName.trim(),
color: that.data.itemColor.trim(),
feature: that.data.itemFeature.trim(),
brand: that.data.itemBrand.trim(),
number: itemNumber,
location: that.data.itemLocation.trim(),
icon: itemIcon,
description: `${that.data.itemColor}${that.data.itemName}${that.data.itemFeature},品牌:${that.data.itemBrand}`,
telephone: that.data.itemPhone.trim(),
imagePath: that.data.imagePath, // 本地存储图片路径
// 添加后端返回的ID便于后续同步
backendId: res.data.id || res.data.number || null
};
// 保存到本地存储
that.saveItemToStorage(newItem);
wx.showModal({
title: '提交成功',
content: `物品信息已成功登记,编号:${itemNumber}`,
showCancel: false,
success: () => {
that.resetForm();
// 返回首页
wx.switchTab({
url: '/pages/index/index'
});
}
});
} else {
// 后端提交失败,尝试只保存到本地
that.saveToLocalOnly(formData);
}
},
fail(err) {
wx.hideLoading();
// 网络失败,只保存到本地
that.saveToLocalOnly(formData);
}
});
},
// 仅保存到本地的备选方案
saveToLocalOnly(formData) {
const that = this;
const itemNumber = 'CAUC' + Date.now().toString().slice(-8);
const itemIcon = this.generateItemIcon(formData.object);
const newItem = {
name: formData.object,
color: formData.color,
feature: formData.feature,
brand: formData.brand,
number: itemNumber,
location: formData.location,
icon: itemIcon,
description: `${formData.color}${formData.object}${formData.feature},品牌:${formData.brand}`,
telephone: formData.telephone,
imagePath: that.data.imagePath, // 保存本地图片路径
isLocalOnly: true // 标记为仅本地存储
};
// 保存到本地存储
this.saveItemToStorage(newItem);
wx.showModal({
title: '提交成功(本地)',
content: `物品信息已保存到本地,编号:${itemNumber}\n网络恢复后可同步到服务器`,
showCancel: false,
success: () => {
that.resetForm();
wx.switchTab({
url: '/pages/index/index'
});
}
});
},
// 生成物品图标
generateItemIcon(itemName) {
const iconMap = {
'手机': '📱',
'耳机': '🎧',
'钱包': '👛',
'钥匙': '🔑',
'水杯': '🥤',
'书包': '🎒',
'书本': '📚',
'电脑': '💻',
'眼镜': '👓',
'手表': '⌚',
'校园卡': '💳',
'身份证': '🆔',
'充电宝': '🔋',
'U盘': '💾',
'鼠标': '🖱️',
'发夹': '🎀',
'钥匙扣': '🔑'
};
for (let key in iconMap) {
if (itemName.includes(key)) {
return iconMap[key];
}
}
return '📦'; // 默认图标
},
// 保存物品到本地存储 - 修复版本
saveItemToStorage(newItem) {
const that = this;
wx.getStorage({
key: 'lostItems',
success: function(storageRes) {
const items = storageRes.data || [];
// 检查是否已存在相同物品
const existingIndex = items.findIndex(item =>
item.name === newItem.name &&
item.location === newItem.location &&
Math.abs(Date.parse(item.timestamp) - Date.parse(newItem.timestamp)) < 60000 // 1分钟内
);
if (existingIndex === -1) {
// 添加时间戳
newItem.timestamp = new Date().toISOString();
items.unshift(newItem); // 新物品添加到开头
wx.setStorage({
key: 'lostItems',
data: items,
success: function(setRes) {
console.log('✅ 物品已保存到本地存储');
},
fail: function(setErr) {
console.error('❌ 本地存储保存失败:', setErr);
}
});
}
},
fail: function(storageErr) {
// 如果没有存储过,创建新数组
newItem.timestamp = new Date().toISOString();
const items = [newItem];
wx.setStorage({
key: 'lostItems',
data: items,
success: function(setRes) {
console.log('✅ 创建新的本地存储并保存物品');
},
fail: function(setErr) {
console.error('❌ 创建本地存储失败:', setErr);
}
});
}
});
},
// 重置表单
resetForm() {
this.setData({
imagePath: '',
isIdentified: false,
itemName: '',
itemColor: '',
itemFeature: '',
itemBrand: '',
itemPhone: '',
itemLocation: '',
uploadText: '请拍照或从相册选择照片',
phoneError: false,
isFormValid: false
});
}
});

@ -0,0 +1,4 @@
{
"usingComponents": {},
"navigationBarTitleText": "智能拾物登记"
}

@ -0,0 +1,87 @@
<view class="container">
<!-- 上传区域 - 只在识别前显示 -->
<view class="upload-container {{isIdentified ? 'hidden' : ''}}">
<view class="upload-area">
<!-- 相机图标按钮 -->
<view class="camera-icon-wrapper" bindtap="takePhoto">
<view class="upload-icon">📷</view>
<view class="camera-label">点击拍照</view>
</view>
<view class="upload-text">{{uploadText}}</view>
<view class="action-buttons">
<button class="action-button photo-btn" bindtap="takePhoto">拍照</button>
<button class="action-button album-btn" bindtap="chooseFromAlbum">从相册选择</button>
</view>
</view>
</view>
<!-- 识别后的内容 -->
<view class="identified-content {{isIdentified ? '' : 'hidden'}}">
<!-- 顶部返回按钮 -->
<view class="header-bar">
<view class="back-btn" bindtap="goBackToUpload">
<text class="back-icon">←</text>
<text class="back-text">返回</text>
</view>
<view class="header-title">智能识物登记</view>
</view>
<!-- 预览图片 -->
<view class="preview-section">
<image class="preview-image" src="{{imagePath}}" mode="aspectFit"></image>
<view class="preview-actions">
<button class="action-btn rechoose-btn" bindtap="rechooseImage">重新选择</button>
<button class="action-btn retake-btn" bindtap="retakePhoto">重新拍摄</button>
</view>
</view>
<!-- 信息表单 -->
<view class="info-form">
<view class="form-title">识别结果</view>
<view class="form-item">
<text class="form-label">物品:</text>
<input class="form-input" value="{{itemName}}" placeholder="请输入物品名称" bindinput="onItemNameInput" />
</view>
<view class="form-item">
<text class="form-label">颜色:</text>
<input class="form-input" value="{{itemColor}}" placeholder="请输入物品颜色" bindinput="onItemColorInput" />
</view>
<view class="form-item">
<text class="form-label">特征:</text>
<input class="form-input" value="{{itemFeature}}" placeholder="请输入物品特征" bindinput="onItemFeatureInput" />
</view>
<view class="form-item">
<text class="form-label">品牌:</text>
<input class="form-input" value="{{itemBrand}}" placeholder="请输入物品品牌" bindinput="onItemBrandInput" />
</view>
<view class="form-item">
<text class="form-label">联系方式:</text>
<input class="form-input" value="{{itemPhone}}" placeholder="请输入您的手机号" bindinput="onItemPhoneInput" />
</view>
<view class="form-item">
<text class="form-label">当前位置:</text>
<view class="location-area">
<input class="form-input location-input" value="{{itemLocation}}" placeholder="请输入当前位置" bindinput="onItemLocationInput" />
<!-- <button class="location-btn" bindtap="getLocation">获取位置</button> -->
</view>
</view>
<view class="note">
注:若实际情况与以上识别结果有不符,可完善以上信息
</view>
<view class="form-actions">
<button class="cancel-btn" bindtap="goBackToUpload">取消</button>
<button class="submit-btn" bindtap="submitForm">发布</button>
</view>
</view>
</view>
</view>

@ -0,0 +1,295 @@
.container {
padding: 20rpx;
background-color: #f8f8f8;
min-height: 90vh;
display: flex;
flex-direction: column;
}
/* 上传容器 - 只在识别前显示 */
.upload-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
margin: -20rpx;
}
.upload-area {
background-color: #fff;
border-radius: 20rpx;
padding:70rpx 50rpx;
text-align: center;
box-shadow: 20rpx 8rpx 30rpx rgba(0,0,0,0.15);
width: 80%;
max-width: 600rpx;
}
/* 相机图标包装 */
.camera-icon-wrapper {
margin-bottom: 40rpx;
padding: 30rpx;
border-radius: 20rpx;
background: linear-gradient(135deg, #09BB07, #08a806);
display: inline-block;
cursor: pointer;
transition: all 0.3s ease;
}
.camera-icon-wrapper:active {
transform: scale(0.95);
background: linear-gradient(135deg, #08a806, #079705);
}
.upload-icon {
font-size: 120rpx;
color: white;
margin-bottom: 20rpx;
}
.camera-label {
font-size: 32rpx;
color: white;
font-weight: 500;
}
.upload-text {
color: #666;
font-size: 36rpx;
margin-bottom: 50rpx;
font-weight: 500;
}
/* 操作按钮组 */
.action-buttons {
display: flex;
gap: 30rpx;
justify-content: center;
}
.action-button {
border: none;
border-radius: 12rpx;
padding: 25rpx 40rpx;
font-size: 32rpx;
font-weight: 500;
transition: all 0.3s ease;
}
.action-button:active {
transform: scale(0.95);
}
.photo-btn {
background-color: #09BB07;
color: white;
flex: 1;
}
.album-btn {
background-color: #10AEFF;
color: white;
flex: 1;
}
/* 识别后的内容 */
.identified-content {
flex: 1;
display: flex;
flex-direction: column;
}
/* 顶部返回栏 */
.header-bar {
display: flex;
align-items: center;
padding: 20rpx 0;
margin-bottom: 20rpx;
background-color: #fff;
border-radius: 10rpx;
padding: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}
.back-btn {
display: flex;
align-items: center;
padding: 15rpx 25rpx;
border-radius: 8rpx;
background-color: #f8f8f8;
margin-right: 20rpx;
}
.back-icon {
font-size: 32rpx;
margin-right: 10rpx;
color: #333;
}
.back-text {
font-size: 28rpx;
color: #333;
}
.header-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
flex: 1;
text-align: center;
margin-right: 120rpx;
}
/* 预览区域 */
.preview-section {
background-color: #fff;
border-radius: 15rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.1);
}
.preview-image {
width: 100%;
height: 400rpx;
border-radius: 10rpx;
margin-bottom: 30rpx;
display: block;
border: 2rpx solid #eee;
}
.preview-actions {
display: flex;
justify-content: space-between;
gap: 20rpx;
}
.action-btn {
flex: 1;
border: none;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
}
.rechoose-btn {
background-color: #f0f0f0;
color: #333;
}
.retake-btn {
background-color: #10AEFF;
color: white;
}
/* 信息表单 */
.info-form {
background-color: #fff;
border-radius: 15rpx;
padding: 40rpx 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.1);
flex: 1;
display: flex;
flex-direction: column;
}
.form-title {
font-size: 38rpx;
font-weight: bold;
margin-bottom: 40rpx;
color: #333;
text-align: center;
}
.form-item {
margin-bottom: 35rpx;
display: flex;
align-items: center;
}
.form-label {
width: 180rpx;
color: #333;
font-size: 32rpx;
font-weight: 500;
}
.form-input {
flex: 1;
/* padding: 0rpx; */
border: 2rpx solid #e5e5e5;
border-radius: 10rpx;
font-size: 30rpx;
box-sizing: border-box;
transition: border-color 0.3s ease;
}
.form-input:focus {
border-color: #09BB07;
}
.location-area {
flex: 1;
display: flex;
align-items: center;
}
.location-input {
flex: 1;
margin-right: 20rpx;
}
.location-btn {
background-color: #10AEFF;
color: white;
border: none;
border-radius: 10rpx;
padding: 24rpx 30rpx;
font-size: 28rpx;
white-space: nowrap;
}
.note {
color: #999;
font-size: 26rpx;
margin-top: 40rpx;
line-height: 1.6;
padding: 20rpx;
background-color: #f9f9f9;
border-radius: 8rpx;
border-left: 4rpx solid #09BB07;
}
/* 表单操作按钮 */
.form-actions {
display: flex;
gap: 20rpx;
margin-top: auto;
padding-top: 40rpx;
}
.cancel-btn {
flex: 1;
background-color: #f8f8f8;
color: #666;
border: 2rpx solid #e5e5e5;
border-radius: 12rpx;
padding: 25rpx;
font-size: 32rpx;
font-weight: 500;
}
.submit-btn {
flex: 2;
background-color: #09BB07;
color: white;
border: none;
border-radius: 12rpx;
padding: 25rpx;
font-size: 32rpx;
font-weight: 500;
}
.hidden {
display: none;
}
Loading…
Cancel
Save