فهرست منبع

```
feat: 添加课程目录页面并重构API服务

- 将API模块重命名为services模块,统一服务层调用
- 新增pages/catalog/index.uvue课程目录页面
- 更新代理配置中的服务器地址
- 修改字典刷新逻辑,添加用户token验证
- 优化首页导航至新目录页面
- 修复进度组件样式问题

BREAKING CHANGE: API路径从/api改为/services
```

408249787 1 هفته پیش
والد
کامیت
532d893b99

+ 4 - 2
.cool/store/dict.ts

@@ -1,6 +1,6 @@
 import { reactive } from "vue";
 import { storage, isNull, parse } from "../utils";
-import { type DICT_DATA, dictData } from "@/api/user";
+import { type DICT_DATA, dictData } from "@/services/user";
 import { user } from "./user";
 
 
@@ -74,8 +74,10 @@ export class Dict {
 	 * @param types 可选,指定需要刷新的字典key数组
 	 */
 	async refresh(): Promise<void> {
+		if (!user.token) {
+			return;
+		}
 		const res = await dictData();
-
 		if (res == null) {
 			return;
 		}

+ 1 - 1
.cool/store/user.ts

@@ -3,7 +3,7 @@ import { forInObject, isNull, isObject, parse, storage } from "../utils";
 import { router } from "../router";
 import { request } from "../service";
 import type { UserInfo } from "../types";
-import { type LoginData, getUsersInfo } from "@/api/user";
+import { type LoginData, getUsersInfo } from "@/services/user";
 import { dict } from "./dict";
 export type Token = {
 	token: string; // 访问token

+ 4 - 4
config/proxy.ts

@@ -2,18 +2,18 @@ export const proxy = {
 	// 开发环境配置
 	dev: {
 		// 本地地址
-		target: "http://192.168.110.161:5001",
+		target: "http://192.168.1.11:5001",
 		changeOrigin: true,
 		rewrite: (path: string) => path.replace("/dev", "")
 	},
-	file: {
+	files: {
 		// 本地地址
-		target: "http://192.168.110.161:5001",
+		target: "http://192.168.1.11:5001",
 		changeOrigin: true,
 	},
 	api: {
 		// 本地地址
-		target: "http://192.168.110.161:5001",
+		target: "http://192.168.1.11:5001",
 		changeOrigin: true,
 		rewrite: (path: string) => path.replace("/api", "")
 	},

+ 7 - 0
pages.json

@@ -12,6 +12,13 @@
 				"navigationStyle": "custom",
 				"disableScroll": true
 			}
+		},
+		{
+			"path": "pages/catalog/index",
+			"style": {
+				"navigationStyle": "custom",
+				"disableScroll": true
+			}
 		}
 	],
 	"globalStyle": {

+ 27 - 23
pages/index/components/progress.uvue → pages/catalog/components/progress.uvue

@@ -1,30 +1,29 @@
 <template>
 	<view class="progress-container">
 		<view class="progress-bar" v-for="i in num" :key="i" :class="{
-      'progress-select':percentage>=i
-    }">
+			'progress-select': percentage >= i
+		}">
 			<view class="progress-fill"></view>
-			<view class="progress-thumb" v-if="i!==num"></view>
+			<view class="progress-thumb" v-if="i !== num"></view>
 		</view>
 	</view>
 </template>
 
 <script lang="ts" setup>
-import { ref, defineProps } from 'vue';
 
 const props = defineProps({
 	percentage: {
 		type: Number,
 		default: 2
 	},
-  num:{
-    type:Number,
-    default:4
-  },
-  size:{
-    type:String,
-    default:'15px'
-  }
+	num: {
+		type: Number,
+		default: 4
+	},
+	size: {
+		type: String,
+		default: '15px'
+	}
 });
 </script>
 
@@ -32,29 +31,34 @@ const props = defineProps({
 .progress-container {
 	width: 100%;
 	padding: 10rpx 0;
-  @apply flex flex-row;
+	@apply flex flex-row;
+
 	.progress-bar {
-  	@apply flex flex-row items-center;
+		@apply flex flex-row items-center;
+
 		.progress-fill {
-     	@apply  rounded-full ;
+			@apply rounded-full;
 			background-color: #E4F0FF;
-      width:v-bind(size);
-      height:v-bind(size);
+			width: v-bind(size);
+			height: v-bind(size);
 		}
+
 		.progress-thumb {
-  	@apply  rounded-full ;
-       width:v-bind(size);
-      height:calc(v-bind(size) / 5);
+			@apply rounded-full;
+			width: v-bind(size);
+			height: calc(v-bind(size) / 5);
 			background-color: #E4F0FF;
 		}
 	}
-  .progress-select{
-    .progress-fill {
+
+	.progress-select {
+		.progress-fill {
 			background-color: #194DCF;
 		}
+
 		.progress-thumb {
 			background-color: #194DCF;
 		}
-  }
+	}
 }
 </style>

+ 143 - 0
pages/catalog/index.uvue

@@ -0,0 +1,143 @@
+<template>
+  <cl-page>
+    <Back />
+    <img src="/static/home/2.png" alt="" class="w-full h-full object-cover">
+    <!-- 精灵图动画 -->
+    <cl-image src="/static/home/3.gif" mode="heightFix" class="!absolute bottom-0 left-0 !w-[44vh] !h-[55vh] z-[1]" />
+    <view>
+
+    </view>
+    <!-- 顶部右侧光标签 -->
+    <view class="light-tag" @tap="visible = true">
+      <image class="light-icon" :src="catalog?.fileList?.[0]?.url"></image>
+      <text class="light-text">{{ catalog?.name }}</text>
+      <cl-icon name="arrow-left-right-line" color="primary"></cl-icon>
+    </view>
+    <view class="boxs">
+      <scroll-view class="scroll-view_H" direction="horizontal" :scroll-left="120" :show-scrollbar="false">
+        <view class="scroll-view-item_H bg-[white]" v-for="course in catalog?.courseList || []" :key="course.id">
+          <cl-image :src="course?.fileList?.[0]?.url" mode="heightFix"
+            class="!w-full !h-[26vh] mb-[2px] rounded-xl"></cl-image>
+          <text class="text-[16px] font-bold">{{
+            course.mainTitle }}</text>
+          <text class="text-[14px] text-[#666]">{{
+            course.assistantTitle }}</text>
+          <view>
+            <Progress :progress="30" />
+          </view>
+        </view>
+      </scroll-view>
+    </view>
+    <view class="footer">
+      <view>
+        <cl-image src="/static/home/4.png" mode="heightFix" class=" !h-[40px] mb-[2px] rounded-xl"></cl-image>
+        <text class="text-[14px] text-white font-bold text-stroke-custom">虚拟实验</text>
+      </view>
+      <view>
+        <cl-image src="/static/home/5.png" mode="heightFix" class=" !h-[40px] mb-[2px] rounded-xl"></cl-image>
+        <text class="text-[14px] text-white font-bold text-stroke-custom">我的收获</text>
+      </view>
+      <view>
+        <cl-image src="/static/home/6.png" mode="heightFix" class=" !h-[40px] mb-[2px] rounded-xl"></cl-image>
+        <text class="text-[14px] text-white font-bold text-stroke-custom">学习报告</text>
+      </view>
+    </view>
+    <cl-popup v-model="visible" :show-header="false" direction="center" :size="500">
+      <view class="p-4">
+        <cl-row :gutter="0">
+          <cl-col :span="6" v-for="item in dataList" :key="item.id" :pt="{
+            className: '!p-2'
+          }" @tap="handleSelect(item)">
+            <view class="select-item" :class="{ selected: item.id === catalog?.id }">
+              <image :src="item?.fileList?.[0]?.url" class="w-[30rpx] h-[30rpx] mb-[2px]"></image>
+              <text>{{ item.name }}</text>
+            </view>
+          </cl-col>
+        </cl-row>
+      </view>
+    </cl-popup>
+  </cl-page>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted } from 'vue'
+import { fetchSubjectConfigInfo } from '@/services/subject/info'
+import type { SubjectCatalogResult } from '@/services/subject/catalog'
+import Progress from './components/progress.uvue'
+import Back from '@/components/back.uvue'
+
+
+const visible = ref<boolean>(false)
+const dataList = ref<SubjectCatalogResult[]>([])
+const catalog = ref<SubjectCatalogResult>()
+async function getDataList() {
+  const res = await fetchSubjectConfigInfo({ id: '69afc7e048070409048c06b6' })
+  dataList.value = res.catalogList || []
+  catalog.value = res?.catalogList?.[0]
+}
+onMounted(() => {
+  getDataList()
+})
+function handleSelect(item: SubjectCatalogResult) {
+  catalog.value = item
+  visible.value = false
+}
+</script>
+
+<style lang="scss" scoped>
+.boxs {
+  @apply w-[100vw] h-[50vh] absolute top-1/2 left-[50vh] z-[1];
+  transform: translateY(-50%);
+}
+
+.scroll-view_H {
+  width: 100%;
+  flex-direction: row;
+}
+
+.scroll-view-item_H {
+  @apply w-[40vh] h-[50vh] mr-[20px] rounded-2xl border-[5px] border-[#1D4BD9] border-solid border-b-[10px] p-1 flex items-center;
+}
+
+.light-tag {
+  @apply absolute top-3 left-1/2 z-[1] flex flex-row items-center bg-white px-3 py-2 font-bold rounded-full shadow-md;
+  transform: translateX(-50%);
+
+  .light-icon {
+    width: 20px;
+    height: 20px;
+    margin-right: 3px;
+  }
+
+  .light-text {
+    font-size: 16px;
+    width: 70px;
+  }
+}
+
+.select-item {
+  @apply flex items-center justify-center rounded-xl border-[3px] border-[#1D4BD9] border-solid border-b-[5px] px-4 py-2 font-bold;
+}
+
+.selected {
+  @apply border-green-500;
+}
+
+.footer {
+  @apply absolute bottom-2 right-5 z-[1] flex flex-row items-center justify-center gap-4;
+}
+
+.text-stroke-custom {
+  color: white;
+  text-shadow:
+    /* 左上角投影 */
+    -1px -1px 0 #1D4BD9,
+    /* 右上角投影 */
+    1px -1px 0 #1D4BD9,
+    /* 左下角投影 */
+    -1px 1px 0 #1D4BD9,
+    /* 右下角投影 */
+    1px 1px 0 #1D4BD9;
+
+}
+</style>

+ 9 - 125
pages/index/home.uvue

@@ -1,131 +1,15 @@
-<template>
-	<cl-page>
-		<img src="/static/home/2.png" alt="" class="w-full h-full object-cover">
-		<!-- 精灵图动画 -->
-		<cl-image src="/static/home/3.gif" mode="heightFix" class="!absolute bottom-0 left-0 !w-[44vh] !h-[55vh] z-[1]" />
-		<!-- <view class="sprite-animation"></view> -->
-		<view>
-
-		</view>
-		<cl-button @tap="handleLogin" class="!absolute top-10 left-10  z-[1]">登录</cl-button>
-		<!-- 顶部右侧光标签 -->
-		<view class="light-tag" @tap="visible = true">
-			<image class="light-icon" :src="catalog?.fileList?.[0]?.url"></image>
-			<text class="light-text">{{ catalog?.name }}</text>
-			<cl-icon name="arrow-left-right-line" color="primary"></cl-icon>
-		</view>
-		<view class="boxs">
-			<scroll-view class="scroll-view_H" direction="horizontal" :scroll-left="120" :show-scrollbar="false">
-				<view class="scroll-view-item_H bg-[white]" v-for="course in catalog?.courseList || []" :key="course.id">
-					<cl-image :src="course?.fileList?.[0]?.url" mode="heightFix"
-						class="!w-full !h-[26vh] mb-[2px] rounded-xl"></cl-image>
-					<text class="text-[16px] font-bold">{{
-						course.mainTitle }}</text>
-					<text class="text-[14px] text-[#666]">{{
-						course.assistantTitle }}</text>
-					<view>
-						<Progress :progress="30" />
-					</view>
-				</view>
-			</scroll-view>
-		</view>
-		<cl-popup v-model="visible" :show-header="false" direction="center" :size="500">
-			<view class="p-4">
-				<cl-row :gutter="0">
-					<cl-col :span="6" v-for="item in dataList" :key="item.id" :pt="{
-						className: '!p-2'
-					}" @tap="handleSelect(item)">
-						<view class="select-item" :class="{ selected: item.id === catalog?.id }">
-							<image :src="item?.fileList?.[0]?.url" class="w-[30rpx] h-[30rpx] mb-[2px]"></image>
-							<text>{{ item.name }}</text>
-						</view>
-					</cl-col>
-				</cl-row>
-			</view>
-		</cl-popup>
-	</cl-page>
-</template>
-
 <script lang="ts" setup>
-import { ref, onMounted } from 'vue'
-import { fetchSubjectConfigInfo } from '@/api/subject/info'
-import type { SubjectCatalogResult } from '@/api/subject/catalog'
-import Progress from './components/progress.uvue'
-
-function handleLogin() {
+function handleLogin(url) {
 	uni.navigateTo({
-		url: '/pages/user/login'
+		url
 	})
 }
-const visible = ref<boolean>(false)
-const dataList = ref<SubjectCatalogResult[]>([])
-const catalog = ref<SubjectCatalogResult>()
-async function getDataList() {
-	const res = await fetchSubjectConfigInfo({ id: '69afc7e048070409048c06b6' })
-	dataList.value = res.catalogList || []
-	catalog.value = res?.catalogList?.[0]
-}
-onMounted(() => {
-	getDataList()
-})
-function handleSelect(item: SubjectCatalogResult) {
-	catalog.value = item
-	visible.value = false
-}
 </script>
+<template>
+	<cl-page>
+		<cl-button @tap="handleLogin('/pages/user/login')">登录</cl-button>
 
-<style lang="scss" scoped>
-.boxs {
-	@apply w-[100vw] h-[50vh] absolute top-1/2 left-[50vh] z-[1];
-	transform: translateY(-50%);
-}
-
-.scroll-view_H {
-	width: 100%;
-	flex-direction: row;
-}
-
-.scroll-view-item_H {
-	@apply w-[40vh] h-[50vh] mr-[20px] rounded-2xl border-[5px] border-[#1D4BD9] border-solid border-b-[10px] p-1 flex items-center;
-}
-
-.light-tag {
-	@apply absolute top-5 right-5 z-[1] flex flex-row items-center bg-white px-3 py-2 font-bold rounded-full shadow-md;
-
-	.light-icon {
-		width: 20px;
-		height: 20px;
-		margin-right: 3px;
-	}
-
-	.light-text {
-		font-size: 16px;
-		width: 70px;
-	}
-}
-
-.select-item {
-	@apply flex items-center justify-center rounded-xl border-[3px] border-[#1D4BD9] border-solid border-b-[5px] px-4 py-2 font-bold;
-}
-
-.selected {
-	@apply border-green-500;
-}
-
-.sprite-animation {
-	@apply absolute bottom-0 left-0 w-[42vh] h-[60vh] z-[1] bg-[url('https://oss.xiaoxiongcode.com/static/home/sprite.png')] bg-no-repeat;
-	background-size: 1460% 1000%;
-	background-position: 0 0;
-	animation: sprite-animation 3s steps(13) infinite;
-}
-
-@keyframes sprite-animation {
-	from {
-		background-position: 0 0;
-	}
-
-	to {
-		background-position: calc(-42vh * 13) 0;
-	}
-}
-</style>
+		<cl-button @tap="handleLogin('/pages/catalog/index')">物理课</cl-button>
+	</cl-page>
+</template>
+<style lang="scss" scoped></style>

+ 1 - 1
pages/user/components/sms-btn.uvue

@@ -11,7 +11,7 @@
 import { computed, reactive, ref } from "vue";
 import { useUi } from "@/uni_modules/cool-ui";
 import { request, type Response, isDark, t, $t, parse } from "@/.cool";
-import { sendSmsCode } from "@/api/user";
+import { sendSmsCode } from "@/services/user";
 const props = defineProps({
 	phone: String
 });

+ 1 - 1
pages/user/login.uvue

@@ -33,7 +33,7 @@ import password from './components/password.uvue';
 import { ref } from 'vue'
 import type { ClTabsItem } from "@/uni_modules/cool-ui";
 import { encryptPassword, user, router } from '@/.cool';
-import { loginApi } from "@/api/user";
+import { loginApi } from "@/services/user";
 const val = ref('quickly_login')
 const list: ClTabsItem[] = [
 	{

+ 0 - 0
api/subject/catalog.ts → services/subject/catalog.ts


+ 0 - 0
api/subject/course.ts → services/subject/course.ts


+ 0 - 0
api/subject/info.ts → services/subject/info.ts


+ 0 - 0
api/types/index.ts → services/types/index.ts


+ 0 - 0
api/user.ts → services/user.ts


+ 0 - 0
static/home/彩虹烧杯_透明图.png → static/home/4.png


+ 0 - 0
static/home/宝箱_透明图.png → static/home/5.png


+ 0 - 0
static/home/卡通贴纸_透明图.png → static/home/6.png