README.md 17.1 KB

捡个电驴 - Taro4 微信小程序项目

项目介绍

基于Taro4框架开发的微信小程序,主要功能包括二手电动车交易、用户认证、订单管理、消息通知等。项目采用Vue3 + Composition API + Pinia状态管理的现代化开发方式。

技术栈

  • 框架: Taro4 + Vue3 (Composition API)
  • UI组件库: NutUI4 + @nutui/icons-vue-taro
  • 状态管理: Pinia
  • 样式: Less + TailwindCSS
  • 网络请求: axios-miniprogram
  • 包管理: pnpm
  • 构建工具: Webpack5

项目结构

src/
├── api/                    # API接口层
│   ├── index.js           # 用户相关API
│   ├── car.js             # 车辆相关API
│   ├── orders.js          # 订单相关API
│   ├── chat.js            # 聊天相关API
│   ├── common.js          # 通用API
│   ├── other.js           # 其他API
│   ├── fn.js              # API请求封装函数
│   └── wx/                # 微信相关API
├── assets/                # 静态资源
│   ├── images/           # 图片资源
│   └── styles/           # 全局样式
├── components/            # 全局组件
│   ├── MessageDetail.vue  # 消息详情组件
│   ├── TabBar.vue        # 底部导航栏
│   ├── navBar.vue        # 顶部导航栏
│   └── ...
├── pages/                 # 页面文件
│   ├── index/            # 首页
│   ├── messages/         # 消息页面
│   ├── profile/          # 个人中心
│   ├── collectionSettings/ # 收款设置
│   └── ...
├── stores/               # 状态管理
│   ├── user.js          # 用户状态
│   ├── router.js        # 路由状态
│   └── ...
├── utils/                # 工具函数
│   ├── request.js       # 网络请求配置
│   ├── config.js        # 环境配置
│   ├── permission.js    # 权限管理
│   ├── tools.js         # 通用工具函数
│   └── ...
├── app.config.js         # 应用配置
├── app.js               # 应用入口
└── app.less             # 全局样式

快速开始

1. 安装依赖

# 推荐使用pnpm
pnpm install

# 或使用npm
npm install

2. 开发环境运行

# 微信小程序开发
npm run dev:weapp

# H5开发
npm run dev:h5

3. 生产环境构建

# 构建微信小程序
npm run build:weapp

# 构建H5
npm run build:h5

API开发规范

API文件组织结构

项目采用模块化的API组织方式,按功能模块划分API文件:

  • api/index.js - 用户相关API(注册、登录、个人信息等)
  • api/car.js - 车辆相关API(发布、编辑、列表、详情等)
  • api/orders.js - 订单相关API(创建、查询、状态更新等)
  • api/chat.js - 聊天消息相关API
  • api/common.js - 通用API(上传、地区数据等)
  • api/other.js - 其他业务API
  • api/fn.js - API请求封装函数

API编写规范

1. API接口定义

// 定义API端点常量
const Api = {
  ADD_VEHICLE: '/srv/?a=vehicle&t=add',
  EDIT_VEHICLE: '/srv/?a=vehicle&t=edit',
  LIST_VEHICLE: '/srv/?a=vehicle&t=list',
}

/**
 * @description: 添加车辆
 * @param {string} title - 标题
 * @param {string} brand - 品牌
 * @param {string} model - 型号
 * @param {number} price - 出让价格
 * @returns {Promise} API响应结果
 */
export const addVehicleAPI = (params) => fn(fetch.post(Api.ADD_VEHICLE, params));

2. 请求封装函数使用

项目使用统一的请求封装函数 fn() 来处理API响应:

import { fn, fetch } from './fn';

// GET请求
export const getProfileAPI = (params) => fn(fetch.get(Api.GET_PROFILE, params));

// POST请求
export const updateProfileAPI = (params) => fn(fetch.post(Api.UPDATE_PROFILE, params));

3. 错误处理机制

fn() 函数处理API响应并返回标准格式:

  • 返回格式:{ code, msg, data }
  • code === 1 表示请求成功,其他值表示失败
  • msg 包含服务器返回的提示信息
  • data 包含具体的业务数据
  • 特殊处理"计全付"相关错误,使用模态框显示

4. API使用示例

在组件中调用API
<script setup>
import { ref, onMounted } from 'vue';
import { getProfileAPI, updateProfileAPI } from '@/api/index';
import { getVehicleListAPI, addVehicleAPI } from '@/api/car';
import { getOrderListAPI } from '@/api/orders';

// 响应式数据
const userInfo = ref({});
const vehicleList = ref([]);
const orderList = ref([]);
const loading = ref(false);

// 获取用户信息
const fetchUserProfile = async () => {
    loading.value = true;
    try {
        const { code, msg, data } = await getProfileAPI();
        if (code === 1) {
            userInfo.value = data;
            console.log('用户信息获取成功:', data);
        } else {
            console.error('获取用户信息失败:', msg);
        }
    } catch (error) {
        console.error('获取用户信息异常:', error);
    } finally {
        loading.value = false;
    }
};

// 更新用户资料
const updateProfile = async (profileData) => {
    const params = {
        nickname: profileData.nickname,
        avatar_url: profileData.avatar_url,
        gender: profileData.gender
    };

    const { code, msg, data } = await updateProfileAPI(params);
    if (code === 1) {
        console.log('资料更新成功:', msg);
        // 重新获取用户信息
        await fetchUserProfile();
    }
};

// 获取车辆列表
const fetchVehicleList = async () => {
    const params = {
        page: 1,
        limit: 10,
        status: 1 // 1-在售 2-已售
    };

    const { code, msg, data } = await getVehicleListAPI(params);
    if (code === 1) {
        vehicleList.value = data.list;
        console.log('车辆列表:', data);
    }
};

// 添加车辆
const addVehicle = async (vehicleData) => {
    const params = {
        brand_id: vehicleData.brand_id,
        model_id: vehicleData.model_id,
        price: vehicleData.price,
        description: vehicleData.description,
        images: vehicleData.images, // 图片数组
        contact_phone: vehicleData.contact_phone
    };

    const { code, msg, data } = await addVehicleAPI(params);
    if (code === 1) {
        console.log('车辆添加成功:', msg);
        // 刷新车辆列表
        await fetchVehicleList();
    }
};

// 获取订单列表
const fetchOrderList = async (status = '') => {
    const params = {
        page: 1,
        limit: 20
    };

    if (status) {
        params.status = status; // 订单状态筛选
    }

    const { code, msg, data } = await getOrderListAPI(params);
    if (code === 1) {
        orderList.value = data.list;
        console.log('订单列表:', data);
    }
};

// 页面加载时获取数据
onMounted(() => {
    fetchUserProfile();
    fetchVehicleList();
    fetchOrderList();
});
</script>
带错误处理的API调用
// 完整的错误处理示例
const handleApiCall = async (apiFunction, params, successMessage = '') => {
    loading.value = true;

    try {
        const { code, msg, data } = await apiFunction(params);

        if (code === 1) {
            // API调用成功
            if (successMessage) {
                // 显示成功提示
                Taro.showToast({
                    title: successMessage,
                    icon: 'success'
                });
            }
            return { code, msg, data };
        } else {
            // API返回失败,显示服务器返回的错误信息
            Taro.showToast({
                title: msg || '操作失败',
                icon: 'none'
            });
            return false;
        }
    } catch (error) {
        // 网络错误或其他异常
        console.error('API调用异常:', error);
        Taro.showToast({
            title: '网络异常,请稍后重试',
            icon: 'none'
        });
        return false;
    } finally {
        loading.value = false;
    }
};

// 使用示例
const saveProfile = async () => {
    const result = await handleApiCall(
        updateProfileAPI,
        { nickname: '新昵称' },
        '资料保存成功'
    );

    if (result) {
        // 处理成功后的逻辑
        await fetchUserProfile();
    }
};
分页数据加载示例
// 分页加载车辆列表
const vehicleListData = ref({
    list: [],
    page: 1,
    limit: 10,
    total: 0,
    hasMore: true
});

// 加载车辆列表(支持分页和下拉刷新)
const loadVehicleList = async (refresh = false) => {
    if (refresh) {
        vehicleListData.value.page = 1;
        vehicleListData.value.list = [];
        vehicleListData.value.hasMore = true;
    }

    if (!vehicleListData.value.hasMore) return;

    const params = {
        page: vehicleListData.value.page,
        limit: vehicleListData.value.limit,
        status: 1
    };

    const { code, msg, data } = await getVehicleListAPI(params);
    if (code === 1) {
        const { list, total } = data;

        if (refresh) {
            vehicleListData.value.list = list;
        } else {
            vehicleListData.value.list.push(...list);
        }

        vehicleListData.value.total = total;
        vehicleListData.value.page++;

        // 判断是否还有更多数据
        vehicleListData.value.hasMore = vehicleListData.value.list.length < total;
    }
};

// 下拉刷新
const onRefresh = () => {
    loadVehicleList(true);
};

// 上拉加载更多
const onLoadMore = () => {
    loadVehicleList(false);
};

网络请求配置

1. 环境配置

项目支持多环境自动切换(utils/config.js):

// 根据小程序运行环境自动切换API地址
function getBaseUrl() {
    const accountInfo = wx.getAccountInfoSync();
    const envVersion = accountInfo.miniProgram.envVersion;

    switch (envVersion) {
        case 'develop': // 开发版
            return 'https://oa-dev.onwall.cn';
        case 'trial': // 体验版
            return 'https://oa-dev.onwall.cn';
        case 'release': // 正式版
            return 'https://jiangedianlv.onwall.cn';
        default:
            return 'https://jiangedianlv.onwall.cn';
    }
}

2. 请求拦截器

自动添加sessionid到请求头:

// 请求拦截器自动添加认证信息
service.interceptors.request.use(config => {
    const sessionid = getSessionId();
    if (sessionid) {
        config.headers.cookie = sessionid;
    }
    return config;
});

3. 响应拦截器

自动处理sessionid更新和错误响应:

// 响应拦截器处理sessionid和错误
service.interceptors.response.use(
    response => {
        // 自动更新sessionid
        const newSessionId = response.headers['set-cookie'];
        if (newSessionId) {
            setSessionId(newSessionId);
        }
        return response;
    },
    error => {
        // 统一错误处理
        console.error('请求失败:', error);
        return Promise.reject(error);
    }
);

状态管理规范

Pinia Store使用

1. 用户状态管理 (stores/user.js)

export const useUserStore = defineStore('user', {
    state: () => ({
        userInfo: {
            avatar_url: '',
            nickname: '',
            phone: '',
            // ... 其他用户信息字段
        },
        isAuthenticated: false,
        isLoading: false
    }),

    getters: {
        // 检查用户信息完整性
        hasCompleteProfile: (state) => {
            return !!(state.userInfo.phone && state.userInfo.nickname);
        },

        // 检查收款信息完整性
        hasCompleteCollectionInfo: (state) => {
            return !!(
                state.userInfo.name &&
                state.userInfo.bank_id &&
                state.userInfo.bank_no &&
                state.userInfo.idcard
            );
        }
    },

    actions: {
        // 获取用户信息
        async fetchUserInfo() {
            this.isLoading = true;
            try {
                const result = await getProfileAPI();
                if (result && result.data) {
                    this.updateUserInfo(result.data);
                    this.isAuthenticated = true;
                }
            } finally {
                this.isLoading = false;
            }
        },

        // 更新用户信息
        updateUserInfo(newUserInfo) {
            this.userInfo = { ...this.userInfo, ...newUserInfo };
        }
    }
});

2. 在组件中使用Store

<script setup>
import { useUserStore } from '@/stores/user';

const userStore = useUserStore();

// 获取用户信息
onMounted(() => {
    userStore.fetchUserInfo();
});

// 使用计算属性
const hasCompleteInfo = computed(() => userStore.hasCompleteProfile);
</script>

权限管理

权限检查机制 (utils/permission.js)

/**
 * 检查用户权限
 * @param {Object} userInfo - 用户信息
 * @param {Array} requiredFields - 必需字段列表
 * @returns {Object} 权限检查结果
 */
export function checkUserPermissions(userInfo, requiredFields = []) {
    const missingFields = requiredFields.filter(field => {
        return !userInfo[field] || userInfo[field] === '';
    });

    return {
        hasPermission: missingFields.length === 0,
        missingFields,
        message: missingFields.length > 0 
            ? `请先完善:${missingFields.join('、')}` 
            : ''
    };
}

组件开发规范

1. Vue3 Composition API

<script setup>
import { ref, computed, onMounted } from 'vue';
import { useUserStore } from '@/stores/user';

// 响应式数据
const loading = ref(false);
const userStore = useUserStore();

// 计算属性
const displayName = computed(() => {
    return userStore.userInfo.nickname || '未设置昵称';
});

// 生命周期
onMounted(() => {
    initData();
});

// 方法定义
const initData = async () => {
    loading.value = true;
    try {
        await userStore.fetchUserInfo();
    } finally {
        loading.value = false;
    }
};
</script>

2. NutUI组件使用

<template>
    <nut-button type="primary" @click="handleSubmit">
        提交
    </nut-button>

    <nut-popup v-model:visible="showPopup">
        <nut-form>
            <nut-form-item label="姓名">
                <nut-input v-model="form.name" placeholder="请输入姓名" />
            </nut-form-item>
        </nut-form>
    </nut-popup>
</template>

样式开发规范

1. TailwindCSS + Less结合使用

<template>
    <view class="container">
        <view class="flex justify-between items-center p-4">
            <text class="title">标题</text>
        </view>
    </view>
</template>

<style lang="less" scoped>
.container {
    background: #f5f5f5;

    .title {
        font-size: 32rpx;
        font-weight: bold;
        color: #333;
    }
}
</style>

2. 响应式单位使用

  • 使用 rpx 作为主要单位(小程序自适应)
  • 字体大小:28rpx32rpx36rpx
  • 间距:16rpx24rpx32rpx

注意事项

1. 开发环境

  • 推荐使用 pnpm 作为包管理器
  • Node.js 版本要求:>= 16.0.0
  • 微信开发者工具版本:>= 1.06.0

2. 代码规范

  • 使用 ESLint 进行代码检查
  • 组件名使用 PascalCase
  • 文件名使用 kebab-case
  • 变量名使用 camelCase

3. 性能优化

  • 合理使用 computedwatch
  • 避免在模板中使用复杂表达式
  • 大列表使用虚拟滚动
  • 图片使用懒加载

4. 调试技巧

  • 使用 console.log 进行调试
  • 利用微信开发者工具的调试功能
  • 网络请求在 Network 面板查看

功能模块说明

已屏蔽功能

  • 用户认证功能:移除了我的认证选项,清理了不再使用的视图模式切换选项
  • 认证相关显示:在"我卖的车"页面移除了认证按钮和认证状态文字
  • 认证菜单项:屏蔽了首页认证入口、个人页面认证菜单、订单页面认证列表等3个地方的认证功能

核心功能模块

  1. 用户管理:注册、登录、个人信息管理
  2. 车辆交易:发布车辆、浏览车辆、车辆详情
  3. 订单系统:下单、支付、订单管理
  4. 消息通知:系统消息、聊天消息
  5. 收款设置:银行卡绑定、收款账户管理

部署说明

1. 微信小程序部署

  1. 运行 npm run build:weapp 构建项目
  2. 使用微信开发者工具打开 dist 目录
  3. 点击"上传"按钮上传代码
  4. 在微信公众平台提交审核

2. H5部署

  1. 运行 npm run build:h5 构建项目
  2. dist 目录部署到服务器
  3. 配置 nginx 或其他 web 服务器

更新日志

  • refactor(订单管理): 移除我的认证选项, 清理不再使用的视图模式切换选项,简化界面
  • 我卖的车: 移除认证相关显示和功能,认证按钮和认证状态文字都屏蔽了
  • refactor(profile): 移除我的认证菜单项, 屏蔽了首页认证入口,我的页面列表上菜单,我的订单上我的认证列表,3个地方