·愿景:"让编程不再难学,让技术与生活更加有趣"
更多课程请访问 xdclass.net
简介:课程介绍、适合人员和学后水平
招聘需求
适合人群
前端开发工程师
后端开发工程师
全栈开发工程师
学后水平
掌握uniapp框架开发项目|多种跨端开发框架的优劣|开发安卓app项目|开发ios app项目|开发微信小程序项目
uniapp的应用+页面+组件生命周期函数|基本路由的配置和跳转传参|进阶不用路由的详细使用
uniapp框架常用组件的使用|uniapp框架常用api的使用|通过Hbuilderx打包上线小程序
项目实战开发小滴课堂微信小程序
项目实战演示
学习形式
简介:uniapp多端课程大纲速览
愿景:"让编程不再难学,让技术与生活更加有趣"
更多课程请访问 xdclass.net
简介:什么是uni-app框架?为什么要学?
什么是uniapp框架
使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到 iOS、Android、Web、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)、快应用等多个平台
官网地址:https://uniapp.dcloud.net.cn/
为什么要学?
当前跨平台普遍存在的问题
使用的设备平台多
维护多个平台的成本
uni-app开发的优势
一套代码、多端发行(可以在项目里调用不同平台的api)
运行体验更好
学习成本低
开发生态、组件丰富
简介:uni-app框架开发app和原生开发app的优劣
优劣
uni-app | 原生(android/ios) | |
---|---|---|
跨端 | 支持ios,Android,H5,微信小程序、支付宝小程序等等 | 不支持 |
学习成本 | 掌握vue、微信小程序即可上手,对前端开发者友好 | 需要另外学习语言android:Java语言、ios:Objective-C |
开发时间 | 比较短 | 比较长 |
功能 | 不够完善 | 完善 |
总结
简介:通过uni-app框架创建项目
创建方式
HBuilderX可视化方式
vue-cli命令行方式
通过HBuilderX可视化方式
HBuilderX工具下载
创建uni-app项目
运行uni-app项目
通过vue-cli命令行方式
环境安装
x# 查看是否安装有@vue/cli
vue -V
# 无则执行命令安装
npm install -g @vue/cli
命令行安装 Vue3/vite/typescript 项目模板
xxxxxxxxxx
npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project
执行会出现的问题
直接访问官网提供的gitee地址下载
简介:项目运行和项目结构目录讲解
运行项目
使用 pnpm 包管理工具
xxxxxxxxxx
# 查看是否安装pnpm
pnpm -v
# 无则安装
npm install -g pnpm
下载依赖
xxxxxxxxxx
pnpm i
运行
xxxxxxxxxx
pnpm dev:h5
项目结构
xxxxxxxxxx
node_modules ——放置项目的依赖文件
src ——放置开发的代码文件
pages ——放置页面文件
static ——放置静态文件
App.vue ——页面入口文件
env.d.ts ——.vue文件的类型说明文件
main.ts ——程序的入口文件,加载各种公共组件
manifest.json ——5+App拓展文件,打包app时要使用
pages.json ——全局配置文件,配置页面文件的路径、窗口样式、原生的导航栏、底部的原生tabbar等等
uni.scss ——uniapp的默认css样式
.gitignore ——git忽略文件
index.html ——项目总的入口文件
package.json ——项目依赖的描述文件
pnpm-lock.yaml ——锁定项目的依赖包版本
tsconfig.json ——TS的语法识别和执行配置文件
vite.config.ts ——编译工具vite的配置文件
简介:uni-app项目运行在安卓真机调试
因为安卓模拟器调试有问题,运行项目后白屏,因此使用安卓真机调试
安卓真机调试
HBuilderX下载真机插件
手机通过USB链接电脑调试
手机开启开发者模式,并且打开USB调试
允许USB调试计算机进行调试
HbuilderX选中要运行的项目
简介:详解uni-app项目运行在ios模拟器调试
下载Xcode开发工具
Hbuilder运行到ios模拟器
简介:详解uni-app项目运行在微信小程序调试
项目运行在微信小程序
打开HbuilderX,运行 ==> 运行到小程序或模拟器 ==> 运行设置 ==> 设置微信开发者工具路径
打开微信开发者工具,选择设置 ==> 安全设置。将服务端口号选择为开启
运行
愿景:"让编程不再难学,让技术与生活更加有趣"
更多课程请访问 xdclass.net
简介:uni-app生命周期有哪些?怎么理解?
生命周期函数
uni-app生命周期分类
应用生命周期
官网文档地址
应用生命周期仅可在App.vue中监听,在页面监听无效
App.vue是uni-app的主组件,所有页面都是在App.vue下进行切换的,是页面入口文件。但App.vue本身不是页面,这里不能编写视图元素,也就是没有< template >
页面生命周期
官网文档地址
支持vue标准的生命周期函数,同时新增了很多其他的生命周期函数
在page目录下配置的页面文件才能生效
uni-app会将 pages.json ==> pages 配置项中的第一个页面,作为当前工程的首页(启动页)
组件生命周期
简介:详解uni-app应用生命周期
应用生命周期函数
函数名 | 说明 |
---|---|
onLaunch | 当uni-app 初始化完成时触发(全局只触发一次) |
onShow | 当 uni-app 启动,或从后台进入前台显示 |
onHide | 当 uni-app 从前台进入后台 |
onError | 当 uni-app 报错时触发 |
onUniNViewMessage | 对 nvue 页面发送的数据进行监听,可参考 nvue 向 vue 通讯 |
onUnhandledRejection | 对未处理的 Promise 拒绝事件监听函数(2.8.1+) |
onPageNotFound | 页面不存在监听函数 |
onThemeChange | 监听系统主题变化 |
onPageNotFound
找不到url对应的页面文件时执行
xxxxxxxxxx
onPageNotFound(() => {
uni.navigateTo({
url:'/pages/404/index'
})
})
简介:详解uni-app应用生命周期
页面生命周期函数
函数名 | 说明 |
---|---|
onLoad | 监听页面加载,其参数为上个页面传递的数据,参数类型为 Object(用于页面传参),参考示例 |
onShow | 监听页面显示。页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面 |
onReady | 监听页面初次渲染完成。注意如果渲染速度快,会在页面进入动画完成前触发 |
onHide | 监听页面隐藏 |
onUnload | 监听页面卸载 |
onResize | 监听窗口尺寸变化 |
onPullDownRefresh | 监听用户下拉动作,一般用于下拉刷新,参考示例 |
onReachBottom | 页面滚动到底部的事件(不是scroll-view滚到底),常用于下拉下一页数据。具体见下方注意事项 |
onPageScroll | 监听页面滚动,参数为Object |
onNavigationBarButtonTap | 监听原生标题栏按钮点击事件,参数为Object |
onBackPress | 监听页面返回,返回 event = {from:backbutton、 navigateBack} ,backbutton 表示来源是左上角返回按钮或 android 返回键;navigateBack表示来源是 uni.navigateBack ;详细说明及使用:onBackPress 详解。支付宝小程序只有真机能触发,只能监听非navigateBack引起的返回,不可阻止默认行为。 |
onNavigationBarSearchInputChanged | 监听原生标题栏搜索输入框输入内容变化事件 |
onNavigationBarSearchInputConfirmed | 监听原生标题栏搜索输入框搜索事件,用户点击软键盘上的“搜索”按钮时触发。 |
onNavigationBarSearchInputClicked | 监听原生标题栏搜索输入框点击事件(pages.json 中的 searchInput 配置 disabled 为 true 时才会触发) |
onPullDownRefresh
用户在app和小程序中下拉操作时执行
xxxxxxxxxx
onPullDownRefresh(() => {
console.log('页面刷新了');
})
// pages.json
对应页面配置 "enablePullDownRefresh": true
愿景:"让编程不再难学,让技术与生活更加有趣"
更多课程请访问 xdclass.net
简介:组件式路由跳转传参
组件式路由跳转
xxxxxxxxxx
<navigator url="/pages/about/index" open-type="navigate" hover-class="navigator-hover">
去个人中心
</navigator>
路由传参
xxxxxxxxxx
# 传递参数
<navigator url="/pages/mine/index?title=小滴课堂" open-type="navigate" hover-class="navigator-hover">
去个人中心
</navigator>
xxxxxxxxxx
# 接受参数
onLoad((option)=>{
console.log(option?.title)
})
简介:函数式路由跳转传参
函数式路由跳转
xxxxxxxxxx
uni.navigateTo({
url: '/pages/index/index'
})
自动补全的插件:uni-app-snippets
路由传参
xxxxxxxxxx
# 传递参数
uni.navigateTo({
url: '/pages/index/index?title=xdclass.net'
})
xxxxxxxxxx
# 接受参数
onLoad((option)=>{
console.log(option?.title)
})
传参的问题
url值有长度限制,太长的字符串会传递失败
解决
通过uniapp的全局自定义事件
通过全局状态管理库处理
简介:不同路由跳转配置的使用
配置2个一级导航页面(tabBar)
pages目录新建vue文件
pages.json配置tabBar
xxxxxxxxxx
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{
"pagePath": "pages/mine/index",
"text": "我的"
}
]
}
路由配置分类
跳转方法 | 说明 |
---|---|
uni.navigateTo() | 保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack 可以返回到原页面 |
uni.redirectTo() | 关闭当前页面,跳转到应用内的某个页面 |
uni.reLaunch() | 关闭所有页面,打开到应用内的某个页面 |
uni.switchTab() | 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面 |
uni.navigateBack() | 关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层 |
注意
实现传参
navigateTo
, redirectTo
只能打开非 tabBar 页面。
switchTab
只能打开 tabBar
页面。
reLaunch
可以打开任意页面
总结
简介:路由跳转配置的详细使用
详解uni.navigateTo()
参数 | 说明 |
---|---|
url | 需要跳转的应用内非 tabBar 的页面的路径 , 路径后可以带参数。参数与路径之间使用?分隔,参数键与参数值用=相连,不同参数用&分隔;如 'path?key=value&key2=value2',path为下一个页面的路径,下一个页面的onLoad函数可得到传递的参数 |
animationType | 窗口显示的动画效果,详见:窗口动画 |
animationDuration | 窗口动画持续时间,单位为 ms |
events | 页面间通信接口,用于监听被打开页面发送到当前页面的数据。2.8.9+ 开始支持。 |
success | 接口调用成功的回调函数 |
fail | 接口调用失败的回调函数 |
complete | 接口调用结束的回调函数(调用成功、失败都会执行) |
xxxxxxxxxx
// 通讯获取事件
import {getCurrentInstance} from 'vue'
const instance = getCurrentInstance().proxy
const eventChannel = instance.getOpenerEventChannel()
getCurrentPages() 使用
获取当前的页面栈,以数组形式按栈的顺序给出,第一个元素为起首页,最后一个元素为当前页面
xxxxxxxxxx
onLoad(() => {
const pages = getCurrentPages()
// @ts-expect-error-error
console.log(pages[pages.length - 1].$page);
})
愿景:"让编程不再难学,让技术与生活更加有趣"
更多课程请访问 xdclass.net
简介:详解uniapp常用的组件使用
视图容器
view
scroll-view
swiper
match-media
movable-area
movable-view
简介:详解uniapp常用的组件使用
基础内容
icon
text
progress
表单组件
button
checkbox-group
input
媒体组件
audio
image
简介:详解uniapp常用的API使用
网络
数据缓存
异步方法 | 同步方法 | |
---|---|---|
设置 | uni.setStorage | uni.setStorageSync |
获取 | uni.getStorage | uni.getStorageSync |
删除指定key | uni.removeStorage | uni.removeStorageSync |
清理缓存 | uni.clearStorage | uni.clearStorageSync |
简介:详解uniapp常用的API使用
界面
uni.showToast
uni.setNavigationBarTitle
uni.setTabBarItem
uni.pageScrollTo
uni.onWindowResize
第三方
uni.login
uni.requestPayment
愿景:"让编程不再难学,让技术与生活更加有趣"
更多课程请访问 xdclass.net
简介:小滴课堂微信小程序项目需求分析
首页
分类
学习
我的
简介:项目初始化创建和gitee代码仓库管理
简介:unocss配置
unocss配置
xxxxxxxxxx
// unocss.config.ts
import presetWeapp from 'unocss-preset-weapp'
import { defineConfig } from 'unocss'
import { transformerAttributify, transformerClass } from 'unocss-preset-weapp/transformer'
export default defineConfig({
presets: [
presetWeapp(), // 工具预设
],
transformers: [
transformerAttributify(), // 支持属性化模式
transformerClass(), // 转换转义类名,支持class写法
],
shortcuts: [
{ center: 'flex items-center justify-center' },
{ around: 'flex items-center justify-around' },
{ between: 'flex items-center justify-between' },
]
})
xxxxxxxxxx
// main.ts
import 'uno.css'
xxxxxxxxxx
// vite.config.ts
import { defineConfig } from 'vite'
import Unocss from 'unocss/vite'
export default defineConfig({
plugins: [Unocss()]
})
简介:响应式语法糖和自动引入插件配置
开启响应式语法糖
xxxxxxxxxx
// vite.config.ts
import { defineConfig } from 'vite'
import Unocss from 'unocss/vite'
export default defineConfig({
plugins: [
uni({
vueOptions: {
reactivityTransform: true
}
})]
})
ts识别语法糖写法
xxxxxxxxxx
"types": ["vue/ref-macros"]
自动引入插件配置
xxxxxxxxxx
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
dts: 'src/typings/auto-imports.d.ts',
imports: ['vue', 'uni-app', 'pinia'],
dirs: ['src/composables']
})]
})
简介:uni-app框架中使用Pinia状态管理的配置
安装插件
xxxxxxxxxx
pnpm i pinia@2.0.30 pinia-plugin-persistedstate@3.0.2 -S
引入Pinia
xxxxxxxxxx
// main.ts
import { createPinia } from 'pinia';
import { createSSRApp } from 'vue';
import { createPersistedState } from 'pinia-plugin-persistedstate';
import App from './App.vue';
import 'uno.css';
// 引入Pinia和持久缓存的设置
const pinia = createPinia().use(
createPersistedState({
storage: {
getItem(key: string) {
return uni.getStorageSync(key);
},
setItem(key: string, value: string) {
uni.setStorageSync(key, value);
},
},
})
);
export function createApp() {
const app = createSSRApp(App).use(pinia);
return {
app,
};
}
简介:uni-app框架中使用Pinia状态管理的使用
src根目录新建composables目录(用于放置Pinia配置文件)
测试Pinia配置成功与否
xxxxxxxxxx
// user.ts
export const useUser = defineStore(
'user',
() => {
let isLogin = $ref<boolean>(false);
return $$({
isLogin,
});
},
{
persist: true,
}
);
配置TS对响应式语法糖的支持
xxxxxxxxxx
"types": ["@dcloudio/types", "vue/ref-macros"]
vue3官网Reactivity Transform文档地址
https://vuejs.org/guide/extras/reactivity-transform.html#refs-vs-reactive-variables
xxxxxxxxxx
返回函数范围内使用 $$ 包裹保持响应性
愿景:"让编程不再难学,让技术与生活更加有趣"
更多课程请访问 xdclass.net
简介:布局+数据获取
布局+数据请求
xxxxxxxxxx
// index.vue
<script setup lang="ts">
import { getBannerList, getCardList } from '@/api'
let bannerList = $ref<string[]>([])
let productList = $ref<any[]>([])
onLoad(async () => {
productList = getCardList().data
bannerList = getBannerList().data[0].imgUrl.split(',').map(item => item.replace('\n', ''))
})
</script>
<template>
<view relative w-full>
</view>
</template>
xxxxxxxxxx
// /api/index.ts
import indexJson from '../json/index.json';
import cardJson from '../json/card.json';
/**
* 获取首页展示的商品卡片列表
*/
export function getCardList() {
return cardJson;
}
/**
* 获取首页展示的轮播图列表
*/
export function getBannerList() {
return indexJson;
}
简介:头部导航+搜索组件
sass安装
xxxxxxxxxx
pnpm i sass -D
Tabbar开发
xxxxxxxxxx
// NavigationBar.vue
<script lang="ts" setup>
import Search from '@/components/Search.vue'
</script>
<template>
<uni-nav-bar :fixed="true" :status-bar="true" :border="false" background-color="#ffffff" class="navbar"
left-width="1px">
<Search color="999999" background-color="#f6f6f6" />
</uni-nav-bar>
</template>
<style lang="scss" scoped>
.navbar {
:deep(.uni-navbar__header-container) {
padding: 0;
}
:deep(.uni-navbar__header) {
padding: 0;
}
}
</style>
搜索组件
xxxxxxxxxx
// Search.vue
<script lang="ts" setup>
defineProps<{ color: string, backgroundColor: string }>()
</script>
<template>
<view class="search_container">
<image src="@/static/images/icons/index/search.svg" style="width: 32rpx; height: 28rpx" mode="scaleToFill"
class="search_icon" />
<view class="search_input" confirm-type="search" :style="{ backgroundColor: `${backgroundColor}` }">
<text>请输入搜索内容</text>
</view>
</view>
</template>
<style lang="scss" scoped>
.search_container {
margin-top: 5rpx;
width: 100%;
display: flex;
align-items: center;
position: relative;
image {
position: absolute;
left: 30rpx;
}
.search_icon_top {
top: 35rpx;
left: 36rpx;
}
.search_input {
height: 80rpx;
width: 50vw;
font-size: 24rpx;
border-radius: 80rpx;
padding-left: 78rpx;
text {
color: #a5a1a1;
line-height: 80rpx;
}
}
}
</style>
简介:轮播图+分类导航
轮播图
xxxxxxxxxx
// IndexSwiper.vue
<script lang="ts" setup>
defineProps<{
bannerList: string[]
}>()
</script>
<template>
<swiper indicator-dots autoplay circular>
<swiper-item v-for="item in bannerList" :key="item">
<image class="swiper_image" mode="scaleToFill" :src="item" />
</swiper-item>
</swiper>
</template>
<style lang="scss" scoped>
swiper {
width: 100%;
height: 310rpx;
.swiper_image {
width: 100%;
height: 310rpx;
border-radius: 10rpx;
}
}
</style>
分类导航
xxxxxxxxxx
// Categories.vue
<script lang="ts" setup>
import Item from './Categories/Item.vue'
</script>
<template>
<view grid grid-cols-4 grid-rows-2 p="x-2 b-4" center mt rounded-2 style="box-shadow: 0 0 40rpx 0 rgba(215, 215, 215, 0.35)">
<Item :id="-1" title="全部" icon="qb" linear-gradient="linear-gradient(-44deg, #674dfa 14%, #baafff 85%)" />
<Item :id="2" title="前端&全栈" icon="qd" linear-gradient="linear-gradient(-44deg, #f76122 14%, #ff9458 85%)" />
<Item :id="1" title="后端&架构" icon="hd" linear-gradient="linear-gradient(-44deg, #d53a4c 14%, #fd8291 85%)" />
<Item :id="3" title="测试&测开" icon="cs" linear-gradient="linear-gradient(-224deg, #fbe444 14%, #ffb613 84%)" />
<Item :id="5" title="运维&容器" icon="yw" linear-gradient="linear-gradient(-44deg, #20c5ad 14%, #6edcd0 82%)" />
<Item :id="7" title="架构&管理" icon="jg" linear-gradient="linear-gradient(-44deg, #4d84fa 14%, #65a5ff 85%)" />
<Item :id="8" title="算法&人工智能" icon="sf" linear-gradient="linear-gradient(-44deg, #ee56e8 14%, #fb9bf7 85%)" />
<Item :id="9" title="大数据" icon="dsj" linear-gradient="linear-gradient(-44deg, #2cc5e6 14%, #91e8fb 84%)" />
</view>
</template>
简介:课程列表
课程列表开发
xxxxxxxxxx
<script lang="ts" setup>
import Item from './ProductContainer/Item.vue'
import type { IProduct, IStageItem } from '@/typings/interface'
defineProps<{
title: string
list: IProduct[] | IStageItem[]
subTitle?: string
}>()
</script>
<template>
<view class="hot_product">
<div between mb-3>
<text class="title" truncate>
</text>
<text v-if="subTitle" text="24 #333333" truncate>
</text>
</div>
<view class="items">
<Item v-for="item in list" :key="item.id" :item="item" />
</view>
</view>
</template>
<style lang="scss" scoped>
.hot_product {
display: flex;
flex-direction: column;
margin-top: 42rpx;
.title {
color: #333333;
font-size: 38rpx;
}
}
</style>
xxxxxxxxxx
<script lang="ts" setup>
import type { IProduct, IStageItem } from '@/typings/interface'
const { item } = defineProps<{ item: IProduct | IStageItem }>()
function isProduct(item: IProduct | IStageItem): item is IProduct {
return (item as IProduct).courseLevel !== undefined
}
</script>
<template>
<view class="item">
<image :src="item.coverImg" mode="scaleToFill" />
<view class="right">
<text class="title">
</text>
<view class="second">
<image v-for="i in 5" :key="i" src="@/static/images/icons/common/fire.png" mode="scaleToFill" />
</view>
<view class="user-price">
<view class="user">
<image src="@/static/images/icons/common/user.png" mode="scaleToFill" />
<text></text>
</view>
<view class="price">
<view>
<text class="dollor">
¥
</text>
<text class="price">
</text>
</view>
</view>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.item {
width: 100%;
display: flex;
margin-bottom: 40rpx;
image {
width: 300rpx;
height: 175rpx;
border-radius: 10rpx;
flex-shrink: 0;
}
.right {
display: flex;
margin-left: 15rpx;
flex-direction: column;
justify-content: space-between;
.title {
color: #333333;
font-size: 27rpx;
overflow: hidden;
text-overflow: ellipsis;
display: box;
line-clamp: 2;
box-orient: vertical;
word-break: break-all;
}
.second {
display: flex;
align-items: center;
text {
color: #7f7f7f;
font-size: 24rpx;
margin-right: 10rpx;
}
image {
width: 27rpx;
height: 27rpx;
margin-right: 5rpx;
}
}
.user-price {
display: flex;
justify-content: space-between;
.user {
display: flex;
align-items: center;
image {
width: 32rpx;
height: 32rpx;
}
text {
font-size: 28rpx;
color: #7f7f7f;
margin-left: 5rpx;
}
}
.price {
display: flex;
align-items: baseline;
.dollor {
font-size: 24rpx;
}
&>view {
display: flex;
align-items: baseline;
margin-left: 20rpx;
&:last-child {
color: #e51b11;
font-size: 40rpx;
}
}
}
}
}
}
</style>
简介:微信小程序项目底部导航开发
Pages.json配置
xxxxxxxxxx
"tabBar": {
"color": "#515151",
"backgroundColor": "#ffffff",
"selectedColor": "#f38e48",
"fontSize": "12px",
"iconWidth": "25px",
"borderStyle": "white",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/images/icons/tabBar/index.png",
"selectedIconPath": "static/images/icons/tabBar/index_active.png"
},
{
"pagePath": "pages/category/index",
"text": "分类",
"iconPath": "static/images/icons/tabBar/categroy.png",
"selectedIconPath": "static/images/icons/tabBar/category_active.png"
},
{
"pagePath": "pages/study/index",
"text": "学习",
"iconPath": "static/images/icons/tabBar/study.png",
"selectedIconPath": "static/images/icons/tabBar/study_active.png"
},
{
"pagePath": "pages/mine/index",
"text": "我的",
"iconPath": "static/images/icons/tabBar/mine.png",
"selectedIconPath": "static/images/icons/tabBar/mine_active.png"
}
]
}
简介:微信小程序项目分类页面开发
布局开发
xxxxxxxxxx
<script lang="ts" setup>
import NavigationBar from './components/NavigationBar.vue'
import ProductContainer from './components/ProductContainer.vue'
</script>
<template>
<view style="background-color: #f3f3f3" overflow-x-hidden>
<NavigationBar />
<ProductContainer />
</view>
</template>
简介:微信小程序项目分类页面开发
课程展示子组件
xxxxxxxxxx
<script lang="ts" setup>
import Product from './ProductContainer/Product.vue'
import { queryProductById } from '@/api/category';
let products = $ref<any>()
products = queryProductById().data.current_data;
</script>
<template>
<view class="container">
<view class="products">
<Product v-for="item, index in products" :key="index" :product="item" />
</view>
</view>
</template>
<style lang="scss" scoped>
.container {
background-color: white;
border-top-left-radius: 30rpx;
border-top-right-radius: 30rpx;
padding: 30rpx 35rpx;
position: relative;
box-sizing: border-box;
.tab {
display: flex;
position: relative;
justify-content: space-around;
.select_btn {
position: relative;
image {
width: 14rpx;
height: 8rpx;
position: absolute;
top: 45%;
left: 120%;
&.rotate {
transform: rotate(180deg);
}
}
}
text {
color: #4d555d;
font-size: 30rpx;
&.active {
color: #f38e48;
}
}
}
.products {
display: flex;
flex-wrap: wrap;
margin-top: 20rpx;
justify-content: space-between;
}
}
</style>
xxxxxxxxxx
<script lang="ts" setup>
import type { IProduct } from '@/typings/interface'
defineProps<{ product: IProduct }>()
</script>
<template>
<view class="product">
<image :src="product.coverImg" mode="scaleToFill" />
<text class="title">
</text>
<view class="level_price">
<view class="price">
<view>
<text class="dollor">
¥
</text>
<text class="price">
</text>
</view>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.product {
display: flex;
position: relative;
flex-direction: column;
width: 320rpx;
margin-bottom: 45rpx;
border-radius: 15rpx;
box-sizing: border-box;
box-shadow: 0px 3px 10px 0px rgba(215, 215, 215, 0.35);
image {
height: 200rpx;
width: 100%;
border-radius: 20rpx 20rpx 0 0;
flex-shrink: 0;
padding-bottom: 15rpx;
}
.title {
color: #555555;
font-size: 22rpx;
display: box;
line-clamp: 2;
word-break: break-all;
text-overflow: ellipsis;
box-orient: vertical;
padding-inline: 15rpx;
overflow: hidden;
}
.level_price {
display: flex;
justify-content: space-between;
align-items: baseline;
padding-inline: 15rpx;
padding-bottom: 15rpx;
.level {
color: #7f7f7f;
font-size: 20rpx;
}
.price {
display: flex;
align-items: baseline;
.dollor {
font-size: 24rpx;
}
&>view {
display: flex;
align-items: baseline;
margin-left: 5rpx;
&:last-child {
color: #e51b11;
font-size: 30rpx;
}
}
}
}
}
</style>
愿景:"让编程不再难学,让技术与生活更加有趣"
更多课程请访问 xdclass.net
简介:布局+数据获取
布局+数据获取
xxxxxxxxxx
<script lang="ts" setup>
import TabContainer from './components/TabContainer.vue'
import NavigationBar from './components/NavigationBar.vue'
</script>
<template>
<view>
<NavigationBar />
<TabContainer />
</view>
</template>
<style>
page {
background: #f5f5f5;
}
</style>
xxxxxxxxxx
<script lang="ts" setup>
import { queryPlayRecordPage } from '@/api/user'
let playRecordPage = $ref<any[]>([])
playRecordPage = queryPlayRecordPage().data.current_data
</script>
<template>
<view class="container">
<view class="products">
111
</view>
</view>
</template>
<style lang="scss" scoped>
.container {
.tab_top {
width: 100%;
display: flex;
background-color: white;
}
.products {
padding: 0 20rpx;
margin-top: 45rpx;
}
}
</style>
简介:课程播放记录列表
课程播放记录列表
xxxxxxxxxx
<script lang="ts" setup>
import type { IPlayRecord } from '@/typings/interface'
defineProps<{ product: IPlayRecord }>()
function getProgress(product: IPlayRecord) {
const progress = Math.ceil((product?.learnIds.split(',')?.length / product?.episodeNum) * 100)
return !product?.learnIds ? 0 : progress >= 100 ? 100 : progress
}
</script>
<template>
<view class="product">
<image :src="product?.coverImg" mode="scaleToFill" />
<view class="right">
<text class="title">
</text>
<view class="progress">
<progress :percent="getProgress(product)" active-color="#f59a23" border-radius="35" />
<text>已学习</text>
<text class="percent">
%
</text>
</view>
<view class="bottom">
<text v-if="product?.learnIds" class="chapter">
学习到:
</text>
<text v-else class="chapter">
还未学习
</text>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.product {
width: 100%;
display: flex;
margin-bottom: 40rpx;
padding: 30rpx 30rpx 30rpx 30rpx;
border-radius: 30rpx;
box-sizing: border-box;
background-color: white;
box-shadow: 0px 3px 10px 0px rgba(215, 215, 215, 0.35);
image {
flex-shrink: 0;
width: 260rpx;
height: 180rpx;
border-radius: 10rpx;
}
.right {
margin-left: 20rpx;
.title {
overflow: hidden;
font-size: 24rpx;
color: #333333;
display: box;
line-clamp: 2;
word-break: break-all;
text-overflow: ellipsis;
box-orient: vertical;
}
.progress {
display: flex;
margin-top: 15rpx;
// 兼容微信小程序
progress {
width: 60%;
margin-right: 15rpx;
}
:deep(.uni-progress) {
width: 60%;
margin-right: 15rpx;
}
:deep(.uni-progress-inner-bar) {
border-radius: 35rpx;
}
text {
font-size: 20rpx;
color: #333333;
margin-right: 5rpx;
&.percent {
color: #f7595b;
}
}
}
.bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 15rpx;
background-color: white;
.chapter {
color: #555555;
font-size: 20rpx;
margin-left: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 250rpx;
}
.continue {
height: 38rpx;
color: white;
font-size: 20rpx;
line-height: 38rpx;
border-radius: 10rpx;
background-color: #f38e48;
flex-shrink: 0;
&::after {
border: none;
}
}
}
}
}
</style>
简介:微信小程序项目我的页面开发
未登录和登录的静态页面
xxxxxxxxxx
<script lang="ts" setup>
import LoginHeader from './components/LoginHeader.vue'
import NavigationBar from './components/NavigationBar.vue'
</script>
<template>
<view class="mine">
<NavigationBar />
<LoginHeader />
</view>
</template>
<style lang="scss" scoped>
</style>
xxxxxxxxxx
<script lang="ts" setup>
</script>
<template>
<uni-nav-bar :fixed="true" :status-bar="true" :border="false" background-color="#ffffff" left-width="300rpx"
right-width="300rpx">
<template #left>
<text class="my_study">
我的
</text>
</template>
</uni-nav-bar>
</template>
<style lang="scss" scoped>
.my_study {
color: #333333;
font-size: 36rpx;
font-weight: bold;
padding-left: 14rpx;
}
</style>
xxxxxxxxxx
<script lang="ts" setup>
const { isLogin } = $(useUser())
const username = computed(() => (isLogin ? '大钊' : '点击登录'))
const avatar = computed(() => (isLogin ? '/static/images/icons/mine/user.png' : '/static/images/icons/mine/user.png'))
const learnTime = computed(() =>
isLogin ? `学习时长:${(100 / 3600).toFixed(2)}小时` : '新用户专属200D币礼包'
)
</script>
<template>
<view class="flex px-2" w-full>
<view relative>
<image :src="avatar" w-120 h-120 rounded-full class="b b-#f38e48" />
</view>
<view m="y-1 x-4" flex="~ col">
<text text="40 #4d555d">
</text>
<view between w-full>
<text text="30 #f38e48">
</text>
</view>
</view>
</view>
</template>
简介:微信小程序项目我的页面开发
登录状态切换
xxxxxxxxxx
import { getDetail } from '@/api/user';
import type { IUser } from '@/typings/interface';
export const useUser = defineStore(
'user',
() => {
let isLogin = $ref<boolean>(false);
let details = $ref<IUser>();
let token = $ref<string>('');
// 更改登录状态和获取用户数据
async function switchLoginState(_token: string) {
token = _token;
isLogin = true;
await syncUserInfo();
uni.switchTab({ url: '/pages/index/index' });
}
async function syncUserInfo() {
if (!token || !isLogin) return;
details = getDetail().data;
}
return $$({
isLogin,
switchLoginState,
details,
});
},
{
persist: true,
}
);
愿景:"让编程不再难学,让技术与生活更加有趣"
更多课程请访问 xdclass.net
简介:微信公众平台小程序注册—邮箱申请
申请
邮箱绑定问题
使用qq邮箱可能会提示“账号被绑定过”
可以使用其他的邮箱,没有的话注册即可
邮箱激活
跳转页面提示 “邮箱已注册” 代表已激活成功
简介:微信公众平台小程序注册—完善信息
使用注册的小程序邮箱账号登录微信公众平台
使用微信绑定上管理员权限
完善基本信息
按照指引的提示填上名称、简称、要求尺寸的头像、介绍、类目
简介:小滴课堂微信小程序项目打包发布流程解读
打包流程
简介:小滴课堂微信小程序项目打包发布流程解读
发布流程
简介:学习uniapp框架如何进阶开发自己的APP?
平台
商业级全栈多端项目-小滴云在线教育平台