赞
踩
vue3+sass+typescript+pinia+uniapp+微信小程序基础
编写工具:Vscode、微信小程序开发
这是一个基于瑞幸咖啡点单小程序创建的一个仅用于自己学习uniapp技术的项目。此项目不涉及服务器、网络等知识,数据内容以及数据类型均由自己编写。
此项目共分为三大块(即三个tabbar页面):index页面、order页面、my页面。
npm run dev:mp-weixin
打包,生成dist文件夹dist/dev/mp-weixin
pnpm i -D @types/wechat-miniprogram @uni-helper/uni-app-types
- {
- "extends": "@vue/tsconfig/tsconfig.json",
- "compilerOptions": {
- "sourceMap": true,
- "ignoreDeprecations": "5.0",
- "baseUrl": ".",
- "paths": {
- "@/*": [
- "./src/*"
- ]
- },
- "lib": [
- "esnext",
- "dom"
- ],
- "types": [
- "@dcloudio/types", // uni-app API 类型
- "miniprogram-api-typings", // 原生微信小程序类型
- "@uni-helper/uni-app-types" // uni-app 组件类型
- ]
- },
- // vue 编译器类型,校验标签类型
- "vueCompilerOptions": {
- "nativeTags": [
- "block",
- "component",
- "template",
- "slot"
- ],
- },
- "include": [
- "src/**/*.ts",
- "src/**/*.d.ts",
- "src/**/*.tsx",
- "src/**/*.vue"
- ]
- }
- //安装uni-ui
- cnpm i @dcloudio/uni-ui
-
- //pages.json
- {
- "easycom": {
- "autoscan": true,
- "custom": {
- // uni-ui 规则如下配置
- "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
- }
- },
- }
在pages.json中配置所有页面
- {
- "pages": [ //pages数组中第一项表示应用启动页
- {
- "path": "pages/index/index",//相对src下页面vue文件位置
- "style": {
- "navigationBarTitleText": "uni-app", //此页面顶部栏标题
- "navigationStyle": "custom" //自定义此页面导航栏(顶部栏样式)
- }
- },
- {
- "path": "pages/my/my",
- "style": {
- "navigationBarTitleText": "my",
- "navigationStyle": "custom"
- }
- },
- {
- "path": "pages/order/order",
- "style": {
- "navigationBarTitleText": "order",
- "navigationStyle": "custom"
- }
- }
- ],
- // 设置 TabBar
- "tabBar": {
- "color": "#333",
- "selectedColor": "#27ba9b",
- "backgroundColor": "#fff",
- "borderStyle": "white",
- "list": [
- {
- "text": "首页",
- "pagePath": "pages/index/index",
- "iconPath": "static/tabbarICON/home_default.png",
- "selectedIconPath": "static/tabbarICON/home_selected.png"
- },
- {
- "text": "点单",
- "pagePath": "pages/order/order",
- //默认状态下此导航栏的图标
- "iconPath": "static/tabbarICON/category_default.png",
- //点击后的图标
- "selectedIconPath": "static/tabbarICON/category_selected.png"
- },
- {
- "text": "我的",
- "pagePath": "pages/my/my",
- "iconPath": "static/tabbarICON/user_default.png",
- "selectedIconPath": "static/tabbarICON/user_selected.png"
- }
- ]
- },
- "globalStyle": { //全局样式
- "navigationBarTextStyle": "black",
- "navigationBarTitleText": "uni-app",
- "navigationBarBackgroundColor": "#F8F8F8",
- "backgroundColor": "#F8F8F8",
- "enablePullDownRefresh": true
- },
- "subPackages": [ //分包配置
- 。。。
- ],
- "preloadRule": {
- // 配置哪个分页预加载哪些子包
- 。。。
- }
- }
- 父组件
- <AdSwiper :list="bannerList" />
- 其它父组件
- <AdSwiper :list="otherBannerList" />
-
- 子组件
- defineProps<{
- list:BannerItem[] //BannerItem为bannerList中成员的数据类型
- }>()
- <navigator url="/pages/my/my">....</navigator>
- 跳转到my页面
- <view class="viewport"
- :style="{ paddingTop: safeAreaInsets?.top + 'px' }" > .... </view>
-
- //获取安全距离,作为order页面padding-top
- const { safeAreaInsets } = uni.getSystemInfoSync();
- <template>
- ...
- <view class="pick-method" @tap="switchMethod">
- <view :class="isTakeOut ? '' : 'active'">自提</view>
- <view :class="isTakeOut ? 'active' : ''">外送</view>
- </view>
- ...
- </template>
-
- <script setup lang="ts">
- import { ref } from "vue";
-
- //通过isTakeOut决定是否为该view添加acitve类名,拥有active类名背景样式为蓝色
- let isTakeOut = ref(false); //true为自提,false为外送
- const switchMethod = () => {
- //点击pick-method切换
- isTakeOut.value = !isTakeOut.value;
- };
- const changeTakeout = (param?: string) => {
- //根据index页面跳转过来传递的参数标识,切换isTakeOut
- console.log(param);
- if (param === "0") {
- isTakeOut.value = false;
- } else if (param === "1") {
- isTakeOut.value = true;
- }
- };
- //将方法暴露出去,供此子组件的父组件order使用
- defineExpose({
- changeTakeout,
- });
- </script>
-
- <style lang="scss">
- .pick-method {
- width: 170rpx;
- height: 50rpx;
- display: flex;
- background-color: rgba(161, 156, 156, 0.507);
- border-radius: 13px;
- align-items: center;
- margin-right: 40rpx;
- view {
- width: 50%;
- height: 50rpx;
- line-height: 50rpx;
- border-radius: 16px;
- text-align: center;
- }
- .active {
- background-color: blue;
- color: white;
- border-radius: 13px;
- }
- }
- </style>
- .tab-swiper {
- width: 100%;
- height: calc(100vh - 245rpx);
-
- .menu-card {
- background-color: rgba(198, 189, 189, 0.42);
- }
- .vip-card {
- width: 100%;
- background-color: rgb(43, 133, 79);
- }
- }
- <view class="tab-bar">
- <view
- class="tab-item"
- v-for="item in tabs"
- :key="item.TABID"
- :class="item.TABID === targetIndex ? ' active' : ''"
- @tap="switchTab(item.TABID)"
- >
- {{ item.title }}
- </view>
- </view>
- </view>
- <swiper class="tab-swiper" @change="onChange" :current="targetIndex">
- <swiper-item class="menu-card" item-goodsId="0">...</swiper-item>
- <swiper-item class="vip-card" item-goodsId="1">...</swiper-item>
- </scroll-view>
- //
- const tabs = [
- { TABID: 0, title: "经典菜单" },
- { TABID: 1, title: "会员卡" },
- ];
- let targetIndex = ref<number>(0); //tab切换目标组件
- //tab点击事件
- const switchTab = (goodsId: number) => {
- targetIndex.value = goodsId;
- console.log(targetIndex.value);
- };
- //swiper的@change事件,current的值发生改变后,
- const onChange = (e: any) => {
- targetIndex.value = e.detail.current;
- console.log(targetIndex.value);
- console.log(e.detail.current);
- };
- <scroll-view scroll-y class="left-tab">
- <view
- class="tab-item-title"
- v-for="item in allMenu"
- :key="item.tabid"
- @tap="switchMenuTab(item.tabid)"
- :class="item.tabid == targetMenuIndex ? 'active' : ''"
- >
- {{ item.categoryList[0] }} //导航名称
- </view>
- </scroll-view>
- <scroll-view
- scroll-y
- class="right-tab"
- :scroll-into-view="targetMenuIndex"
- @scroll="rightScroll"
- >
- <view
- class="tab-container"
- v-for="item in allMenu"
- :key="item.tabid"
- :id="item.tabid"
- >
- ...
- </view>
- </scroll-view>
- //
- let targetMenuIndex = ref("tab1"); //默认显示右侧第一个子项
- const switchMenuTab = (goodsId: any) => {
- targetMenuIndex.value = goodsId;
- console.log(targetMenuIndex.value, goodsId);
- };
- let toTopDistance: any = [];
- const getRightTabInfor = () => {
- const instance = getCurrentInstance();
- const query = uni.createSelectorQuery().in(instance);
- // 获取右侧所有格子的信息
- query
- .selectAll(".tab-container")
- .boundingClientRect((data) => {
- const formatData = JSON.parse(JSON.stringify(data));
- for (let i = 0; i < formatData.length; i++) {
- console.log(formatData[i].top ) //获取的是每个子项顶部距离屏幕顶部的距离(px)
- // 获取所有子盒子距离父盒子顶部距离时
- let temp = (formatData[i].top - 260).toFixed(0); //需要减去大概整个顶部栏高度
- toTopDistance.push(temp); //将所有子项目的距离存储到toTopDistance数组中
- }
- })
- .exec();
- };
- const rightScroll = (e: any) => {
- //scroll事件可以获取当前一些信息
- let SCROLL_TOP = e.detail.scrollTop; //当前滚动的距离(从scroll-view框架顶部到内容超出框架顶部的距离)
- //当前超出框架顶部的距离大于等于0并且小于第二个盒子顶部距离框架顶部的距离时,左侧导航显示的是tab1导航
- if (0 <= SCROLL_TOP && SCROLL_TOP < toTopDistance[1]) {
- targetMenuIndex.value = `tab1`;
- } else if (toTopDistance[1] <= SCROLL_TOP && SCROLL_TOP < toTopDistance[2]) {
- targetMenuIndex.value = `tab2`;
- } else if (toTopDistance[2] <= SCROLL_TOP && SCROLL_TOP < toTopDistance[3]) {
- targetMenuIndex.value = `tab3`;
- } else if (toTopDistance[3] <= SCROLL_TOP && SCROLL_TOP < toTopDistance[4]) {
- targetMenuIndex.value = `tab4`;
- }
- };
- onMounted(() => {
- getRightTabInfor();
- });
- 为幸运送和到店取添加tap事件
- uni.setStorageSync("order-key", '1'); //保存不同的参数到storage中
- uni.setStorageSync("order-key", '0');
- uni.switchTab({ //跳转到order页面
- url: /pages/order/order,
- });
-
-
- //OrderTopBanner
- ...
- const changeTakeout = (param?: string) => {
- console.log(param);
- //参数为0,说明是从幸运送跳转来的
- if (param === "0") {
- isTakeOut.value = false;
- } else if (param === "1") {
- isTakeOut.value = true;
- }
- };
- defineExpose({ //将changeTakeout方法暴露给父组件,因为参数只能从父组件获取
- changeTakeout,
- });
- ...
-
- // order/order
- 获取子组件
- <OrderTopBanner ref="RefChild" />
-
- const RefChild = ref(); //获取子组件实例对象
- const callChildFn = () => { //创建callChildFn方法调用子组件方法
- let orderKey = uni.getStorageSync("order-key"); //从storage中获取参数
- uni.removeStorageSync("order-key"); //获取后就移除该参数
- RefChild.value.changeTakeout(orderKey); //调用子组件的chagneTakeout方法,并传入参数
- };
-
- onShow(()=>{
- callChildFn() //在跳转到order页面后立即执行此方法
- })
- const popup = ref();
- const popupOverlay = ref();
- let isPopupOverlay: boolean = false;
- let isShowPopup: boolean = false;
- let customPopupStyle = ref({
- display: "none",
- transform: "translateY(400rpx)",
- });
- let customPopupOverlayStyle = ref({
- display: "none",
- });
- const onTapUncollpasedBanner = () => {
- // 有订单,默认展开显示 订单,点击打开popup展示所有订单
- isShowPopup = !isShowPopup;
- isPopupOverlay = !isPopupOverlay;
- if (isShowPopup === false) {
- customPopupStyle.value.display = "none";
- customPopupOverlayStyle.value.display = "none";
- } else if (isShowPopup === true) {
- customPopupStyle.value.display = "flex";
- customPopupOverlayStyle.value.display = "block";
- }
- };
- "subPackages": [
- {
- // 进入order页后预加载一下分包
- "root": "pagesOrder",
- "pages": [
- {
- "path": "Purchase/Purchase",
- "style": {
- "navigationBarTitleText": "Purchase"
- }
- },
- {
- "path": "GoodsDetail/GoodsDetail",
- "style": {
- "navigationBarTitleText": "GoodsDetail",
- "navigationStyle": "custom"
- }
- }
- ]
- },
- {
- ....其它分包
- }
- ],
- "preloadRule": {
- // 配置哪个页面预加载哪些分包
- "pages/order/order": {
- "network": "all",
- "packages": [
- "pagesOrder"
- ]
- },
- {
- ...
- }
- }
- //GoodsDetail页面
- const onAddShoppingCar = () => {
- ...
- // 添加入购物车
- // 跳转到order界面,并将当前订单商品传递给order
- uni.setStorageSync(
- "banner-orders",
- JSON.parse(JSON.stringify(currGoodsOrder))
- );
- uni.switchTab({
- url: "/pages/order/order",
- success: (success) => {
- uni.showToast({
- title: "添加成功",
- position: "center",
- });
- },
- });
- ...
- };
- //order页面接收加入购物车的商品数据
- // pushBannerOrders:获取新增购物车订单
- const pushBannerOrders = () => {
- // 从参数中获取新的order,并新增到bannerOrders中
- let order: OrderItem = Array.from(uni.getStorageSync("banner-orders"));
- removeDuplicateOrder(order);
- // 获取后清除这个order
- uni.removeStorageSync("banner-orders");
- };
-
- onShow(()=>{
- ...
- pushBannerOrdes();
- ...
- })
- const removeDuplicateOrder = (ORDER: OrderItem[]) => {
- // 1.处理ORDER
- // 创建新数组接收去重后的ORDER
- let newOrders: OrderItem[] = [];
- // 在ORDER中,所有订单出了goodsCustom和goodsCustomFormat存在不一致外,无法通过深度判断goodsCustom(对象)去重,所以使用goodsCustomFormat是否存在相同的
- // 使用map存储判断后的数据
- let map = new Map();
- for (let item of ORDER) {
- if (!map.has(item.goodsCustomFormat)) {
- map.set(item.goodsCustomFormat, item);
- }
- }
- newOrders = [...map.values()];
- // 判断newORders中每个成员在ORDER中的数量并创建newOrdersNum变量接收
- let newOrdersNum: number[] = [];
- newOrders.forEach((item) => {
- let i = 0;
- ORDER.forEach((ITEM) => {
- if (ITEM.goodsCustomFormat === item.goodsCustomFormat) {
- i++;
- }
- });
- newOrdersNum.push(i);
- });
- // 将newOrdersNum数组中的数据按顺序赋值给每个newOrders中的成员的goodsNum属性
- for (let i in newOrders) {
- newOrders[i].goodsNum = newOrdersNum[i];
- }
-
- // newOrders中一定是同goodsId的商品,区分在同goodId是否有不同goodsCustomFormat,而newOrders是已经去重的数组,每个成员都是同goodsId不同goodsCustomFormat。
- // 在为bannerOrders注入数据时,判断如下:
- // 1.判断当前bannerOrders中是否存在当前需要注入的数据newOrders的商品种类(judge,判断bannerOrders中是否存在与newOrders中的goodsId一致的商品)
- // 1.1 newOrders中的商品在bannerOrders中存在,判断bannerOrders中该数据的goodsCustomFormat是否与当前newOrders中的某个成员的goodsCustomFormat一致
- // 1.1.1 item.goodsCustomFormat == ITEM.goodsCustomFormat即存在与newOrders中某个成员同商品同商品类型的数据,则增加bannerOrders中的goodsNum
- // 1.1.2 在bannerOrders中找不到同类型goodsCustomFormat的商品,则将newOrders中的该数据push到bannerOrders中
- // 1.2 newOrders中的商品在bannerOrders中不存在,即newOrders中的goodsId在bannerOrders中找不到,则直接将整个newOrders拼接到bannerOrders中(完成第一次添
- // 加购物车后,第二次不同的商品添加入购物车,直接将第二次的所有商品拼接到当前的bannerOrders中)
- let judge = bannerOrders.value.findIndex(
- (item: OrderItem) => item.goodsId === newOrders[0].goodsId
- );
- if (judge !== -1) {
- //1.1
- bannerOrders.value.forEach((item) => {
- newOrders.forEach((ITEM) => {
- // 1.1
- if (item.goodsCustomFormat == ITEM.goodsCustomFormat) {
- // 1.1.1
- item.goodsNum += ITEM.goodsNum;
- } else if (
- bannerOrders.value.findIndex(
- (item3) => item3.goodsCustomFormat === ITEM.goodsCustomFormat
- ) === -1
- ) {
- // 1.1.2
- bannerOrders.value.push(ITEM);
- }
- });
- });
- } else {
- // 1.2
- bannerOrders.value = bannerOrders.value.concat(newOrders);
- }
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。