更新:增加“课程首页”功能
This commit is contained in:
3
androidPrivacy.json
Normal file
3
androidPrivacy.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"prompt": "template"
|
||||
}
|
||||
@@ -27,5 +27,19 @@ export const commonApi = {
|
||||
data: { id }
|
||||
})
|
||||
return res.agreement
|
||||
},
|
||||
/**
|
||||
* 获取消息列表(新闻播报)
|
||||
* @param isBook 是否是图书相关 0-否 1-是
|
||||
* @param isMedical 是否是医学相关 0-否 1-是
|
||||
* @param isSociology 是否是社会学相关 0-否 1-是
|
||||
* @returns 消息列表
|
||||
*/
|
||||
getMessageList(isBook: number, isMedical: number, isSociology: number) {
|
||||
return mainClient.request<IMessageListResponse>({
|
||||
url: 'common/message/listByPage',
|
||||
method: 'POST',
|
||||
data: { isBook, isMedical, isSociology }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
54
api/modules/course.ts
Normal file
54
api/modules/course.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// api/modules/course.ts
|
||||
import { createRequestClient } from '../request'
|
||||
import { SERVICE_MAP } from '../config'
|
||||
import type {
|
||||
ICourseMedicalTreeResponse,
|
||||
IUserLateCourseListResponse,
|
||||
IMarketCourseListResponse
|
||||
} from '@/types/course'
|
||||
|
||||
const client = createRequestClient({ baseURL: SERVICE_MAP.MAIN })
|
||||
|
||||
/**
|
||||
* 课程相关API
|
||||
*/
|
||||
export const courseApi = {
|
||||
/**
|
||||
* 获取课程分类树
|
||||
* @returns 分类数据
|
||||
*/
|
||||
getCourseMedicalTree() {
|
||||
return client.request<ICourseMedicalTreeResponse>({
|
||||
url: 'medical/home/getCourseMedicalTree',
|
||||
method: 'POST',
|
||||
data: {}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取用户最近观看课程列表
|
||||
* @returns 观看记录列表
|
||||
*/
|
||||
getUserLateCourseList() {
|
||||
return client.request<IUserLateCourseListResponse>({
|
||||
url: 'medical/home/getUserLateCourseList',
|
||||
method: 'POST',
|
||||
data: {}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取市场课程列表(试听课程)
|
||||
* @param id 市场ID
|
||||
* @param limit 每页数量
|
||||
* @param page 页码
|
||||
* @returns 课程列表
|
||||
*/
|
||||
getMarketCourseList(id: number, limit: number, page: number) {
|
||||
return client.request<IMarketCourseListResponse>({
|
||||
url: 'medical/home/getMarketCourseList',
|
||||
method: 'POST',
|
||||
data: { id, limit, page }
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
<view class="navbar">
|
||||
<view class="statusBar" :style="{height:getStatusBarHeight()+'px'}"></view>
|
||||
<view class="titleBar" :style="{height:getTitleBarHeight()+'px'}">
|
||||
<wd-navbar :title="title" :left-arrow="leftArrow" @click="handleClickLeft">
|
||||
<wd-navbar v-if="title" :title="title" :left-arrow="leftArrow" @click="handleClickLeft"></wd-navbar>
|
||||
<wd-navbar v-else :left-arrow="leftArrow" @click="handleClickLeft">
|
||||
<template #title>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
@@ -38,7 +39,14 @@ const handleClickLeft = () => {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: var(--wot-navbar-background);
|
||||
z-index: 9;
|
||||
|
||||
.statusBar{
|
||||
background: var(--wot-navbar-background);
|
||||
}
|
||||
|
||||
.titleBar{
|
||||
background: var(--wot-navbar-background);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -269,5 +269,13 @@
|
||||
},
|
||||
"workOrder": {
|
||||
"submit_success": "Submitted successfully"
|
||||
},
|
||||
"course": {
|
||||
"title": "Course",
|
||||
"watchHistory": "Watch History",
|
||||
"tryListen": "Try Listening",
|
||||
"moreTryListen": "More Trials",
|
||||
"buy": "Buy",
|
||||
"searchPlaceholder": "Search courses..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,5 +270,13 @@
|
||||
},
|
||||
"workOrder": {
|
||||
"submit_success": "提交成功"
|
||||
},
|
||||
"course": {
|
||||
"title": "课程",
|
||||
"watchHistory": "观看记录",
|
||||
"tryListen": "精彩试听",
|
||||
"moreTryListen": "更多试听",
|
||||
"buy": "购买",
|
||||
"searchPlaceholder": "搜索课程..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name" : "EducationApp2",
|
||||
"name" : "Amazing Limited",
|
||||
"appid" : "__UNI__1250B39",
|
||||
"description" : "",
|
||||
"description" : "Amazing Limited",
|
||||
"versionName" : "1.0.1",
|
||||
"versionCode" : 101,
|
||||
"transformPx" : false,
|
||||
@@ -10,44 +10,90 @@
|
||||
"usingComponents" : true,
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
"compilerVersion" : 3,
|
||||
"compatible" : {
|
||||
"ignoreVersion" : true
|
||||
},
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : true,
|
||||
"alwaysShowBeforeRender" : false,
|
||||
"waiting" : true,
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules" : {},
|
||||
"modules" : {
|
||||
"Camera" : {},
|
||||
"Payment" : {}
|
||||
},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
/* android打包配置 */
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
"<uses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\"/>"
|
||||
],
|
||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
|
||||
"minSdkVersion" : 21
|
||||
"minSdkVersion" : 23,
|
||||
"targetSdkVersion" : 35
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {
|
||||
"dSYMs" : false
|
||||
"dSYMs" : false,
|
||||
"privacyDescription" : {
|
||||
"NSPhotoLibraryUsageDescription" : "Ensure the normal use of your avatar modification, appeal feedback, image upload, and message upload functions in this app.",
|
||||
"NSPhotoLibraryAddUsageDescription" : "Ensure the normal use of the functions of modifying avatars, uploading images for appeals and feedback, and uploading images for comments in this app.",
|
||||
"NSCameraUsageDescription" : "Ensure the normal use of the functions of modifying avatars, uploading images for appeals and feedback, and uploading images for comments in this app."
|
||||
},
|
||||
"idfa" : false
|
||||
},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {}
|
||||
"sdkConfigs" : {
|
||||
"payment" : {
|
||||
"google" : {},
|
||||
"stripe" : {
|
||||
"__platform__" : [ "ios", "android" ],
|
||||
"returnURL_ios" : "com.amazinglimited://stripe"
|
||||
}
|
||||
}
|
||||
},
|
||||
"icons" : {
|
||||
"android" : {
|
||||
"hdpi" : "unpackage/res/icons/72x72.png",
|
||||
"xhdpi" : "unpackage/res/icons/96x96.png",
|
||||
"xxhdpi" : "unpackage/res/icons/144x144.png",
|
||||
"xxxhdpi" : "unpackage/res/icons/192x192.png"
|
||||
},
|
||||
"ios" : {
|
||||
"appstore" : "unpackage/res/icons/1024x1024.png",
|
||||
"ipad" : {
|
||||
"app" : "unpackage/res/icons/76x76.png",
|
||||
"app@2x" : "unpackage/res/icons/152x152.png",
|
||||
"notification" : "unpackage/res/icons/20x20.png",
|
||||
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||
"proapp@2x" : "unpackage/res/icons/167x167.png",
|
||||
"settings" : "unpackage/res/icons/29x29.png",
|
||||
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||
"spotlight" : "unpackage/res/icons/40x40.png",
|
||||
"spotlight@2x" : "unpackage/res/icons/80x80.png"
|
||||
},
|
||||
"iphone" : {
|
||||
"app@2x" : "unpackage/res/icons/120x120.png",
|
||||
"app@3x" : "unpackage/res/icons/180x180.png",
|
||||
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||
"notification@3x" : "unpackage/res/icons/60x60.png",
|
||||
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||
"settings@3x" : "unpackage/res/icons/87x87.png",
|
||||
"spotlight@2x" : "unpackage/res/icons/80x80.png",
|
||||
"spotlight@3x" : "unpackage/res/icons/120x120.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"splashscreen" : {
|
||||
"useOriginalMsgbox" : true
|
||||
}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
@@ -70,7 +116,8 @@
|
||||
"usingComponents" : true
|
||||
},
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
"enable" : false,
|
||||
"version" : "2"
|
||||
},
|
||||
"vueVersion" : "3"
|
||||
}
|
||||
|
||||
21
pages.json
21
pages.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"path": "pages/course/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "%index.title%"
|
||||
}
|
||||
@@ -123,21 +123,6 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
// "tabBar": {
|
||||
// "color": "#7A7E83",
|
||||
// "selectedColor": "#007AFF",
|
||||
// "borderStyle": "black",
|
||||
// "backgroundColor": "#F8F8F8",
|
||||
// "list": [{
|
||||
// "pagePath": "pages/index/index",
|
||||
// "text": "%index.home%"
|
||||
// },
|
||||
// {
|
||||
// "pagePath": "pages/component/component",
|
||||
// "text": "%index.component%"
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
"tabBar": {
|
||||
"color": "#444444",
|
||||
"selectedColor": "#079307",
|
||||
@@ -145,7 +130,7 @@
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"pagePath": "pages/course/index",
|
||||
"iconPath": "static/tab/icon1_n.png",
|
||||
"selectedIconPath": "static/tab/icon1_y.png",
|
||||
"text": "%tabar.course%"
|
||||
@@ -166,7 +151,7 @@
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "疯子读书",
|
||||
"navigationBarTitleText": "太湖国际",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"navigationStyle": "custom"
|
||||
|
||||
@@ -317,16 +317,6 @@ function goToDetail(id: number) {
|
||||
url: `/pages/book/detail?id=${id}`
|
||||
})
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
if (pageFrom.value === 'order') {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
} else {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { homeApi } from '@/api/modules/home'
|
||||
import type {
|
||||
@@ -459,7 +460,13 @@ onMounted(() => {
|
||||
// 重置活动标签选中状态
|
||||
currentActivityIndex.value = 0
|
||||
showActivity.value = false
|
||||
})
|
||||
|
||||
/**
|
||||
* 页面显示
|
||||
*/
|
||||
onShow(() => {
|
||||
// 刷新数据
|
||||
getVipInfo()
|
||||
getMyBooks()
|
||||
getRecommendBooks()
|
||||
|
||||
699
pages/course/index.vue
Normal file
699
pages/course/index.vue
Normal file
@@ -0,0 +1,699 @@
|
||||
<template>
|
||||
<view class="course-home-page">
|
||||
<!-- 头部区域 -->
|
||||
<view class="home-bg" :style="{ paddingTop: notchHeight + 'px' }">
|
||||
<wd-search
|
||||
hide-cancel
|
||||
light
|
||||
clearable
|
||||
class="search-bar"
|
||||
:placeholder="$t('course.searchPlaceholder')"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<view class="icon-hua">
|
||||
<image
|
||||
src="../../static/course/homeLogo.png"
|
||||
mode="aspectFit"
|
||||
class="icon-hua-img"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 课程分类标签区域 -->
|
||||
<!-- <view class="newLeve2">
|
||||
<view class="home_nar nomargin" style="padding: 0; background-color: #fff">
|
||||
<view class="flexbox">
|
||||
<view
|
||||
:class="['hn_cl_tit', currentIndex == index ? 'active' : '']"
|
||||
@click="curseClick(item, index)"
|
||||
v-for="(item, index) in curseTagList"
|
||||
:key="item.id"
|
||||
>
|
||||
<image :src="item.icon" mode="aspectFit"></image>
|
||||
<text>{{ item.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="fourBox"
|
||||
style="padding: 0; padding-bottom: 8rpx"
|
||||
v-if="sbuMedicalTagsList && sbuMedicalTagsList.length > 0"
|
||||
>
|
||||
<view
|
||||
class="childrenBox fourIcon flexbox"
|
||||
style="justify-content: space-around"
|
||||
>
|
||||
<view
|
||||
class="item flexbox"
|
||||
@click="curseClickJump(item)"
|
||||
v-for="(item, index) in sbuMedicalTagsList"
|
||||
:key="item.id"
|
||||
>
|
||||
<image
|
||||
:src="item.icon"
|
||||
mode="aspectFit"
|
||||
v-if="item.icon != '' && item.icon != null"
|
||||
></image>
|
||||
<text>{{ item.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 观看记录区域 -->
|
||||
<view class="learnBox" v-if="learnList.length > 0">
|
||||
<view class="titleBox flexbox">
|
||||
<image src="../../static/course/learing.png" mode="aspectFit"></image>
|
||||
<text>{{ $t('course.watchHistory') }}</text>
|
||||
</view>
|
||||
<view class="learn flexbox">
|
||||
<view
|
||||
class="item"
|
||||
v-for="(item, index) in learnList"
|
||||
:key="item.id"
|
||||
@click="onPageJump('/pages/course/courseDetail', item.id)"
|
||||
>
|
||||
<view class="img" style="overflow: hidden">
|
||||
<image
|
||||
v-if="item.image && item.image != ''"
|
||||
:src="item.image"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<image v-else src="../../static/course/nobg.jpg" mode="widthFix"></image>
|
||||
</view>
|
||||
<view class="txt555">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 新闻播报区域 -->
|
||||
<view style="padding: 0 5px;" v-if="newsList.length > 0">
|
||||
<view class="newsBox flexbox">
|
||||
<view class="icon">
|
||||
<wd-icon name="sound" size="22px"></wd-icon>
|
||||
</view>
|
||||
<view class="newscoll">
|
||||
<swiper
|
||||
class="swiper"
|
||||
interval="5000"
|
||||
circular
|
||||
autoplay
|
||||
vertical
|
||||
:indicator-dots="false"
|
||||
>
|
||||
<swiper-item
|
||||
class="item"
|
||||
v-for="(item, index) in newsList"
|
||||
:key="item.id"
|
||||
@click="newsClick(item)"
|
||||
>
|
||||
<view class="swiper-item">{{ item.title }}</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 精彩试听区域 -->
|
||||
<view class="learnBox" v-if="tryListenList.length > 0">
|
||||
<view class="titleBox flexbox">
|
||||
<image src="../../static/course/try_listen.png" mode="aspectFit"></image>
|
||||
<text>{{ $t('course.tryListen') }}</text>
|
||||
</view>
|
||||
<view class="learn flexbox shiting">
|
||||
<view
|
||||
class="item"
|
||||
v-for="(item, index) in tryListenList"
|
||||
:key="item.id"
|
||||
@click="onPageJump('/pages/course/courseDetail', item.id, item.title)"
|
||||
>
|
||||
<view class="imgcontainer">
|
||||
<image
|
||||
v-if="item.image == '' || !item.image"
|
||||
src="../../static/course/nobg.jpg"
|
||||
mode="aspectFit"
|
||||
>
|
||||
</image>
|
||||
<image v-else :src="item.image"></image>
|
||||
</view>
|
||||
<view class="buyItems flexbox">
|
||||
<view class="txt555">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
<view class="buybtn">
|
||||
<span>{{ $t('course.buy') }}</span>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="moreBox shiting">
|
||||
<text @click="onPageJump('/pages/course/tryListen', 1, $t('course.tryListen'))"
|
||||
>{{ $t('course.moreTryListen') }}</text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { onShow, onHide, onPullDownRefresh, onPageScroll, onLoad } from '@dcloudio/uni-app'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { courseApi } from '@/api/modules/course'
|
||||
import { commonApi } from '@/api/modules/common'
|
||||
import type { IMedicalTag, ICourse, INews } from '@/types/course'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// 系统信息
|
||||
const notchHeight = ref<number>(0) // 刘海屏高度
|
||||
const scrollTop = ref<number>(0) // 滚动位置
|
||||
|
||||
// 分类相关
|
||||
const curseTagList = ref<IMedicalTag[]>([]) // 一级分类标签列表
|
||||
const sbuMedicalTagsList = ref<IMedicalTag[]>([]) // 二级分类标签列表
|
||||
const currentIndex = ref<number>(0) // 当前选中的一级分类索引
|
||||
const currentItem = ref<IMedicalTag | null>(null) // 当前选中的一级分类项
|
||||
|
||||
// 观看记录
|
||||
const learnList = ref<ICourse[]>([]) // 观看记录列表
|
||||
|
||||
// 新闻播报
|
||||
const newsList = ref<INews[]>([]) // 新闻列表
|
||||
|
||||
// 精彩试听
|
||||
const tryListenList = ref<ICourse[]>([]) // 试听课程列表
|
||||
|
||||
/**
|
||||
* 处理搜索点击
|
||||
*/
|
||||
const handleSearch = ({ value }: { value: string }) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/book/search?keyword=${value}`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取课程分类数据
|
||||
*/
|
||||
const getMedicalTags = async () => {
|
||||
try {
|
||||
const res = await courseApi.getCourseMedicalTree()
|
||||
if (res && res.code === 0) {
|
||||
if (res.labels && res.labels.length > 0) {
|
||||
curseTagList.value = res.labels
|
||||
// 根据 currentIndex 设置初始选中的分类
|
||||
if (res.labels[currentIndex.value]) {
|
||||
const selectedTag = res.labels[currentIndex.value]
|
||||
if (selectedTag.isLast === 0) {
|
||||
// 非终极分类,显示子分类
|
||||
if (selectedTag.children && selectedTag.children.length > 0) {
|
||||
sbuMedicalTagsList.value = selectedTag.children
|
||||
} else {
|
||||
sbuMedicalTagsList.value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
curseTagList.value = []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取课程分类失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取观看记录
|
||||
*/
|
||||
const getLearnCourse = async () => {
|
||||
try {
|
||||
const res = await courseApi.getUserLateCourseList()
|
||||
if (res && res.code === 0) {
|
||||
if (res.page && res.page.length > 0) {
|
||||
learnList.value = res.page
|
||||
} else {
|
||||
learnList.value = []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取观看记录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取试听课程列表
|
||||
*/
|
||||
const getTryListenList = async () => {
|
||||
try {
|
||||
const res = await courseApi.getMarketCourseList(1, 6, 1)
|
||||
if (res && res.code === 0) {
|
||||
if (res.courseList && res.courseList.records && res.courseList.records.length > 0) {
|
||||
tryListenList.value = res.courseList.records
|
||||
} else {
|
||||
tryListenList.value = []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取试听课程失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新闻列表
|
||||
*/
|
||||
const getNewsList = async () => {
|
||||
try {
|
||||
const res = await commonApi.getMessageList(0, 1, 0)
|
||||
if (res && res.code === 0) {
|
||||
if (res.messages && res.messages.length > 0) {
|
||||
newsList.value = res.messages
|
||||
} else {
|
||||
newsList.value = []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取新闻列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一请求所有数据
|
||||
*/
|
||||
const requestAll = async () => {
|
||||
await getLearnCourse()
|
||||
await getMedicalTags()
|
||||
await getTryListenList()
|
||||
await getNewsList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 一级分类点击处理
|
||||
*/
|
||||
const curseClick = (item: IMedicalTag, index: number) => {
|
||||
currentItem.value = item
|
||||
currentIndex.value = index
|
||||
|
||||
if (item.isLast === 0) {
|
||||
// 非终极分类,显示子分类
|
||||
if (item.children && item.children.length > 0) {
|
||||
sbuMedicalTagsList.value = item.children
|
||||
} else {
|
||||
sbuMedicalTagsList.value = []
|
||||
}
|
||||
} else {
|
||||
// 终极分类,直接跳转
|
||||
uni.navigateTo({
|
||||
url: `/pages/course/index?id=${item.id}&title=${item.title}&pid=${item.id}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 二级分类点击处理
|
||||
*/
|
||||
const curseClickJump = (item: IMedicalTag) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/course/index?id=${item.id}&title=${item.title}&pid=${item.pid}`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新闻点击处理
|
||||
*/
|
||||
const newsClick = (item: INews) => {
|
||||
if (item.type === 1 && item.url) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/news/newsForwebview?newsId=${item.id}&url=${item.url}&type=${item.type}`
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/pages/news/news?newsId=${item.id}&url=${item.url}&type=${item.type}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面跳转统一处理
|
||||
*/
|
||||
const onPageJump = (url: string, id?: number, title?: string) => {
|
||||
let targetUrl = url
|
||||
if (id !== undefined) {
|
||||
targetUrl += `?id=${id}`
|
||||
if (title) {
|
||||
targetUrl += `&title=${encodeURIComponent(title)}`
|
||||
}
|
||||
}
|
||||
uni.navigateTo({ url: targetUrl })
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面挂载
|
||||
*/
|
||||
onMounted(() => {
|
||||
// 获取系统信息,设置刘海屏高度
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
notchHeight.value = systemInfo.safeArea?.top || 0
|
||||
|
||||
// 重置分类索引
|
||||
currentIndex.value = 0
|
||||
|
||||
// 请求所有数据
|
||||
requestAll()
|
||||
})
|
||||
|
||||
/**
|
||||
* 页面显示
|
||||
*/
|
||||
onShow(() => {
|
||||
// 检查是否有固定的分类选择状态
|
||||
const fixed = uni.getStorageSync('fixed')
|
||||
if (fixed && currentItem.value) {
|
||||
curseClick(currentItem.value, currentIndex.value)
|
||||
} else {
|
||||
currentIndex.value = 0
|
||||
currentItem.value = null
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
requestAll()
|
||||
})
|
||||
|
||||
/**
|
||||
* 页面隐藏
|
||||
*/
|
||||
onHide(() => {
|
||||
// 清除固定状态
|
||||
uni.removeStorageSync('fixed')
|
||||
})
|
||||
|
||||
/**
|
||||
* 下拉刷新
|
||||
*/
|
||||
onPullDownRefresh(() => {
|
||||
requestAll().then(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 页面滚动
|
||||
*/
|
||||
onPageScroll((e) => {
|
||||
scrollTop.value = e.scrollTop
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// SCSS 变量定义
|
||||
$theme-color: #55aa7f;
|
||||
$theme-color-light: #e4eefa;
|
||||
$bg-color: #f7f7f7;
|
||||
$card-bg: #ffffff;
|
||||
$text-primary: #333333;
|
||||
$text-secondary: #666666;
|
||||
$text-placeholder: #999999;
|
||||
$border-color: #eeeeee;
|
||||
|
||||
.course-home-page {
|
||||
min-height: 100vh;
|
||||
background-color: $bg-color;
|
||||
font-size: 28upx;
|
||||
}
|
||||
|
||||
// 头部区域样式
|
||||
.home-bg {
|
||||
background-image: url('@/static/course/home_bg.jpg');
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
padding: 30rpx;
|
||||
position: relative;
|
||||
height: 340rpx;
|
||||
|
||||
.icon-hua {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: block;
|
||||
padding-top: 20rpx;
|
||||
|
||||
.icon-hua-img {
|
||||
width: 100%;
|
||||
height: 160rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
// 公共样式
|
||||
.flexbox {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.nomargin {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
// 分类标签样式
|
||||
.newLeve2 {
|
||||
background-color: #fff;
|
||||
padding: 0 10rpx;
|
||||
border-top: 1px solid #eee;
|
||||
padding-top: 4rpx;
|
||||
}
|
||||
|
||||
.home_nar {
|
||||
margin: 10px 0;
|
||||
justify-content: space-between;
|
||||
color: #333;
|
||||
margin-bottom: 0;
|
||||
padding: 0 5px;
|
||||
|
||||
.hn_cl_tit {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
margin-right: 8rpx;
|
||||
border-bottom: 1px solid #fff;
|
||||
|
||||
image {
|
||||
width: 100rpx;
|
||||
height: 90rpx;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
display: block;
|
||||
padding-bottom: 20rpx;
|
||||
text-align: center;
|
||||
margin-top: 4rpx;
|
||||
font-size: 28rpx;
|
||||
color: #00337f;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.hn_cl_tit:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.hn_cl_tit.active {
|
||||
background-color: $theme-color-light;
|
||||
|
||||
text {
|
||||
color: #3361a5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.childrenBox {
|
||||
background-color: $theme-color-light !important;
|
||||
border-radius: 6rpx !important;
|
||||
box-shadow: 0px 0px 10px 0px rgba(167, 187, 228, 0.3);
|
||||
justify-content: center;
|
||||
box-shadow: none !important;
|
||||
|
||||
.item {
|
||||
text {
|
||||
color: #3361a5;
|
||||
}
|
||||
}
|
||||
|
||||
image {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.fourIcon {
|
||||
justify-content: space-between;
|
||||
box-shadow: 0px 0px 10px 0px rgba(167, 187, 228, 1);
|
||||
text-align: center;
|
||||
height: 60px;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
line-height: 60px;
|
||||
|
||||
.item {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #76664d;
|
||||
padding-left: 6rpx;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
// 观看记录和试听样式
|
||||
.learnBox {
|
||||
background-color: #fff;
|
||||
margin: 10px 5px 20px;
|
||||
border-radius: 20rpx;
|
||||
padding: 10px;
|
||||
box-shadow: 0px 0px 10px 0px rgba(167, 187, 228, 0.3);
|
||||
|
||||
.img {
|
||||
width: 100%;
|
||||
background-color: #f7f7f7;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.learn {
|
||||
justify-content: space-between;
|
||||
margin-top: 20rpx;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.item {
|
||||
width: 326rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 220rpx;
|
||||
}
|
||||
|
||||
.txt555 {
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.titleBox {
|
||||
align-items: center;
|
||||
|
||||
image {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 30rpx;
|
||||
padding-left: 10rpx;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.shiting {
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.buyItems {
|
||||
padding-top: 10rpx;
|
||||
align-items: center;
|
||||
|
||||
.buybtn {
|
||||
display: block;
|
||||
width: 28%;
|
||||
padding: 0 4px;
|
||||
font-size: 24rpx;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
background-color: $theme-color;
|
||||
color: #fff;
|
||||
border-radius: 20rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.txt555 {
|
||||
width: 70%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.moreBox {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
|
||||
text {
|
||||
display: inline-block;
|
||||
border: 1px solid $theme-color;
|
||||
padding: 14rpx 0;
|
||||
width: 80%;
|
||||
border-radius: 60rpx;
|
||||
color: $theme-color;
|
||||
}
|
||||
}
|
||||
|
||||
.imgcontainer {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
// 新闻播报样式
|
||||
.newsBox {
|
||||
justify-content: space-between;
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 0px 10px 0px rgba(167, 187, 228, 0.3);
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 20rpx;
|
||||
padding: 10rpx;
|
||||
line-height: 40rpx;
|
||||
height: 60rpx;
|
||||
|
||||
.icon {
|
||||
color: #00337f;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.newscoll {
|
||||
overflow: hidden;
|
||||
width: calc(100% - 70rpx);
|
||||
height: 40rpx;
|
||||
.item {
|
||||
.swiper-item {
|
||||
font-size: 28rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<view class="login-page">
|
||||
<!-- Logo 背景区域 -->
|
||||
<view class="logo-bg">
|
||||
<text class="welcome-text">Hello! Welcome to<br>Taimed International</text>
|
||||
<text class="welcome-text">Hello! Welcome to<br>Amazing Limited</text>
|
||||
<image src="@/static/icon/login_icon.png" mode="aspectFit" class="icon-hua-1"></image>
|
||||
<image src="@/static/icon/login_icon.png" mode="aspectFit" class="icon-hua-2"></image>
|
||||
</view>
|
||||
@@ -333,7 +333,7 @@ const onSubmit = async () => {
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
url: '/pages/course/index'
|
||||
})
|
||||
}, 600)
|
||||
} else {
|
||||
|
||||
BIN
static/course/homeLogo.png
Normal file
BIN
static/course/homeLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
BIN
static/course/home_bg.jpg
Normal file
BIN
static/course/home_bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
BIN
static/course/learing.png
Normal file
BIN
static/course/learing.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
static/course/try_listen.png
Normal file
BIN
static/course/try_listen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -11,6 +11,7 @@
|
||||
--color-blue-500: oklch(62.3% 0.214 259.815);
|
||||
--color-white: #fff;
|
||||
--spacing: 0.25rem;
|
||||
--font-weight-bold: 700;
|
||||
--radius-lg: 0.5rem;
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
@@ -208,6 +209,69 @@
|
||||
max-width: 96rem;
|
||||
}
|
||||
}
|
||||
.m-2 {
|
||||
margin: calc(var(--spacing) * 2);
|
||||
}
|
||||
.mx-2 {
|
||||
margin-inline: calc(var(--spacing) * 2);
|
||||
}
|
||||
.mx-4 {
|
||||
margin-inline: calc(var(--spacing) * 4);
|
||||
}
|
||||
.mx-auto {
|
||||
margin-inline: auto;
|
||||
}
|
||||
.my-3 {
|
||||
margin-block: calc(var(--spacing) * 3);
|
||||
}
|
||||
.mt-1 {
|
||||
margin-top: calc(var(--spacing) * 1);
|
||||
}
|
||||
.mt-2 {
|
||||
margin-top: calc(var(--spacing) * 2);
|
||||
}
|
||||
.mt-4 {
|
||||
margin-top: calc(var(--spacing) * 4);
|
||||
}
|
||||
.mr-2 {
|
||||
margin-right: calc(var(--spacing) * 2);
|
||||
}
|
||||
.mr-3 {
|
||||
margin-right: calc(var(--spacing) * 3);
|
||||
}
|
||||
.mr-4 {
|
||||
margin-right: calc(var(--spacing) * 4);
|
||||
}
|
||||
.mb-2 {
|
||||
margin-bottom: calc(var(--spacing) * 2);
|
||||
}
|
||||
.mb-3 {
|
||||
margin-bottom: calc(var(--spacing) * 3);
|
||||
}
|
||||
.mb-4 {
|
||||
margin-bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
.mb-5 {
|
||||
margin-bottom: calc(var(--spacing) * 5);
|
||||
}
|
||||
.ml-2 {
|
||||
margin-left: calc(var(--spacing) * 2);
|
||||
}
|
||||
.ml-3 {
|
||||
margin-left: calc(var(--spacing) * 3);
|
||||
}
|
||||
.line-clamp-1 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
.line-clamp-2 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
@@ -232,15 +296,27 @@
|
||||
.table {
|
||||
display: table;
|
||||
}
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.w-\[100px\] {
|
||||
width: 100px;
|
||||
}
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
.flex-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.flex-shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
@@ -250,9 +326,33 @@
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.rounded-full {
|
||||
border-radius: calc(infinity * 1px);
|
||||
}
|
||||
.rounded-lg {
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
@@ -260,6 +360,10 @@
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
}
|
||||
.border-t {
|
||||
border-top-style: var(--tw-border-style);
|
||||
border-top-width: 1px;
|
||||
}
|
||||
.bg-\[blue\] {
|
||||
background-color: blue;
|
||||
}
|
||||
@@ -272,12 +376,80 @@
|
||||
.bg-blue-500 {
|
||||
background-color: var(--color-blue-500);
|
||||
}
|
||||
.bg-white {
|
||||
background-color: var(--color-white);
|
||||
}
|
||||
.bg-gradient-to-br {
|
||||
--tw-gradient-position: to bottom right in oklab;
|
||||
background-image: linear-gradient(var(--tw-gradient-stops));
|
||||
}
|
||||
.p-2 {
|
||||
padding: calc(var(--spacing) * 2);
|
||||
}
|
||||
.p-3 {
|
||||
padding: calc(var(--spacing) * 3);
|
||||
}
|
||||
.p-4 {
|
||||
padding: calc(var(--spacing) * 4);
|
||||
}
|
||||
.p-5 {
|
||||
padding: calc(var(--spacing) * 5);
|
||||
}
|
||||
.p-\[5px\] {
|
||||
padding: 5px;
|
||||
}
|
||||
.px-2 {
|
||||
padding-inline: calc(var(--spacing) * 2);
|
||||
}
|
||||
.px-3 {
|
||||
padding-inline: calc(var(--spacing) * 3);
|
||||
}
|
||||
.px-4 {
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
}
|
||||
.px-5 {
|
||||
padding-inline: calc(var(--spacing) * 5);
|
||||
}
|
||||
.px-20 {
|
||||
padding-inline: calc(var(--spacing) * 20);
|
||||
}
|
||||
.py-1 {
|
||||
padding-block: calc(var(--spacing) * 1);
|
||||
}
|
||||
.py-2 {
|
||||
padding-block: calc(var(--spacing) * 2);
|
||||
}
|
||||
.py-3 {
|
||||
padding-block: calc(var(--spacing) * 3);
|
||||
}
|
||||
.py-5 {
|
||||
padding-block: calc(var(--spacing) * 5);
|
||||
}
|
||||
.py-10 {
|
||||
padding-block: calc(var(--spacing) * 10);
|
||||
}
|
||||
.py-20 {
|
||||
padding-block: calc(var(--spacing) * 20);
|
||||
}
|
||||
.pt-1 {
|
||||
padding-top: calc(var(--spacing) * 1);
|
||||
}
|
||||
.pt-2 {
|
||||
padding-top: calc(var(--spacing) * 2);
|
||||
}
|
||||
.pb-5 {
|
||||
padding-bottom: calc(var(--spacing) * 5);
|
||||
}
|
||||
.pb-10 {
|
||||
padding-bottom: calc(var(--spacing) * 10);
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.font-bold {
|
||||
--tw-font-weight: var(--font-weight-bold);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
.text-\[\#000\] {
|
||||
color: #000;
|
||||
}
|
||||
@@ -297,9 +469,20 @@
|
||||
--tw-ordinal: ordinal;
|
||||
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
|
||||
}
|
||||
.line-through {
|
||||
text-decoration-line: line-through;
|
||||
}
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.shadow-sm {
|
||||
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
.ring {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
@@ -357,6 +540,10 @@
|
||||
inherits: false;
|
||||
initial-value: solid;
|
||||
}
|
||||
@property --tw-font-weight {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-ordinal {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -513,6 +700,7 @@
|
||||
--tw-skew-x: initial;
|
||||
--tw-skew-y: initial;
|
||||
--tw-border-style: solid;
|
||||
--tw-font-weight: initial;
|
||||
--tw-ordinal: initial;
|
||||
--tw-slashed-zero: initial;
|
||||
--tw-numeric-figure: initial;
|
||||
|
||||
@@ -6,9 +6,9 @@ page {
|
||||
--wot-fs-secondary: 14px; // 次要信息,注释/补充/正文
|
||||
|
||||
// 导航栏
|
||||
// --wot-navbar-background: #5355C8;
|
||||
// --wot-navbar-color: #fff;
|
||||
// --wot-navbar-hover-color: #4D4DB9;
|
||||
--wot-navbar-background: #fff;
|
||||
--wot-navbar-color: #000;
|
||||
--wot-navbar-hover-color: #fff;
|
||||
|
||||
// 底部tabbar
|
||||
// --wot-tabbar-height: 60px;
|
||||
|
||||
53
types/course.d.ts
vendored
Normal file
53
types/course.d.ts
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// types/course.d.ts
|
||||
/**
|
||||
* 课程相关类型定义
|
||||
*/
|
||||
|
||||
import type { IApiResponse } from './book'
|
||||
|
||||
/** 医学标签(课程分类) */
|
||||
export interface IMedicalTag {
|
||||
id: number // 标签ID
|
||||
title: string // 标签标题
|
||||
icon: string // 标签图标URL
|
||||
isLast: number // 是否为终极分类 0-否 1-是
|
||||
children?: IMedicalTag[] // 子分类列表
|
||||
pid?: number // 父级ID
|
||||
}
|
||||
|
||||
/** 课程信息 */
|
||||
export interface ICourse {
|
||||
id: number // 课程ID
|
||||
title: string // 课程标题
|
||||
image: string // 课程封面图
|
||||
}
|
||||
|
||||
/** 新闻信息 */
|
||||
export interface INews {
|
||||
id: number // 新闻ID
|
||||
title: string // 新闻标题
|
||||
type: number // 新闻类型 1-外部链接 其他-内部页面
|
||||
url: string // 新闻链接
|
||||
}
|
||||
|
||||
/** 课程分类树响应 */
|
||||
export interface ICourseMedicalTreeResponse extends IApiResponse {
|
||||
labels: IMedicalTag[] // 分类标签列表
|
||||
}
|
||||
|
||||
/** 用户最近观看课程列表响应 */
|
||||
export interface IUserLateCourseListResponse extends IApiResponse {
|
||||
page: ICourse[] // 课程列表
|
||||
}
|
||||
|
||||
/** 市场课程列表响应 */
|
||||
export interface IMarketCourseListResponse extends IApiResponse {
|
||||
courseList: {
|
||||
records: ICourse[] // 课程列表
|
||||
}
|
||||
}
|
||||
|
||||
/** 消息列表响应 */
|
||||
export interface IMessageListResponse extends IApiResponse {
|
||||
messages: INews[] // 消息列表
|
||||
}
|
||||
9
uni_modules/nx-turn/changelog.md
Normal file
9
uni_modules/nx-turn/changelog.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 1.0.3(2024-08-15)
|
||||
* 修复在vue2中不支持的语法
|
||||
## 1.0.2(2024-08-09)
|
||||
* 修改在vue2中不支持的语法
|
||||
## 1.0.1(2024-07-19)
|
||||
* 去除部分无用代码
|
||||
## 1.0.0(2024-07-10)
|
||||
* 第一次发布
|
||||
* 仿真翻页,支持翻起书角
|
||||
777
uni_modules/nx-turn/components/nx-turn/js/nx-turn.js
Normal file
777
uni_modules/nx-turn/components/nx-turn/js/nx-turn.js
Normal file
@@ -0,0 +1,777 @@
|
||||
const PI = Math.PI;
|
||||
/**
|
||||
* 90度角弧度值
|
||||
*/
|
||||
const A90 = PI / 2;
|
||||
|
||||
let pageNumber = 0;
|
||||
|
||||
let pageCount = -1;
|
||||
|
||||
let moving = false;
|
||||
|
||||
export const point2D = (x, y) => {
|
||||
return {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
}
|
||||
|
||||
export const setPageNumber = (number) => {
|
||||
pageNumber = number;
|
||||
}
|
||||
|
||||
export const setPageCount = (count) => {
|
||||
pageCount = count;
|
||||
}
|
||||
|
||||
let staticParam = {
|
||||
mode: 'half',
|
||||
page: {
|
||||
width: 0,
|
||||
height: 0
|
||||
},
|
||||
startPoint: point2D(0, 0),
|
||||
pointVerticalPosition: "c",
|
||||
moveHorizontalPosition: 'r',
|
||||
pageCorner: null
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
staticParam = {
|
||||
mode: staticParam.mode,
|
||||
page: staticParam.page,
|
||||
startPoint: point2D(0, 0),
|
||||
pointVerticalPosition: "c",
|
||||
moveHorizontalPosition: 'r',
|
||||
pageCorner: null
|
||||
}
|
||||
moving = false;
|
||||
ending = false;
|
||||
return {
|
||||
wrapper: {},
|
||||
content: {},
|
||||
bwrapper: {},
|
||||
bcontent: {},
|
||||
gradient: {},
|
||||
bgradient: {}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 触摸开始事件
|
||||
* @param page 包含页面的长和宽:{ width: w, height: h }
|
||||
* @param startPoint 起始触摸点:{ x: x, y: y }
|
||||
*/
|
||||
export const touchStartEvent = (page, startPoint) => {
|
||||
staticParam.page = page;
|
||||
staticParam.startPoint = startPoint;
|
||||
setVerticalPosition(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触摸移动事件
|
||||
* @param point 移动时触摸点:{ x: x, y: y }
|
||||
* @returns Translate样式
|
||||
*/
|
||||
export const touchMoveEvent = (point) => {
|
||||
getCornerPosition(point);
|
||||
let style = {};
|
||||
/*
|
||||
* 满足以下条件,样式进行变化
|
||||
*
|
||||
* 从右翻页(向下一页)并且存在下一页
|
||||
* pageCount == -1 表示 不限制向下翻页,即:永远存在下一页
|
||||
* pageNumber < pageCount - 1 表示 当前页不是最后一页
|
||||
* 或者
|
||||
* 从左翻页(向上一页)并且存在上一页
|
||||
* pageNumber > 0 表示 当前页不是第一页
|
||||
* moving 表示 正在进行翻页动作(非第一次触摸移动)
|
||||
*/
|
||||
if (
|
||||
(staticParam.moveHorizontalPosition == 'r' && (pageCount == -1 || pageNumber < pageCount - 1))
|
||||
|| ((staticParam.moveHorizontalPosition == 'l') && (pageNumber > 0 || moving))
|
||||
) {
|
||||
style = getTranslateStyle(point);
|
||||
if (staticParam.moveHorizontalPosition == 'l' && !moving) {
|
||||
// 向上一页翻并且第一次触摸移动,翻动页面应展示上一页的数据
|
||||
pageNumber--;
|
||||
}
|
||||
moving = true;
|
||||
}
|
||||
return {
|
||||
style: style,
|
||||
pageNumber: pageNumber
|
||||
};
|
||||
}
|
||||
let ending = false;
|
||||
/**
|
||||
* 触摸结束事件
|
||||
* @param point 结束时触摸点:{ x: x, y: y }
|
||||
* @returns Translate样式
|
||||
*/
|
||||
export const touchEndEvent = (point, cb, stop, clickCenter) => {
|
||||
if(ending) {
|
||||
return;
|
||||
}
|
||||
ending = true;
|
||||
let o;
|
||||
let endPosition;
|
||||
let turn = false;
|
||||
let isClick = false;
|
||||
// 取消从左翻页
|
||||
let isCancel4l = false;
|
||||
if (staticParam.pageCorner) {
|
||||
endPosition = getEndPointPosition(point);
|
||||
} else if (staticParam.mode == "half") {
|
||||
// staticParam.pageCorner不存在,说明触摸点未移动,即:点击
|
||||
isClick = true;
|
||||
// 点击水平位置
|
||||
staticParam.moveHorizontalPosition = getPointHorizontalPosition(point);
|
||||
if (staticParam.moveHorizontalPosition != "c") {
|
||||
// 点击右边向左翻页,反之
|
||||
endPosition = staticParam.moveHorizontalPosition == "r" ? "l" : "r";
|
||||
staticParam.pageCorner = "r";
|
||||
staticParam.pointVerticalPosition = "c";
|
||||
} else {
|
||||
// 点击中间
|
||||
reset();
|
||||
clickCenter && clickCenter();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 翻页方向(从哪个方向翻页)
|
||||
let pointHorizontalPosition = staticParam.moveHorizontalPosition;
|
||||
// 如果是展示半本书模式,且从左翻页,设定为从右翻页
|
||||
if (staticParam.mode == "half" && staticParam.moveHorizontalPosition == "l") {
|
||||
pointHorizontalPosition = "r";
|
||||
}
|
||||
|
||||
if (endPosition == pointHorizontalPosition) { // 触摸结束点位置与翻页方向相同
|
||||
// 获取页面角落的坐标
|
||||
o = getPageCornersCoordinates(staticParam.pageCorner, staticParam.page);
|
||||
if (staticParam.moveHorizontalPosition == "l") {
|
||||
if (!moving) {
|
||||
// 向上翻页并且是首页
|
||||
if (pageNumber == 0) {
|
||||
staticParam.pageCorner = null;
|
||||
if (stop) stop({
|
||||
...animationOverStyle(),
|
||||
isFirst: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
pageNumber--;
|
||||
}
|
||||
}
|
||||
} else { // 触摸结束点位置与翻页方向不同
|
||||
// 获取结束点(翻开书页的书角顶点最终的位置)
|
||||
o = getEndingPoint(staticParam.pageCorner, staticParam.page);
|
||||
if (staticParam.moveHorizontalPosition == "r") {
|
||||
if(pageCount != -1 && pageNumber >= pageCount - 1) {
|
||||
staticParam.pageCorner = null;
|
||||
if (stop) stop({
|
||||
...animationOverStyle(),
|
||||
isLast: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
turn = true;
|
||||
} else { // 取消翻页(从左翻页)
|
||||
isCancel4l = true;
|
||||
}
|
||||
}
|
||||
if (o) {
|
||||
const to = [o.x, o.y];
|
||||
createAnimation({
|
||||
from: [point.x, point.y],
|
||||
to: to,
|
||||
duration: 500,
|
||||
frame: (value) => {
|
||||
if (cb) cb({
|
||||
style: getTranslateStyle(point2D(value[0], value[1])),
|
||||
pageNumber: pageNumber
|
||||
});
|
||||
},
|
||||
complete: () => {
|
||||
if ((staticParam.moveHorizontalPosition == "r" && turn) || isCancel4l) {
|
||||
pageNumber++;
|
||||
}
|
||||
if (stop) stop(animationOverStyle());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const animationOverStyle = () => {
|
||||
return {
|
||||
style: reset(),
|
||||
pageNumber: pageNumber
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面对角线长度
|
||||
* @param page 包含页面的长和宽:{ width: w, height: h }
|
||||
*/
|
||||
export const getPageDiagonalLength = (page) => {
|
||||
return Math.sqrt(Math.pow(page.width, 2) + Math.pow(page.height, 2));
|
||||
}
|
||||
/**
|
||||
* 获取Translate样式
|
||||
* @param point 移动时触摸点:{ x: x, y: y }
|
||||
*/
|
||||
const getTranslateStyle = (point) => {
|
||||
let computeResult, a, tr, position, origin;
|
||||
/**
|
||||
* 结束点(翻开书页的书角顶点最终的位置)
|
||||
*/
|
||||
const endingPoint = getEndingPoint(staticParam.pageCorner, staticParam.page);
|
||||
switch (staticParam.pageCorner) {
|
||||
case 'l':
|
||||
return;
|
||||
case 'r':
|
||||
point.x = Math.max(Math.min(point.x, staticParam.page.width - 1), endingPoint.x);
|
||||
computeResult = compute(point);
|
||||
a = -radians2degrees(computeResult.alpha);
|
||||
tr = point2D(-computeResult.tr.x, computeResult.tr.y);
|
||||
position = [0, 0, 0, 1];
|
||||
origin = [0, 0];
|
||||
break;
|
||||
case 'tl':
|
||||
// point.x = Math.max(point.x, 1);
|
||||
return;
|
||||
case 'tr':
|
||||
point.x = Math.max(Math.min(point.x, staticParam.page.width - 1), endingPoint.x);
|
||||
computeResult = compute(point);
|
||||
a = -radians2degrees(computeResult.alpha);
|
||||
tr = point2D(-computeResult.tr.x, computeResult.tr.y);
|
||||
position = [0, 0, 0, 1];
|
||||
origin = [0, 0];
|
||||
break;
|
||||
case 'bl':
|
||||
// point.x = Math.max(point.x, 1);
|
||||
return;
|
||||
case 'br':
|
||||
point.x = Math.max(Math.min(point.x, staticParam.page.width - 1), endingPoint.x);
|
||||
computeResult = compute(point);
|
||||
a = radians2degrees(computeResult.alpha);
|
||||
tr = point2D(-computeResult.tr.x, -computeResult.tr.y);
|
||||
position = [0, 1, 1, 0]
|
||||
origin = [0, 100];
|
||||
break;
|
||||
}
|
||||
return transformStyle(a, tr, computeResult, position, origin);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置触摸在页面的上中下的位置,'t'、'c'、'b',分别代表上部、中部、下部。
|
||||
* @param page 包含页面的长和宽:{ width: w, height: h }
|
||||
* @param startPoint 起始触摸点:{ x: x, y: y }
|
||||
*/
|
||||
const setVerticalPosition = (page) => {
|
||||
if (page.height * 0.3 > staticParam.startPoint.y) {
|
||||
staticParam.pointVerticalPosition = 't';
|
||||
} else if (page.height * 0.7 > staticParam.startPoint.y) {
|
||||
staticParam.pointVerticalPosition = 'c';
|
||||
} else if (page.height > staticParam.startPoint.y) {
|
||||
staticParam.pointVerticalPosition = 'b';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置翻开页面页角的位置,'tl'、'tr'、'bl'、'br'、'l' 和 'r',分别代表左上角、右上角、左下角、右下角、左边和右边。
|
||||
* @param point 移动触摸点:{ x: x, y: y }
|
||||
*/
|
||||
const setCornerPosition = (point) => {
|
||||
if (staticParam.startPoint.x >= point.x) {
|
||||
staticParam.moveHorizontalPosition = 'r';
|
||||
} else {
|
||||
staticParam.moveHorizontalPosition = 'l';
|
||||
}
|
||||
if (staticParam.moveHorizontalPosition == 'l' && staticParam.mode == 'half') {
|
||||
staticParam.pageCorner = "r";
|
||||
staticParam.pointVerticalPosition = "c";
|
||||
} else if (staticParam.pointVerticalPosition == 'c') {
|
||||
staticParam.pageCorner = staticParam.moveHorizontalPosition;
|
||||
} else {
|
||||
staticParam.pageCorner = staticParam.pointVerticalPosition + staticParam.moveHorizontalPosition;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取翻开页面页角的位置,'tl'、'tr'、'bl'、'br'、'l' 和 'r',分别代表左上角、右上角、左下角、右下角、左边和右边。
|
||||
* @param point 移动触摸点:{ x: x, y: y }
|
||||
*/
|
||||
const getCornerPosition = (point) => {
|
||||
if (!staticParam.pageCorner) {
|
||||
setCornerPosition(point);
|
||||
}
|
||||
return staticParam.pageCorner;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取结束触摸点在页面的位置,'l'、'r',分别代表左边和右边。
|
||||
* @param point 结束触摸点:{ x: x, y: y }
|
||||
*/
|
||||
const getEndPointPosition = (point) => {
|
||||
let middle;
|
||||
if (staticParam.mode == 'half') {
|
||||
if (staticParam.moveHorizontalPosition == 'l') {
|
||||
middle = staticParam.page.width * 0.2
|
||||
} else {
|
||||
middle = staticParam.page.width * 0.8
|
||||
}
|
||||
}
|
||||
if (middle > point.x) {
|
||||
return 'l';
|
||||
} else {
|
||||
return 'r';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取触摸点在页面的位置,'l'、'c'、'r',分别代表左边,中间和右边。
|
||||
* @param point 触摸点:{ x: x, y: y }
|
||||
*/
|
||||
const getPointHorizontalPosition = (point) => {
|
||||
if (staticParam.page.width * 0.3 > point.x) {
|
||||
return 'l';
|
||||
}
|
||||
if (staticParam.page.width * 0.7 > point.x) {
|
||||
return 'c';
|
||||
} else {
|
||||
return 'r';
|
||||
}
|
||||
}
|
||||
|
||||
const compute = (point) => {
|
||||
const page = staticParam.page;
|
||||
const top = staticParam.pointVerticalPosition == 't';
|
||||
const center = staticParam.pointVerticalPosition == "c"
|
||||
const left = staticParam.mode != 'half' && staticParam.moveHorizontalPosition == 'l';
|
||||
const pageCorner = getCornerPosition(point);
|
||||
// 获取页面角落的坐标
|
||||
const o = getPageCornersCoordinates(pageCorner, page);
|
||||
const pageDiagonalLength = getPageDiagonalLength(page);
|
||||
// 触摸/鼠标点相对于页面角落的坐标
|
||||
const rel = point2D(0, 0);
|
||||
rel.x = (o.x) ? o.x - point.x : point.x;
|
||||
rel.y = (o.y) ? o.y - point.y : point.y;
|
||||
// 触摸/鼠标点与页面角落的中间点坐标
|
||||
const middle = point2D(0, 0);
|
||||
middle.x = (left) ? page.width - rel.x / 2 : point.x + rel.x / 2;
|
||||
middle.y = rel.y / 2;
|
||||
|
||||
/**
|
||||
* 计算触摸/鼠标点与y轴的夹角弧度值alpha(α)。
|
||||
* 其中,
|
||||
* A90是一个常量,表示90度的角度弧度值,
|
||||
* Math.atan2函数返回由两个参数(y和x)确定的点与x轴的夹角弧度值,范围为[-π, π]。
|
||||
*/
|
||||
const alpha = center ? A90 : A90 - Math.atan2(rel.y, rel.x);
|
||||
|
||||
/** ??
|
||||
* 计算弧度值gamma(γ),gamma的计算方式为alpha减去middle点与x轴的夹角弧度值(反正切值, (0,0)为原点)
|
||||
*/
|
||||
const gamma = alpha - Math.atan2(middle.y, middle.x);
|
||||
|
||||
// console.log("gamma:", gamma)
|
||||
|
||||
/** ??
|
||||
* 计算动态的距离,其中点middle的坐标为(x, y),gamma为一个角度值。具体实现过程如下:
|
||||
* 首先,根据middle点的坐标,计算出该点到原点(0,0)的距离,即sqrt(x^2 + y^2)。
|
||||
* 然后,根据gamma的正弦值,计算出一个缩放因子,即sin(gamma)。
|
||||
* 最后,将距离与缩放因子相乘,并取最大值为0,得到最终的距离值。
|
||||
* 作用是根据给定的角度和一个点的坐标,计算出一个距离值,该距离值表示了该点到原点的最远距离。
|
||||
*/
|
||||
const distance = Math.max(0, Math.sin(gamma) * Math.sqrt(Math.pow(middle.x, 2) + Math.pow(middle.y, 2)));
|
||||
|
||||
// console.log("distance:", distance)
|
||||
|
||||
|
||||
/** ??
|
||||
* 根据middle点到原点的距离和alpha角度计算并返回一个二维点。
|
||||
*/
|
||||
const tr = point2D(distance * Math.sin(alpha), distance * Math.cos(alpha));
|
||||
// console.log("tr:", tr)
|
||||
|
||||
if (alpha > A90) {
|
||||
tr.x = tr.x + Math.abs(tr.y * rel.y / rel.x);
|
||||
tr.y = 0;
|
||||
if (Math.round(tr.x * Math.tan(PI - alpha)) < page.height) {
|
||||
point.y = Math.sqrt(Math.pow(page.height, 2) + 2 * middle.x * rel.x);
|
||||
if (top) point.y = page.height - point.y;
|
||||
return compute(point);
|
||||
}
|
||||
}
|
||||
|
||||
const mv = point2D(0, 0);
|
||||
|
||||
if (alpha > A90) {
|
||||
const beta = PI - alpha;
|
||||
const dd = pageDiagonalLength - page.height / Math.sin(beta);
|
||||
mv.x = Math.round(dd * Math.cos(beta));
|
||||
mv.y = Math.round(dd * Math.sin(beta));
|
||||
if (left) mv.x = -mv.x;
|
||||
if (top) mv.y = -mv.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 中缝(订口边)与上边(从上翻)或下边(从下翻)(书顶或书底)未翻开的长度
|
||||
*/
|
||||
const px = Math.round(tr.y / Math.tan(alpha) + tr.x);
|
||||
|
||||
/**
|
||||
* 上边(从上翻)或下边(从下翻)(书顶或书底)翻开的长度
|
||||
*/
|
||||
const side = page.width - px;
|
||||
/**
|
||||
* 翻开书页的书角顶点与上边(从上翻)或下边(从下翻)(书顶或书底)的距离
|
||||
*/
|
||||
const sideX = side * Math.cos(alpha * 2);
|
||||
/**
|
||||
* 翻开书页的顶点与翻口的距离
|
||||
*/
|
||||
const sideY = side * Math.sin(alpha * 2);
|
||||
/**
|
||||
* 翻开书页的书角顶点与中缝(订口边)和上边(从下翻)或下边(从上翻)(书顶或书底)的相对距离
|
||||
*/
|
||||
const df = point2D(
|
||||
Math.round((left ? side - sideX : px + sideX)),
|
||||
Math.round(top ? sideY : center ? 0 : page.height - sideY)
|
||||
);
|
||||
/**
|
||||
* 翻开的斜边长度
|
||||
*/
|
||||
const gradientSize = side * Math.sin(alpha);
|
||||
/**
|
||||
* 结束点(翻开书页的书角顶点最终的位置)
|
||||
*/
|
||||
const endingPoint = getEndingPoint(pageCorner, page);
|
||||
|
||||
const far = Math.sqrt(Math.pow(endingPoint.x - point.x, 2) + (center ? 0 : Math.pow(endingPoint.y - point.y,
|
||||
2))) / page.width;
|
||||
/**
|
||||
* 翻开书角的阴影透明度
|
||||
*/
|
||||
const shadowVal = Math.sin(A90 * ((far > 1) ? 2 - far : far));
|
||||
/**
|
||||
* 翻开书角的斜边阴影透明度
|
||||
*/
|
||||
const gradientOpacity = Math.min(far, 1);
|
||||
/**
|
||||
* 翻开书角的斜边阴影颜色初始透明度
|
||||
*/
|
||||
const gradientStartVal = gradientSize > 100 ? (gradientSize - 100) / gradientSize : 0;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const gradientEndPointA = point2D(
|
||||
gradientSize * Math.sin(alpha) / page.width * 100,
|
||||
gradientSize * Math.cos(alpha) / page.height * 100);
|
||||
|
||||
if (center) gradientEndPointA.y = 100 - gradientEndPointA.y;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const gradientEndPointB = point2D(
|
||||
gradientSize * 1.2 * Math.sin(alpha) / page.width * 100,
|
||||
gradientSize * 1.2 * Math.cos(alpha) / page.height * 100);
|
||||
|
||||
if (!left) gradientEndPointB.x = 100 - gradientEndPointB.x;
|
||||
if (!top) gradientEndPointB.y = 100 - gradientEndPointB.y;
|
||||
|
||||
tr.x = Math.round(tr.x);
|
||||
tr.y = Math.round(tr.y);
|
||||
return {
|
||||
alpha: alpha,
|
||||
df: df,
|
||||
tr: tr,
|
||||
mv: mv,
|
||||
shadowVal: shadowVal,
|
||||
gradientOpacity: gradientOpacity,
|
||||
gradientStartVal: gradientStartVal,
|
||||
gradientEndPointA: gradientEndPointA,
|
||||
gradientEndPointB: gradientEndPointB
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 转换样式
|
||||
* @param page 包含页面的长和宽:{ width: w, height: h }
|
||||
* @param a 角度
|
||||
* @param tr
|
||||
* @param computeResult 计算结果
|
||||
* @param position 定位,数字数组:[left, top, right, bootom],取值(0,1)0:0,1:auto
|
||||
* @param origin 变换原点,数字数组:[x, y]
|
||||
*/
|
||||
const transformStyle = (a, tr, computeResult, position, origin) => {
|
||||
const top = staticParam.pointVerticalPosition == 't';
|
||||
const center = staticParam.pointVerticalPosition == "c"
|
||||
const left = staticParam.mode != 'half' && staticParam.moveHorizontalPosition == 'l';
|
||||
const page = staticParam.page;
|
||||
const {
|
||||
df,
|
||||
mv,
|
||||
shadowVal,
|
||||
gradientOpacity,
|
||||
gradientStartVal,
|
||||
gradientEndPointA,
|
||||
gradientEndPointB
|
||||
} = computeResult;
|
||||
// 获取页面对角线长度
|
||||
const pageDiagonalLength = getPageDiagonalLength(page);
|
||||
// 定位选项
|
||||
const positionOpt = ['0', 'auto'];
|
||||
const mvW = (page.width - pageDiagonalLength) * origin[0] / 100;
|
||||
const mvH = (page.height - pageDiagonalLength) * origin[1] / 100;
|
||||
const positionStyle = {
|
||||
left: positionOpt[position[0]],
|
||||
top: positionOpt[position[1]],
|
||||
right: positionOpt[position[2]],
|
||||
bottom: positionOpt[position[3]]
|
||||
};
|
||||
const aliasingFk = (a != 90 && a != -90) ? 1 : 0;
|
||||
const transformOrigin = origin[0] + '% ' + origin[1] + '%';
|
||||
|
||||
const style = {};
|
||||
|
||||
// 页面正面外框样式
|
||||
style.wrapper = {
|
||||
transform: translate(-tr.x + mvW - aliasingFk, -tr.y + mvH) + rotate(-a),
|
||||
transformOrigin: transformOrigin,
|
||||
};
|
||||
// 页面正面内容框样式
|
||||
style.content = {
|
||||
...positionStyle,
|
||||
transform: rotate(a) + translate(tr.x + aliasingFk, tr.y),
|
||||
transformOrigin: transformOrigin,
|
||||
};
|
||||
// 页面背面外框样式
|
||||
style.bwrapper = {
|
||||
transform: translate(-tr.x + mv.x + mvW, -tr.y + mv.y + mvH) + rotate(-a),
|
||||
transformOrigin: transformOrigin,
|
||||
};
|
||||
// 页面背面内容框样式
|
||||
style.bcontent = {
|
||||
...positionStyle,
|
||||
transform: rotate(a) +
|
||||
translate(tr.x + df.x - mv.x - page.width * origin[0] / 100, tr.y + df.y - mv.y - page.height *
|
||||
origin[1] /
|
||||
100) +
|
||||
rotate((180 / a - 2) * a),
|
||||
transformOrigin: transformOrigin,
|
||||
boxShadow: '0 0 20px rgba(0,0,0,' + Math.max(0.3, 0.5 * shadowVal) + ')'
|
||||
};
|
||||
|
||||
|
||||
if (origin[0])
|
||||
gradientEndPointA.x = 100 - gradientEndPointA.x;
|
||||
|
||||
if (origin[1])
|
||||
gradientEndPointA.y = 100 - gradientEndPointA.y;
|
||||
|
||||
// 翻开斜边样式
|
||||
style.gradient = gradientStyle(page,
|
||||
point2D(left ? 0 : 100, top ? 0 : 100),
|
||||
point2D(gradientEndPointB.x, gradientEndPointB.y),
|
||||
[
|
||||
[0.6, 'rgba(0,0,0,0)'],
|
||||
[0.8, 'rgba(0,0,0,' + (0.3 * gradientOpacity) + ')'],
|
||||
[1, 'rgba(0,0,0,0)']
|
||||
],
|
||||
3);
|
||||
|
||||
// 翻开背面斜边样式
|
||||
style.bgradient = gradientStyle(page,
|
||||
point2D(left ? 100 : 0, top ? 0 : 100),
|
||||
point2D(gradientEndPointA.x, gradientEndPointA.y),
|
||||
[
|
||||
[gradientStartVal, 'rgba(0,0,0,0)'],
|
||||
[((1 - gradientStartVal) * 0.8) + gradientStartVal, 'rgba(0,0,0,' + (0.2 * gradientOpacity) +
|
||||
')'
|
||||
],
|
||||
[1, 'rgba(255,255,255,' + (0.2 * gradientOpacity) + ')']
|
||||
],
|
||||
3);
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
const gradientStyle = (page, p0, p1, colors, numColors) => {
|
||||
|
||||
let j;
|
||||
const cols = [];
|
||||
|
||||
// for (j = 0; j < numColors; j++) {
|
||||
// cols.push('color-stop(' + colors[j][0] + ', ' + colors[j][1] + ')');
|
||||
// }
|
||||
// return {
|
||||
// backgroundImage: '-webkit-gradient(linear, ' +
|
||||
// p0.x + '% ' +
|
||||
// p0.y + '%,' +
|
||||
// p1.x + '% ' +
|
||||
// p1.y + '%, ' +
|
||||
// cols.join(',') + ' )'
|
||||
// }
|
||||
p0 = {
|
||||
x: p0.x / 100 * page.width,
|
||||
y: p0.y / 100 * page.height
|
||||
};
|
||||
p1 = {
|
||||
x: p1.x / 100 * page.width,
|
||||
y: p1.y / 100 * page.height
|
||||
};
|
||||
|
||||
const dx = p1.x - p0.x;
|
||||
const dy = p1.y - p0.y;
|
||||
const angle = Math.atan2(dy, dx);
|
||||
const angle2 = angle - Math.PI / 2;
|
||||
const diagonal = Math.abs(page.width * Math.sin(angle2)) + Math.abs(page.height * Math.cos(angle2));
|
||||
const gradientDiagonal = Math.sqrt(Math.pow(dy, 2) + Math.pow(dx, 2));
|
||||
const corner = point2D((p1.x < p0.x) ? page.width : 0, (p1.y <= p0.y) ? page.height : 0);
|
||||
const slope = Math.tan(angle);
|
||||
const inverse = slope == 0 ? 0 : -1 / slope;
|
||||
const x = inverse - slope == 0 ? 0 : (inverse * corner.x - corner.y - slope * p0.x + p0.y) / (inverse - slope);
|
||||
const c = {
|
||||
x: x,
|
||||
y: inverse * x - inverse * corner.x + corner.y
|
||||
};
|
||||
const segA = (Math.sqrt(Math.pow(c.x - p0.x, 2) + Math.pow(c.y - p0.y, 2)));
|
||||
for (j = 0; j < numColors; j++) {
|
||||
cols.push(' ' + colors[j][1] + ' ' + ((segA + gradientDiagonal * colors[j][0]) * 100 / diagonal) + '%');
|
||||
}
|
||||
return {
|
||||
backgroundImage: 'linear-gradient(' + (Math.PI / 2 + angle) + 'rad,' + cols.join(',') + ')'
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 将角度从弧度转换为度
|
||||
* @param radians
|
||||
*/
|
||||
const radians2degrees = (radians) => {
|
||||
return radians / PI * 180;
|
||||
}
|
||||
/**
|
||||
* 返回CSS旋转值
|
||||
*/
|
||||
const rotate = (degrees) => {
|
||||
return ' rotate(' + degrees + 'deg) ';
|
||||
}
|
||||
/**
|
||||
* 返回CSS变换值
|
||||
*/
|
||||
const translate = (x, y) => {
|
||||
return ' translate(' + x + 'px, ' + y + 'px) ';
|
||||
}
|
||||
/**
|
||||
* 获取页面角落坐标
|
||||
* @param corner 角落位置,'tl'、'tr'、'bl'、'br'、'l' 和 'r',分别代表左上角、右上角、左下角、右下角、左边和右边。
|
||||
* @param page 包含页面的长和宽:{ width: w, height: h }
|
||||
* @param opts 偏移量
|
||||
*/
|
||||
const getPageCornersCoordinates = (corner, page, opts) => {
|
||||
opts = opts || 0;
|
||||
switch (corner) {
|
||||
case 'tl': // 左上角
|
||||
return point2D(opts, opts);
|
||||
case 'tr': // 右上角
|
||||
return point2D(page.width - opts, opts);
|
||||
case 'bl': // 左下角
|
||||
return point2D(opts, page.height - opts);
|
||||
case 'br': // 右下角
|
||||
return point2D(page.width - opts, page.height - opts);
|
||||
case 'l': // 左边
|
||||
return point2D(opts, 0);
|
||||
case 'r': // 右边
|
||||
return point2D(page.width - opts, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取结束点(翻开书页的书角顶点最终的位置)
|
||||
* @param corner 角落位置,'tl'、'tr'、'bl'、'br'、'l' 和 'r',分别代表左上角、右上角、左下角、右下角、左边和右边。
|
||||
* @param page 包含页面的长和宽:{ width: w, height: h }
|
||||
*/
|
||||
const getEndingPoint = (corner, page) => {
|
||||
|
||||
switch (corner) {
|
||||
case 'tl':
|
||||
return point2D(page.width * 2, 0);
|
||||
case 'tr':
|
||||
return point2D(-page.width, 0);
|
||||
case 'bl':
|
||||
return point2D(page.width * 2, page.height);
|
||||
case 'br':
|
||||
return point2D(-page.width, page.height);
|
||||
case 'l':
|
||||
if (staticParam.mode == 'half') {
|
||||
return point2D(page.width, 0);
|
||||
} else {
|
||||
return point2D(page.width * 2, 0);
|
||||
}
|
||||
case 'r':
|
||||
return point2D(-page.width, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const requestAnimation = (callback) => {
|
||||
let windowRequestAnimationFrame;
|
||||
if (window) {
|
||||
windowRequestAnimationFrame = window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame;
|
||||
}
|
||||
if (windowRequestAnimationFrame) {
|
||||
windowRequestAnimationFrame(callback);
|
||||
} else {
|
||||
setTimeout(callback, 1000 / 60);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动画
|
||||
*/
|
||||
const createAnimation = (param) => {
|
||||
if (param) {
|
||||
// 动画开始时间
|
||||
const startTime = new Date().getTime();
|
||||
param.duration = param.duration || 0;
|
||||
param.count = 0;
|
||||
animationFun(startTime, param);
|
||||
}
|
||||
}
|
||||
|
||||
const animationFun = (startTime, param) => {
|
||||
// 动画已执行时间
|
||||
let timeDiff = Math.min(param.duration, (new Date()).getTime() - startTime);
|
||||
// 变化值
|
||||
const transformValues = [];
|
||||
if (param.from && param.to) {
|
||||
if (!param.from.length) param.from = [param.from];
|
||||
if (!param.to.length) param.to = [param.to];
|
||||
// 最小变换参数长度
|
||||
const valueLength = Math.min(param.from.length, param.to.length);
|
||||
// 计算当前执行的参数值
|
||||
for (let i = 0; i < valueLength; i++) {
|
||||
transformValues.push(easing(timeDiff, param.from[i], param.to[i], param.duration));
|
||||
}
|
||||
}
|
||||
if (param.frame) param.frame((transformValues.length == 1) ? transformValues[0] : transformValues);
|
||||
// 判断动画是否结束
|
||||
if (timeDiff == param.duration) {
|
||||
if (param.complete) param.complete();
|
||||
} else {
|
||||
requestAnimation(() => {
|
||||
animationFun(startTime, param);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const easing = (timeDiff, from, to, duration) => {
|
||||
const t = timeDiff / duration - 1;
|
||||
return (to - from) * Math.sqrt(1 - Math.pow(t, 2)) + from;
|
||||
}
|
||||
262
uni_modules/nx-turn/components/nx-turn/nx-turn.vue
Normal file
262
uni_modules/nx-turn/components/nx-turn/nx-turn.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<view class="nx-turn">
|
||||
<view
|
||||
class="nx-turn-page-wrapper turn"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
@touchcancel="touchCancel"
|
||||
>
|
||||
<view
|
||||
class="nx-turn-page-content-wrapper"
|
||||
:style="[{
|
||||
width: `${pageContentWrapperWidth}px`,
|
||||
height: `${pageContentWrapperWidth}px`
|
||||
}, transformStyle.wrapper]"
|
||||
>
|
||||
<view
|
||||
class="nx-turn-page-content-box"
|
||||
:class="customClass"
|
||||
:style="[{
|
||||
width: `${pageWrapperInfo.width}px`,
|
||||
height: `${pageWrapperInfo.height}px`
|
||||
}, customStyle, transformStyle.content]"
|
||||
>
|
||||
<slot name="page-content" :page="page"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="nx-turn-page-content-wrapper nx-turn-page-content-wrapper-b"
|
||||
:style="[{
|
||||
width: `${pageContentWrapperWidth}px`,
|
||||
height: `${pageContentWrapperWidth}px`
|
||||
}, transformStyle.bwrapper]"
|
||||
>
|
||||
<view
|
||||
class="nx-turn-page-content-box nx-turn-page-content-b-box"
|
||||
:class="customClass"
|
||||
:style="[{
|
||||
width: `${pageWrapperInfo.width}px`,
|
||||
height: `${pageWrapperInfo.height}px`
|
||||
}, customStyle, transformStyle.bcontent]"
|
||||
>
|
||||
<view
|
||||
class="nx-turn-page-content-wrapper-b-shadow"
|
||||
:style="[{
|
||||
width: `${pageWrapperInfo.width}px`,
|
||||
height: `${pageWrapperInfo.height}px`
|
||||
}, transformStyle.bgradient]"
|
||||
></view>
|
||||
<view class="nx-turn-page-content-b"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
:style="[{
|
||||
width: `${pageWrapperInfo.width}px`,
|
||||
height: `${pageWrapperInfo.height}px`
|
||||
}, transformStyle.gradient]"
|
||||
></view>
|
||||
</view>
|
||||
<view
|
||||
class="nx-turn-page-wrapper next"
|
||||
:class="customClass"
|
||||
:style="customStyle"
|
||||
>
|
||||
<slot name="next-page-content" :page="page + 1"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getPageDiagonalLength,
|
||||
point2D,
|
||||
touchStartEvent,
|
||||
touchMoveEvent,
|
||||
touchEndEvent,
|
||||
setPageNumber,
|
||||
setPageCount
|
||||
} from './js/nx-turn.js';
|
||||
export default {
|
||||
name:"nx-turn",
|
||||
props: {
|
||||
/**
|
||||
* 初始页码,从0开始
|
||||
*/
|
||||
initPage: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 页数
|
||||
*/
|
||||
pageCount: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
customClass: {
|
||||
type: String,
|
||||
default: 'nx-turn-theme'
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: () => { return {} }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 起始触摸点X坐标
|
||||
touchStartX: 0,
|
||||
// 起始触摸点Y坐标
|
||||
touchStartY: 0,
|
||||
// 书页内容包装外框宽度(等于书页对角线长度)
|
||||
pageContentWrapperWidth: 0,
|
||||
// 书页包装外框信息
|
||||
pageWrapperInfo: { width: 0, height: 0 },
|
||||
// 变换样式
|
||||
transformStyle: {},
|
||||
// 当前页码
|
||||
page: 0,
|
||||
mouseIsDown: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.setPage();
|
||||
setPageCount(this.pageCount);
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.init();
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
initPage() {
|
||||
this.setPage();
|
||||
},
|
||||
pageCount(newValue) {
|
||||
setPageCount(newValue);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query
|
||||
.select('.nx-turn-page-wrapper.turn')
|
||||
.boundingClientRect(async (data) => {
|
||||
this.pageWrapperInfo = data;
|
||||
this.pageContentWrapperWidth = getPageDiagonalLength(data);
|
||||
this.$emit('init-completed', this.pageWrapperInfo);
|
||||
})
|
||||
.exec();
|
||||
},
|
||||
setPage() {
|
||||
this.page = this.initPage;
|
||||
setPageNumber(this.page);
|
||||
},
|
||||
touchStart(e) {
|
||||
this.touchStartX = e.touches[0].clientX;
|
||||
this.touchStartY = e.touches[0].clientY;
|
||||
touchStartEvent(this.pageWrapperInfo, point2D(this.touchStartX, this.touchStartY));
|
||||
},
|
||||
mouseDown(e) {
|
||||
this.mouseIsDown = true;
|
||||
this.touchStart(e);
|
||||
},
|
||||
touchMove(e) {
|
||||
// this.closeMenu();
|
||||
const point = e.touches[0];
|
||||
const { style, pageNumber } = touchMoveEvent(point2D(point.clientX, point.clientY));
|
||||
this.transformStyle = style;
|
||||
this.page = pageNumber;
|
||||
this.$emit('turning');
|
||||
},
|
||||
mouseMove(e) {
|
||||
if(this.mouseIsDown) {
|
||||
this.touchMove(e);
|
||||
}
|
||||
},
|
||||
touchEnd(e) {
|
||||
e.preventDefault();
|
||||
const point = e.changedTouches[0];
|
||||
this.handleTouchEnd(point2D(point.clientX, point.clientY));
|
||||
},
|
||||
mouseUp(e) {
|
||||
this.mouseIsDown = false;
|
||||
this.touchEnd(e);
|
||||
},
|
||||
touchCancel(e) {
|
||||
this.handleTouchEnd(point2D(this.touchStartX, this.touchStartY));
|
||||
},
|
||||
handleTouchEnd(point) {
|
||||
touchEndEvent(
|
||||
point,
|
||||
({ style, pageNumber }) => {
|
||||
// this.closeMenu();
|
||||
this.transformStyle = style;
|
||||
this.page = pageNumber;
|
||||
this.$emit('turning');
|
||||
},
|
||||
({ style, pageNumber, isFirst, isLast }) => {
|
||||
this.transformStyle = style;
|
||||
this.page = pageNumber;
|
||||
this.$emit('turned', { pageNumber, isFirst, isLast });
|
||||
},
|
||||
() => {
|
||||
this.$emit('click-center');
|
||||
}
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.nx-turn {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&-page {
|
||||
&-wrapper {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
perspective: 1000px;
|
||||
&.turn {
|
||||
z-index: 20;
|
||||
}
|
||||
&.next {
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
&-content {
|
||||
height: 100%;
|
||||
&-wrapper {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
overflow: hidden;
|
||||
z-index: auto;
|
||||
transform-origin: 0% 100%;
|
||||
&-b {
|
||||
transform: translate(-100%, 0);
|
||||
&-shadow {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-box {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-theme {
|
||||
background-color: #EBD6B1;
|
||||
color: mix(#000000, #EBD6B1, 70%);
|
||||
border-color: mix(#FFFFFF, #EBD6B1, 70%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
84
uni_modules/nx-turn/package.json
Normal file
84
uni_modules/nx-turn/package.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"id": "nx-turn",
|
||||
"displayName": "楠昕仿真翻页",
|
||||
"version": "1.0.3",
|
||||
"description": "楠昕仿真翻页",
|
||||
"keywords": [
|
||||
"翻页",
|
||||
"仿真翻页"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "component-vue",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "u",
|
||||
"app-uvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "u",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
151
uni_modules/nx-turn/readme.md
Normal file
151
uni_modules/nx-turn/readme.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# nx-turn
|
||||
#使用须知
|
||||
|
||||
* 1、这是一个翻页组件,适用于小说翻页功能
|
||||
* 2、这个插件支持APP-VUE、H5、微信小程序
|
||||
|
||||
#props属性
|
||||
| 属性名称 | 类型 | 默认值 | 可选值 | 说明 |
|
||||
| :----- | :----: | :----: | :----: | :---- |
|
||||
| initPage | Number | 0 | - | 初始页码,从0开始 |
|
||||
| pageCount | Number | -1 | - | 总页数。-1时无限向下翻页 |
|
||||
| customClass | String | nx-turn-theme | - | 自定义class |
|
||||
| customStyle | Object | - | - | 自定义style |
|
||||
|
||||
#event事件
|
||||
| 事件名称 | 参数 | 说明 |
|
||||
| :----- | :----: | :---- |
|
||||
| init-completed | pageWrapperInfo: 书页节点信息 | 组件初始化完成事件 |
|
||||
| turning | ---- | 书页正在翻动事件 |
|
||||
| turned | info = { pageNumber, isFirst, isLast },pageNumber: 当前页(从0开始),isFirst: 是否是第一页,isLast: 是否是最后一页 | 书页翻动完成事件 |
|
||||
| click-center | ---- | 点击书页中部事件 |
|
||||
|
||||
#slot插槽
|
||||
| 插槽名称 | 参数 | 说明 |
|
||||
| :----- | :----: | :---- |
|
||||
| page-content | page: 当前页页码(从0开始) | 当前页/翻动页插槽,传入自定义页面内容 |
|
||||
| next-page-content | page: 下一页页码(从1开始) | 下一页插槽,传入自定义页面内容,一般和当前页一致 |
|
||||
|
||||
#快速开始
|
||||
```html
|
||||
<view class="content">
|
||||
<nx-turn>
|
||||
<template v-slot:page-content="{ page }">
|
||||
<text>{{ page }}</text>
|
||||
</template>
|
||||
<template v-slot:next-page-content="{ page }">
|
||||
<text>{{ page }}</text>
|
||||
</template>
|
||||
</nx-turn>
|
||||
</view>
|
||||
```
|
||||
```css
|
||||
page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
#完整示例
|
||||
```html
|
||||
<view class="content">
|
||||
<nx-turn
|
||||
:initPage="page"
|
||||
:pageCount="pageContent.length"
|
||||
custom-class="theme-blue"
|
||||
@init-completed="initCompleted"
|
||||
@turning="handleTurning"
|
||||
@turned="handleTurned"
|
||||
@click-center="handleClickCenter"
|
||||
>
|
||||
<template v-slot:page-content="{ page }">
|
||||
<view class="page-content">
|
||||
<text>{{ pageContent[page] }}</text>
|
||||
</view>
|
||||
</template>
|
||||
<template v-slot:next-page-content="{ page }">
|
||||
<view class="page-content">
|
||||
<text>{{ pageContent[page] }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</nx-turn>
|
||||
</view>
|
||||
```
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 当前页码
|
||||
page: 0,
|
||||
// 分页数据
|
||||
pageContent: [
|
||||
"使用须知\n1. 这是一个翻页组件,适用于小说翻页功能\n2. 这个插件支持APP-VUE、H5、微信小程序",
|
||||
"1.0.0(2024-07-10)\n第一次发布\n仿真翻页,支持翻起书角"
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
initCompleted(pageWrapperInfo) {
|
||||
console.log('页面节点信息:', pageWrapperInfo);
|
||||
},
|
||||
handleTurning() {
|
||||
uni.showToast({
|
||||
title: '翻页中',
|
||||
duration: 100,
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
handleTurned(info) {
|
||||
console.log('当前页面信息:', info);
|
||||
if (info.isFirst) {
|
||||
uni.showToast({
|
||||
title: '已经是第一页了',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
if (info.isLast) {
|
||||
uni.showToast({
|
||||
title: '已经是最后一页了',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
handleClickCenter() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '点击中部',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```css
|
||||
page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
/* #ifdef VUE3 */
|
||||
::v-deep
|
||||
/* #endif */
|
||||
/* #ifdef VUE2 */
|
||||
/deep/
|
||||
/* #endif */
|
||||
.theme-blue {
|
||||
background-color: #DCE2F1;
|
||||
color: mix(#000000, #DCE2F1, 50%);
|
||||
border-color: mix(#FFFFFF, #DCE2F1, 70%);
|
||||
}
|
||||
.page-content {
|
||||
padding: 15px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user