基于 Trae 开发的校园二手仓小程序

技术

picture.image

本文代码较多,PC 端访问知识库原文(https://sourl.cn/hBirCb)获得最好阅读体验~

picture.image

成果展示

实践材料

  • Trae IDE(分析文件、代码辅助、文件管理)。点击阅读原文,直达下载链接~

picture.image

  • 微信开发者工具(模拟调试、真机预览)

picture.image

  • Cmder(项目提交、远程管理)

picture.image

  • Sourcetree(本地可视化管理代码,Bug 追查)

picture.image

本实践需要的语法基础

“ 说明:此项目初衷是能够快速成功部署上线,且本人电气专业,非计算机出身,经切身实践验证,纯小白在项目初期面对微信小程序环境,只需最基本的前端语法基础!相信AI辅助效能!相信自己能力!

前端三大件*(对应微信小程序可以简单理解为换了个名字)*

1..wxml(定义网页的结构和内容:基础外观)

  • 类似 HTML,但使用的是微信小程序自定义的标签和语法。
  • 使用了类似于 Vue.js 的模板语法,支持数据绑定、条件渲染、列表渲染等功能。
  • 标签是微信小程序特有的,例如

、 等,而不是标准的 HTML 标签。

  • 项目示例
  
<view class="t\_card" bindtap="home">  
      <image src="/images/home.png"></image>  
      <text>首页</text>  
</view>  

2..wxss(控制网页的布局、视觉样式:包装美化)

  • 类似于 CSS,但有一些扩展功能。
  • 支持大部分 CSS 属性,但也有一些限制(比如某些高级 CSS 特性可能不支持)。
  • 增加了尺寸单位 rpx,可以根据屏幕宽度进行自适应布局(750rpx = 屏幕宽度)。
  • 支持全局样式和局部样式。
  • 项目示例
  
.avatar {  
      width: 100rpx;  
      height: 100rpx;  
      border-radius: 50%;  
      margin-right: 16rpx; /* 给头像右边一些空间 */  
      border: 2px solid #fbbd08;  
      object-fit: cover; /* 确保图像覆盖整个圆形区域 */  
}  
  
.details {  
      display: flex;  
      flex-direction: column;  
      justify-content: space-between;  
      height: 100%;  
      margin-right: auto; /* 让联系信息靠右 */  
}  
  

3..js(添加交互性和动态功能:逻辑功能)

  • 类似 JavaScript
  • 负责处理页面的数据、事件和生命周期。
  • 提供了微信小程序特有的 API,比如 wx.request(网络请求)、wx.showToast(弹窗提示)等。
  • 数据绑定采用单向数据流(类似于 React 或 Vue 的响应式机制)。
  • 页面逻辑通过 Page() 方法定义,包含 data(数据)、onLoad(生命周期函数)、事件处理等。
  • 项目示例
  
    /**  
    * 页面的初始数据  
    */  
    data: {  
        first\_title: true,  
        //place: '',  
    },  
    onLoad(e) {  
        this.getuserdetail();  
        this.data.id = e.scene;  
        this.getPublish(e.scene);  
    },  
    changeTitle(e) {  
        let that = this;  
        that.setData({  
              first\_title: e.currentTarget.dataset.id  
        })  
    },  

开始实践

项目实践目的

  • 项目说明
  • 因为追求效率,所以使用开源项目 GitHub开源二手书籍交易平台 进行大 换 血 ,在此非常感谢作者进行分享。总共提交了 34 次,耗时 13378 分钟,从设计到UI再到所有页面逻辑代码皆由本人一人修改测验完成。
  • 作为专门为校内服务的二手仓小程序,初衷就是快速进行绿色交换,一个纯粹二手交易发布信息的发布平台,并没有做留言,私信,点赞和线上支付等功能,一是因为学校内部就已经拥有了一个非常棒的小程序,能方便我们学生进行交流娱乐等,而我只是细分了一个二手的功能;二是因为我作为个人,非企业,还没有相关权限,资金也不够支撑;三是因为我个人精力有限,运营安全方面无法顾及。
  • 前期准备
  • 微信小程序作为一款公众应用不仅仅是代码项目的实现,个人还需要掌握 微信公众平台 注册小程序并进行后期资料的补充;以及通过 微信官方文档 确保代码规范性,防止使用停维接口,确保代码稳健性;当遇到问题时,寻求 微信开放社区 上的经验作为辅助,为了版本迭代维护,建议附加学习使用 git 代码管理相关工具。
  • 云开发
  • 作为小白,我们只需要完成前端的内容,后端的内容选择微信云开发, 注意一定是在创建项目前就选择“微信云开发”,而非选择“不使用云服务”再后期在项目里面点击“云开发”进行开通 ,否则会出现云开发开通失败的情况!

picture.image

代码实现过程

跨维认知,踏出壁垒

本人虽为小白,但是面对任何事情都抱有学习的态度,不分专业,不分技术,只要条件足够,自己想做,有骨子干劲,不会就学呗~ 谁不是从 0 开始的?大多数阻碍我们的就是我们自己,收藏夹吃灰的宝藏一直就在那里静静地躺着,一遇到一点难处就放弃,半途而废,不实践永无提升与成长,逼自己一把,保持热忱的心态,终究也能成为大佬!技术落地能力+超导体级执行力——即刻启动!

学会模板,快速搭建

对于新手开发者来说,使用项目模板进行开发是一个不错的选择。这不仅能够帮助快速上手,还能让我们在实践中学习最佳实践和规范,通过使用这些模板,可以避免一些常见的陷阱,并且学习到正确的开发方式。而且,理解一个完整的项目架构对于一个新手可能会感到压力山大,模板提供了一个简化的起点,使得我们可以专注于学习核心概念和技术,利用 Trae 快速搭建,而不是被复杂的配置所困扰。在这个项目之前,我也利用模板利用 AI 制作了个人网站—— SpongeBob 的个人网站,且已经完全部署、成功上线!欢迎访问!

picture.image

源码理解,Trae 一下

阅读源码、理解源码是项目修改第一步,这部分没有做好,后期工作难以开展。利用 Trae 的 Chat 帮助我进行大致文件的分析,梳理好逻辑,确定好自己的设计需求,综合考量项目制作强度,并添加相应的编译模式。

picture.image

迭代综述,Trae 赋能

  1. 设计思路
  • 首页: 参考淘宝、闲鱼等交易平台的 UI 设计,并保留原作者的宫格/列表展示功能和切换导航标签功能,拟定列表形式下每个商品 or 需求模块展示图片、详情(截取 20 字)、类别标签、价格、用户昵称、头像;宫格形式下展示图片、类别标签、价格。整体错落有致,信息关键醒目。

picture.image

picture.image

  
<!-- 分类导航 -->  
<view class="{{scrollTop>310?'nofixed':''}}"></view>  
<view class="kind\_contain {{scrollTop>310?'fixed':''}}">  
      <view class="nav-item {{-2==collegeCur?'tab-on':''}}" bindtap="selectAll">  
            <view class="nav-text">全部</view>  
      </view>  
      <scroll-view scroll-x class="nav" scroll-with-animation scroll-left="{{scrollLeft}}rpx">  
            <view class="nav-item" wx:for="{{college}}" wx:key="id" bindtap="collegeSelect" data-id="{{index}}">  
                  <view class="nav-text {{index==collegeCur+1?'tab-on':''}}">{{item.name}}</view>  
            </view>  
      </scroll-view>  
      <view class="kind\_img" bindtap="showlist">  
            <image lazy-load src="{{showList?'/images/l\_down.png':'/images/l\_right.png'}}" />  
      </view>  
      <view class="kindlist\_box" wx:if="{{showList}}">  
            <view class="kindlist\_card">  
                  <view class="list\_grid">  
                        <block wx:for="{{college}}" wx:key="id">  
                              <view class="list\_one" bindtap="collegeSelect" data-id="{{index}}" data-class="{{item.id}}">  
                                    <view class="{{index==collegeCur+1?'list-on':''}}">  
                                          {{item.name}}  
                                    </view>  
                              </view>  
                        </block>  
                  </view>  
            </view>  
      </view>  
</view>  
<!-- 宫格显示 -->  
<view hidden="{{!iscard}}">  
      <view class="card\_grid" wx:if="{{list.length>0}}">  
            <block wx:for="{{list}}" wx:key="\_id">  
                  <view class="card\_one" bindtap="detail" data-id="{{item.\_id}}">  
                        <image lazy-load class="card\_poster" mode="aspectFill" src="{{item.images && item.images.length > 0 ? item.images[0] : '/images/moren.png'}}" />  
                        <view class="card\_between">  
                              <view class="card\_title text-cut">{{item.type}}</view>  
                              <view class="card\_author text-cut">{{item.category}}</view>  
                        </view>  
                        <view class="card\_between">  
                              <view class="list\_price">  
                                    <text class="price-symbol"></text>  
                                    {{item.price}}  
                              </view>  
                              <image lazy-load class="card\_buy" src="/images/buy.png"></image>  
                        </view>  
                  </view>  
            </block>  
      </view>  
</view>  
<!-- 列表显示 -->  
<view hidden="{{iscard}}">  
      <block wx:if="{{list.length>0}}">  
            <block wx:for="{{list}}" wx:key="\_id">  
                  <view class="list\_box" bindtap="detail" data-id="{{item.\_id}}">  
                        <image lazy-load class="list\_poster" mode="aspectFill" src="{{item.images && item.images.length > 0 ? item.images[0] : '/images/moren.png'}}" />  
                        <view class="list\_content">  
                              <view class="list\_title">{{item.details}}</view>  
                              <view class="list\_word">  
                                    <view class="list\_info">  
                                          <text class="type-text">{{item.type}}</text>  
                                          <text class="category-text">{{item.category}}</text>  
                                    </view>  
                              </view>  
                              <view class="list\_between">  
                                    <view class="list\_price">  
                                          <text class="price-symbol"></text>  
                                          {{item.price}}  
                                    </view>  
                              </view>  
                              <view class="user\_info\_container">  
                                    <view class="user\_info">  
                                          <!-- <image class="avatar" lazy-load src="{{item.gender==0?'/images/girl.png':'/images/boy.png'}}"></image> -->  
                                          <image class="avatar" mode="aspectFill" lazy-load src="{{item.touxiang || '/images/avator.png'}}" />  
                                          <view class="{{item.\_openid == 'ofJNi7PO93SYI1cTNif3ltnTjJ-I' ? 'admin-nickname' : 'normal-nickname'}}">  
                                                {{item.nickname}}  
                                          </view>  
                                    </view>  
                                    <view class="tab">详情</view>  
                              </view>  
                        </view>  
                  </view>  
            </block>  
      </block>  
</view>  

  • 发布页: 分步骤一二三根据校园交易信息有效性,决定增加步骤一发布需求 or 发布商品,并提供种类供选择,步骤二相应的信息模版为详情+联系方式+图片+价格形式,填写关键信息,最终信息上传至 publish 集合。

picture.image

picture.image

picture.image

  
const db = wx.cloud.database();  
const app = getApp();  
const config = require("../../config.js");  
Page({  
      data: {  
            systeminfo: app.systeminfo,  
            entime: {  
                  enter: 600,  
                  leave: 300  
            }, //进入褪出动画时长  
            college: JSON.parse(config.data).college.splice(1),  
            steps: [{  
                        text: '步骤一',  
                        desc: '选择标签'  
                  },  
                  {  
                        text: '步骤二',  
                        desc: '补充具体信息'  
                  },  
                  {  
                        text: '步骤三',  
                        desc: '发布成功'  
                  },  
            ],  
            categories: [  
              { name: '书籍资料', days: 60 , id: -1 },  
              { name: '学习用具', days: 60 , id: 0  },  
              { name: '数码产品', days: 45 , id: 1  },  
              { name: '衣饰化妆', days: 45 , id: 2  },  
              { name: '运动器材', days: 45 , id: 3  },  
              { name: '寝具用品', days: 45 , id: 4  },  
              { name: '委托合作', days: 3 , id: 5   },  
              { name: '其他', days: 30 , id: 6 }  
            ],  
            selectedCategory: '', // 存储用户选择的类别  
            isProductOrDemand: '', // 存储用户选择的商品或需求  
            cids: '-1', //学院选择的默认值  
            dura: 0, // 根据选择自动设定  
            details: '', // 新增详情信息输入  
            contactInfo: '', // 新增联系方式输入  
            images: [], // 存储选择的图片路径  
            maxImages: 4, // 最大图片数量  
            price: '', // 默认为空字符串  
            showPopup: false, // 控制弹出层显示/隐藏  
            inputFocused: false // 控制输入框是否自动聚焦  
      },  
      //恢复初始态  
      initial() {  
            let that = this;  
            that.setData({  
                  price: '',  
                  selectedCategory: '',  
                  isProductOrDemand: '',  
                  cids: '-1', //学院选择的默认值  
                  details: '',  
                  contactInfo: '',  
                  images: [],  
                  show\_a: true, // 初始显示步骤一  
                  show\_b: false, // 初始不显示步骤二  
                  show\_c: false,// 初始不显示步骤三  
                  active: 0 // 初始激活步骤一  
            })  
      },  
      onLoad() {  
            this.initial();  
            //this.setData({ price: '0' });  
      },  
  
      // 步骤一新增选择发布类型和类别的方法  
chooseType(e) {  
  const type = e.detail.value;  
  this.setData({ isProductOrDemand: type });  
},  
chooseCategory(e) {  
  const categoryIndex = e.detail.value;  
  const selectedCategory = this.data.categories[categoryIndex];  
  this.setData({  
    cids:selectedCategory.id,  
    selectedCategory: selectedCategory.name,  
    dura: selectedCategory.days  
  });  
},  
  
        
      confirm() {  
        console.log("开始执行 confirm 方法");  
        //console.log("当前的 openid: ", app.openid);  
            let that = this;  
             
            if (!that.data.isProductOrDemand || !that.data.selectedCategory) {  
              wx.showToast({  
                title: '请选择发布类型和类别',  
                icon: 'none'  
              });  
                
              returnfalse;  
            }  
            if (!app.openid) {  
                  wx.showModal({  
                        title: '温馨提示',  
                        content: '该功能需要注册方可使用,是否马上去注册',  
                        success(res) {  
                              if (res.confirm) {  
                                    wx.navigateTo({  
                                          url: '/pages/login/login',  
                                    })  
                              }  
                        }  
                  })  
                  returnfalse  
            }  
            //console.log("所有条件已满足,准备继续执行");  
              
              // 直接进入下一步骤,模拟已登录状态  
  that.setData({  
    show\_a: false,  
    show\_b: true,  
    active: 1,  
  });  
      },  
  
  
  
//步骤二上传逻辑  
  
// 新增的事件处理函数  
  
bindDetailsInput(e) {  
  this.setData({  
    details: e.detail.value  
  });  
},  
  
bindContactInput(e) {  
  this.setData({  
    contactInfo: e.detail.value  
  });  
},  
//图片  
chooseImage: function() {  
  const that = this;  
  const count = this.data.maxImages - this.data.images.length; // 计算还可以选择多少张图片  
  
  wx.chooseImage({  
    count: count,  
    sizeType: ['original', 'compressed'],  
    sourceType: ['album', 'camera'],  
    success(res) {  
      const tempFilePaths = res.tempFilePaths;  
      // 上传每一张选中的图片  
      Promise.all(tempFilePaths.map(tempFilePath =>   
        wx.cloud.uploadFile({  
          cloudPath: `your-folder-name/${Date.now()}-${Math.floor(Math.random(0, 1)*1000)}.png`, // 根据需要设置文件路径和名称  
          filePath: tempFilePath,  
        })  
      )).then(results => {  
        const fileIds = results.map(result => result.fileID);  
        that.setData({  
          images: that.data.images.concat(fileIds), // 将fileID存入images数组  
        });  
      }).catch(error => {  
        console.error('图片上传失败:', error);  
      });  
    },  
    fail() {  
      // 如果用户取消选择图片,这里可以不做处理  
    }  
  });  
},  
      
      deleteImage: function(e) {  
        const index = e.currentTarget.dataset.index;  
        let images = this.data.images;  
        images.splice(index, 1); // 从数组中移除对应的图片  
        this.setData({  
          images: images,  
        });  
      },  
      
      previewImage: function(e) {  
        const index = e.currentTarget.dataset.index;  
        const current = this.data.images[index];  
        wx.previewImage({  
          current: current,  
          urls: this.data.images  
        });  
      },  
  
  
// 显示数字输入框  
// 显示数字输入框  
showPriceInput() {  
  this.setData({  
    showPopup: true,  
    inputFocused: true // 设置为true以自动聚焦  
  }, () => {  
    // 使用定时器延迟执行,确保弹出层已经渲染完成  
    setTimeout(() => {  
      const inputComponent = this.selectComponent('#priceInput');  
      if (inputComponent) {  
        inputComponent.focus(); // 手动调用focus方法  
      }  
    }, 300); // 根据实际情况调整延迟时间  
  });  
},  
  
// 隐藏数字输入框  
hidePriceInput() {  
  this.setData({  
    showPopup: false,  
    inputFocused: false // 关闭自动聚焦  
  });  
},  
  
// 当输入价格时触发  
onPriceInput(event) {  
let value = event.detail.value;  
  
  // 如果输入为空,直接设置为空字符串  
if (value === '') {  
    this.setData({ price: '' });  
    return;  
  }  
  
  // 检查并处理输入值  
if (/^\.\d$/.test(value)) { // 如果以单个小数点开头并跟随一个数字,在前面补0  
    value = '0' + value;  
  } elseif (value.includes('.')) { // 包含小数点的情况  
    const [integerPart, decimalPart] = value.split('.');  
      
    // 整数部分不能超过4位  
    let formattedIntegerPart = integerPart;  
    if (integerPart.length > 6) {  
      formattedIntegerPart = integerPart.substring(0, 6); // 只保留前4位  
    }  
    // 小数部分不能超过1位  
    let formattedDecimalPart = decimalPart.length > 1 ? decimalPart.substring(0, 1) : decimalPart;  
  
    // 合并整数和小数部分  
    value = formattedIntegerPart + '.' + formattedDecimalPart;  
  } else { // 不包含小数点的部分  
    // 整数部分长度限制为4位  
    if (value.length > 6) {  
      value = value.substring(0, 6); // 只保留前4位  
    }  
    value = parseInt(value, 10).toString(); // 转换为整数后转回字符串  
  }  
  
  // 更新价格数据  
  this.setData({ price: value }, () => {  
    // 确保即使输入不合规,界面也反映正确的值  
    const currentPrice = this.data.price;  
    if (currentPrice && currentPrice.includes('.')) {  
      const [integerPart, decimalPart] = currentPrice.split('.');  
      if (decimalPart.length > 1) {  
        // 如果小数部分超过了1位,则调整它  
        this.setData({ price: `${integerPart}.${decimalPart.substring(0, 1)}` });  
      }  
    }  
  });  
},  
  
goToPreviousStep: function() {  
  this.setData({  
    show\_a: true, // 显示步骤一  
    show\_b: false, // 隐藏步骤二  
    active: 0 // 更新步骤条的激活状态为步骤一  
  });  
},  
  
// 发布逻辑  
publish() {  
let that = this;  
  
  // 检查是否已经获取了用户的openid  
if (!app.openid) {  
    wx.showToast({  
      title: '请先登录',  
      icon: 'none'  
    });  
    return;  
  }  
  
  // 使用openid从user集合中获取用户的昵称  
  db.collection('user').where({  
    \_openid: app.openid  
  }).get({  
    success: function(res) {  
      if (res.data.length > 0) { // 确保找到了用户数据  
        const nickname = res.data[0].Nickname; // 假设字段名为Nickname,请根据实际情况修改  
        const xinbie = res.data[0].gender.id;  
        const avatar = res.data[0].info.avatarUrl;  
        // 继续进行发布的逻辑  
        if (!that.data.details || that.data.details.length > 210 || !that.data.price || !that.data.contactInfo || that.data.contactInfo.length > 20) {  
          wx.showToast({  
            title: '请检查详情、价格联系方式',  
            icon: 'none'  
          });  
          returnfalse;  
        }  
        wx.showModal({  
          title: '温馨提示',  
          content: '请确保您填写的信息无误,是否马上发布?',  
          success(res) {  
            if (res.confirm) {  
              db.collection('publish').add({  
                data: {  
                  type: that.data.isProductOrDemand == 'demand' ? '需求':'商品',  
                  category: that.data.selectedCategory,  
                  collegeid: that.data.cids,  
                  details: that.data.details,  
                  contactInfo: that.data.contactInfo,  
                  images: that.data.images,  
                  price: that.data.price,  
                  createTime: db.serverDate(),  
                  nickname: nickname, // 添加用户昵称  
                  openid: app.openid, // 确保openid也被上传  
                  gender: xinbie,  
                  touxiang: avatar,  
                  //campus: that.data.campus,  
                  dura: new Date().getTime() + that.data.dura * (24 * 60 * 60 * 1000),  
                },  
                success(e) {  
                  that.setData({  
                    show\_a: false,  
                    show\_b: false,  
                    show\_c: true,  
                    active: 2,  
                    detail\_id: e.\_id  
                  });  
                  wx.pageScrollTo({ scrollTop: 0 });  
                },  
                fail(err) {  
                  console.error('发布失败:', err);  
                  wx.showToast({  
                    title: '发布失败,请重试',  
                    icon: 'none'  
                  })  
                }  
              })  
            }  
          }  
        });  
      } else {  
        wx.showToast({  
          title: '未找到用户信息,请检查登录状态',  
          icon: 'none'  
        });  
      }  
    },  
    fail: function(err) {  
      console.error('获取用户信息失败:', err);  
      wx.showToast({  
        title: '获取用户信息失败,请稍后再试',  
        icon: 'none'  
      });  
    }  
  });  
},  
      detail() {  
            let that = this;  
            wx.navigateTo({  
                  url: '/pages/detail/detail?scene=' + that.data.detail\_id,  
            })  
      }  
})  

  • 详情页: 相关信息从云开发中的 publish 集合中获取,并根据当前用户的 openid 从 user 集合中获取用户的头像、昵称;点击用户头像跳转至个人发布页面,查看该用户的发布记录,如果是本用户所发,则相应增加删除键。

picture.image

picture.image

picture.image

picture.image

  
<wxs src="../../common.wxs" module="morejs" />  
<view class="top\_contain" style="height: 530rpx; display: flex; box-sizing: border-box">  
      <swiper class="top\_img" indicator-dots="true" autoplay="true" interval="3000" duration="500" style="height: 420rpx; display: flex; box-sizing: border-box; width: 700rpx">  
            <block wx:for="{{images}}" wx:key="index">  
                  <swiper-item>  
                        <image lazy-load src="{{item}}" mode="aspectFill" bindtap="previewImage" data-index="{{index}}"></image>  
                  </swiper-item>  
            </block>  
      </swiper>  
      <view class="price\_box" style="position: relative; left: -1rpx; top: -6rpx">  
            <view class="now\_price">¥{{price}}元</view>  
            <view class="publish\_type">{{publishType}}</view>  
            <view class="category">{{category}}</view>  
      </view>  
</view>  
<view class="blank" style="height: 27rpx; display: block; box-sizing: border-box"></view>  
<view class="center\_contain">  
      <view class="c\_title title\_on">发布详情</view>  
</view>  
<!-- 发布信息 -->  
<view class="user\_box">  
      <image lazy-load mode="aspectFill" src="{{avatarUrl}}" class="avatar" bindtap="goToAuthorPage"></image>  
      <view class="details">  
            <view class="sex">  
                  <image lazy-load src="{{genderId==0?'/images/girl.png':'/images/boy.png'}}"></image>  
            </view>  
            <view class="des\_box">  
                  <view class="{{isadmin == 'ol6n36\_xtRPxfRrclkXGnNe-T4OY' ? 'admin-name' : 'normal-name'}}">  
                        {{username}}  
                  </view>  
            </view>  
      </view>  
      <view class="contact">{{contactInfo}}</view>  
</view>  
<view class="detail\_box">  
      <view class="detail\_content">{{details}}</view>  
</view>  
<view class="time\_box">  
      <view class="time">发布于{{createtime}}</view>  
</view>  
<view style="height: 96rpx;"></view>  
<!-- 底部导航 -->  
<view class="tabbar">  
      <view class="t\_card" bindtap="home">  
            <image src="/images/home.png"></image>  
            <text>首页</text>  
      </view>  
      <view class="t\_card">  
            <image src="/images/share.png"></image>  
            <text>分享</text>  
            <button class="t\_button" open-type="share"></button>  
      </view>  
      <view class="t\_card collect\_box" bindtap="collect">  
            <view class="collect shadow" style="width: 100%; display: flex; justify-content: center; align-items: center;">  
                  收藏  
            </view>  
      </view>  
</view>  
<!-- 悬浮客服功能 -->  
<view class="contact\_box" bindtap="go" data-go="/pages/kefu/kefu" animation="{{animationKefuData}}">  
      <image src="/images/ww.jpg"></image>  
      <view>反馈</view>  
</view>  

  • 注册登录页: 因为该小程序的目标用户为校内学生,且最终的交易为用户之间直接加联系方式进行,平台不承担诸如线上支付、跑腿等功能,考虑快捷性和隐私性,注册时不直接获取微信用户的头像、昵称、微信号等,让用户自己填写昵称,性别,并供学院选择,后期也可以上传头像,修改信息等;再次登录无需密码等验证方式,直接采用 openid 进行 user 集合检索匹对。

picture.image

picture.image

picture.image

  
const db = wx.cloud.database();  
const app = getApp();  
const config = require("../../config.js");  
Page({  
  
      /**  
       * 页面的初始数据  
       */  
      data: {  
            ids: -1,  
            xinbie: -1,  
             
            Nickname: '',  
            campus: JSON.parse(config.data).campus,  
            gender: JSON.parse(config.data).gender,  
              
      },  
      choose: function(e) {  
            let that = this;  
            const type = e.currentTarget.dataset.type; // 获取选择器类型  
            const index = e.detail.value; // 获取选择的索引值  
              
            if (type === 'campus') {  
                // 更新学院选择状态  
                that.setData({  
                    ids: index  
                });  
            } elseif (type === 'gender') {  
                // 更新性别选择状态  
                that.setData({  
                    xinbie: index  
                },() => {  
                  console.log("更新后的xinbie:", that.data.xinbie); // 打印更新后的xinbie  
              });  
            }  
        },  
        
  
      // 用户手动输入昵称的处理函数  
    handlenameInput(e) {  
      this.setData({  
        Nickname: e.detail.value.trim()  
      });  
    },  
       
       
       
      getUserInfo() {  
            let that = this;  
            wx.getUserProfile({  
                  desc: '获取用户信息',  
                  success: (res) => {  
                        that.setData({  
                              userInfo: res.userInfo  
                        })  
                        that.check();  
                  },  
                  fail: (err) => {  
                        wx.showToast({  
                              title: '获取用户信息失败',  
                              icon: 'none',  
                              duration: 2000  
                        });  
                  }  
            })  
      },  
      //校检  
      check() {  
            let that = this;  
              
            let username = that.data.Nickname; // 使用用户手动输入的昵称  
            if (username == '') {  
                  wx.showToast({  
                        title: '请先填写你的昵称',  
                        icon: 'none',  
                        duration: 2000  
                  });  
                  returnfalse  
            }  
            //先检查昵称是否已存在  
            db.collection('user').where({  
                  Nickname: username  
            }).get({  
                  success: function(res) {  
                        if (res.data.length > 0) {  
                              wx.showToast({  
                                    title: '该昵称已被使用,请更换',  
                                    icon: 'none',  
                                    duration: 2000  
                              });  
                              returnfalse;  
                        }  
                        //继续执行其他校验  
                        //校检校区  
                        let ids = that.data.ids;  
                        if (ids == -1) {  
                              wx.showToast({  
                                    title: '请选择您的学院',  
                                    icon: 'none',  
                                    duration: 2000  
                              });  
                              returnfalse  
                        }  
                        //校验性别  
                        let xinbie = that.data.xinbie;  
                        if (xinbie == -1) {  
                              wx.showToast({  
                                    title: '请选择您的性别',  
                                    icon: 'none',  
                                    duration: 2000  
                              });  
                              returnfalse  
                        }  
                        //所有校验通过,继续执行提交  
                        wx.showLoading({  
                              title: '正在提交',  
                        });  
                        console.log("xinbie 的值:", that.data.xinbie);  
                        db.collection('user').add({  
                              data: {  
                                    campus: that.data.campus[that.data.ids],  
                                    gender: that.data.gender[that.data.xinbie],  
                                    Nickname: that.data.Nickname,  
                                    stamp: new Date().toLocaleString(),  
                                    info: Object.assign({}, that.data.userInfo, { nickName: username }), // 修改部分  
                                    useful: true,  
                                    parse: 0,  
                              },  
                              success: function(res) {  
                                    //console.log(res)  
                                    db.collection('user').doc(res.\_id).get({  
                                          success: function(res) {  
                                                app.userinfo = res.data;  
                                                app.openid = res.data.\_openid;  
                                                // 添加这行代码来保存 openid 到本地存储  
                                                wx.setStorageSync('openid', res.data.\_openid);  
                                                wx.navigateBack({})  
                                          },  
                                    })  
                              },  
                              fail() {  
                                    wx.hideLoading();  
                                    wx.showToast({  
                                          title: '注册失败,请重新提交',  
                                          icon: 'none',  
                                    })  
                              }  
                        })  
                  }  
            })  
      },  
})  

2.制作步骤

“ 在开发过程中,Trae 主要用于快速分析代码逻辑缺陷和性能瓶颈。典型的协作流程为:上传代码片段→接收优化建议→本地调试验证,这一模式显著减少了重复性调试时间。

【以发布页为例】

  • 内容及样式: 添加 publish.wxml 文件至对话中,在 Trae 的 Builder 模式下提出自己的迭代需求,一旦修改后,不用着急点击全部接受,而是去微信开发者工具同步打开同样路径的项目进行实时调试预览,直至首页的结构内容元素达到设计需求。而修改 publish.wxss 页面布局样式也是类似操作,但是相对前者需要更多的耐心进行试验。
  • 页面交互: 这一步即添加 publish.js 至对话中,依次解决步骤一、二、三的交互逻辑,每完成一个步骤都会感叹Trae的能效!相对于前者交互逻辑修改较为简单,毕竟框架基础搭好了,无非选择在哪个元素添加触发函数。

picture.image

3.问题解决

“ 作为小白,利用 Trae 辅助编写项目,毕竟自己的编程功底薄弱,唯一的问题也即全部的问题——调试不出来自己想要的效果。

  • 最小化复现问题

不要一次性调试整个页面,而是拆解成最小代码块(比如先让一个按钮生效),用 console.log 或调试工具逐行验证数据流。

  • 对比学习:Trae + 官方文档

Trae 解释代码 → 再去查 MDN/微信小程序文档进行深入理解。例如:Trae 提示“this.setData 是异步的”,我就去研究官方对数据更新的说明。

  • 改一点,测一点

不要一次性改大量代码!每改一个小部分就立刻预览测试,如果报错,先用 Trae 分析,再尝试自己根据错误信息推测原因。

  • 心态!心态!心态!调试是一场修行!

一个 bug 卡半天很正常,别急着放弃;每天解决一个小问题,一个月后就能独立写功能;Trae 给的答案,要自己动手改、自己运行验证。

  • 记录日志

遇到一个坑,就记录下来(比如:“wx.request 的 success 回调里 this 会变,要用 箭头函数”),下次再遇到类似问题,就能快速解决。

picture.image

  • 总结

不断的提问,预览和调试,遇到错误也还是交给 AI ,让它帮自己进行一个分析,但并非说 AI 是万能的,一个没有任何代码基础的小白,可以说都不知道怎么提问,代码就算给出来了,总得复制过来进行一个调试是吧,基础的一个软件操作界面也是必要的,还有后面出现一些 bug ,无脑听从 AI 解决方案,不去进行一个问题的理解,没有自己的主见,往往是非常被动的!

不会敲代码没关系,但是要学会提问,从问题解析中挖掘相应的知识,进行吸收,当做下一个项目时候,能够得心应手,对整体的一个项目框架和逻辑有初步的认知,否则根本无从下手,都不知道从哪个文件进行修改,对自己的学习应用能力提升也毫无作用,当初次打开这个开源项目的时候,并非上来直接就是开始修改,而是花了两三天,去理解每个文件的作用,小程序的文件特性,比如每个文件夹下这固定4个文件的作用是什么,还有运行环境,云开发等,去看官方文档,或者利用AI帮助理解,做笔记,画思维导图,写出自己的一个设计方案,记录初始每个文件所实现的一个功能,最后呢才开始着手进行一个修改。

总结思考

实现功能

  • login 用户一次性登录注册,修改个人信息。
  • start 校园二手仓启动页。
  • index 首页列表/宫格展示商品/需求,并可切换导航栏筛选转至相应类别。
  • publish 用户发布:选择标签、分类,填写详情至发布成功。
  • detail 详情展示,可跳转个人发布页。
  • help 客服帮助跳转反馈。
  • search 搜索及历史。
  • my “我的”个人界面。

体验版发布

picture.image

存在问题

  • 图片加载慢,考虑是自己选择沿用原代码版本语言格式,未顺应最新技术规范。
  • 跳转等交互响应流畅度不够,考虑是自己小白水平有限,性能优化知识薄弱。

探索方向

  • 在原有的基础上,优化响应加载性能。

  • 增添用户留言,私信,点赞,收藏功能。

  • 建立健全的发布审核机制。

picture.image

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论