对接选课接口

This commit is contained in:
Net 2025-05-16 16:16:41 +08:00
parent f4e8120d91
commit 8e709572b3
23 changed files with 3274 additions and 1267 deletions

View File

@ -1,26 +1,21 @@
// 参数接口
// 响应接口
import {get, post} from "@/utils/request";
import { get, post } from "@/utils/request";
/**
*
*/
export const serverTimeApi = async () => {
return await get('/api/server/serverTime')
}
export const standardWordsRandomApi = async () => {
return await get('/api/standardWords/findRandom')
}
export const cmsArticleFindPageApi = async (params: any) => {
return await get("/api/cmsArticle/findPage", params);
export const loginRegisterJzApi = async (params: any) => {
return await post("/open/login/registerJz", params);
};
export const xkAddXkqdApi = async (params: any) => {
return await post("/mobile/xk/addXkqd", params);
};
export const agencyAgencyListApi = async () => {
return await get("/api/agency/agencyList");
export const xkListApi = async (params: any) => {
return await get("/mobile/xk/list", params);
};
export const serverCheckInRangeTimeApi = async () => {
return await get("/api/server/checkInRangeTime");
export const xkXkqdApi = async (params: any) => {
return await get("/mobile/xk/xkqd", params);
};
export const kcjhFindKcjhByKcIdApi = async (params: any) => {
return await get("/api/kcjh/findKcjhByKcId", params);
};

View File

@ -1,22 +1,40 @@
import {get, post} from "@/utils/request";
import { get, post } from "@/utils/request";
//字典接口
export const dicApi = async (param: { pid: number }) => {
return await get("/api/dic/findByPid", param);
return await get("/api/dic/findByPid", param);
};
//字典接口
export const findDicTreeByPidApi = async (param: { pid: number }) => {
return await get("/api/dic/findDicTreeByPid", param);
};
//根据id查询部门
export const deptFindAllDeptsByPidApi = async (param: { pid: number }) => {
return await get("/api/dept/findAllDeptsByPid", param);
return await get("/api/dept/findAllDeptsByPid", param);
};
//刷新token
export const refreshTokenApi = async (param: { refresh_token: string }) => {
return await post("/userlogin/refresh-token?refresh_token=" + param.refresh_token, param);
return await post(
"/userlogin/refresh-token?refresh_token=" + param.refresh_token,
param
);
};
//修改用户部门
export const userSaveApi = async (param: { id: string, deptId: string, deptName: string }) => {
return await post("/api/user/save?deptId=" + param.deptId + '&deptName=' + param.deptName + '&id=' + param.id, param);
export const userSaveApi = async (param: {
id: string;
deptId: string;
deptName: string;
}) => {
return await post(
"/api/user/save?deptId=" +
param.deptId +
"&deptName=" +
param.deptName +
"&id=" +
param.id,
param
);
};

View File

@ -1,49 +1,68 @@
//登录接口
import {get, post} from "@/utils/request";
import { get, post } from "@/utils/request";
//密码登录接口
export const loginPass = async (param: { username: string, password: string, openId: number | string }) => {
return await post("/userlogin/check?username=" + param.username + '&password=' + param.password + '&openId=' + param.openId, param
)
;
export const loginPass = async (param: {
username: string;
password: string;
openId: number | string;
}) => {
return await post(
"/userlogin/check?username=" +
param.username +
"&password=" +
param.password +
"&openId=" +
param.openId,
param
);
};
//验证码登录接口
export const loginCode = async (param: { phone: string | number, code: string | number, openId: number | string }) => {
return await post("/open/sms/checkCode", param);
export const loginCode = async (param: {
phone: string | number;
code: string | number;
openId: number | string;
}) => {
return await post("/open/sms/checkCode", param);
};
//验证码登录接口
export const loginCheckCode = async (param: any) => {
return await post("/open/sms/checkCode", param);
return await post("/open/sms/checkCode", param);
};
//获取验证码接口
export const sendCodeApi = async (param:any) => {
return await get("/open/sms/sendCode", param);
export const sendCodeApi = async (param: any) => {
return await get("/open/sms/sendCode", param);
};
//获取验证码接口
export const sendToUserApi = async (param: string) => {
return await post("/open/sms/sendToUser", param);
return await post("/open/sms/sendToUser", param);
};
//微信获取手机号登录接口
export const weChatLogin = async (param: any) => {
return await get("/userlogin/weloginByCode", param);
return await get("/userlogin/weloginByCode", param);
};
//获取用户按钮权限
export const authenticationApi = async (param: { userId: string }) => {
return await get("/api/authentication/find-by-user", param);
};
// 汇兴小程序登录
export const huiXingLoginApi = async (param: { token: string }) => {
return await post("/userlogin/appCheck?token=" + param.token, param);
return await get("/api/authentication/find-by-user", param);
};
//获取公众号票据
export const wxConfigApi = async (param: any) => {
return await post("/userlogin/wxConfig", param);
return await post("/userlogin/wxConfig", param);
};
export const checkOpenId = async (param: { openId: string | number, appCode: string }) => {
return await post("/open/sms/checkOpenId", param);
export const checkOpenId = async (param: {
openId: string | number;
appCode: string;
}) => {
return await post("/open/login/jz/checkUser", param);
};
export const updateUserApi = async (param: any) => {
return await post("/open/login/js/updateUser", param);
};
export const findJsByPhoneApi = async (param: any) => {
return await get("/api/js/findJsByPhone", param);
};

View File

@ -1,73 +1,128 @@
<template>
<uni-data-picker :popup-title="'请选择'+attrs.label"
:localdata="range"
v-model="newValue"
:map="{text:attrs.componentProps.rangeKey,value:attrs.componentProps.savaKey}"
@change="onchange" @nodeclick="onnodeclick" @popupopened="onpopupopened"
@popupclosed="onpopupclosed"
:readonly="!!attrs.componentProps.disabled"
v-bind="attrs.componentProps"
<view>
<u-loading-icon
:show="loadingShow"
style="justify-content: start"
size="20"
/>
<uni-data-picker
v-if="!loadingShow"
:popup-title="'请选择' + attrs.label"
:localdata="range"
v-model="newValue"
:map="{
text: attrs.componentProps.rangeKey,
value: attrs.componentProps.savaKey,
}"
@change="onchange"
@nodeclick="onnodeclick"
@popupopened="onpopupopened"
@popupclosed="onpopupclosed"
:readonly="!!attrs.componentProps.disabled"
v-bind="attrs.componentProps"
>
<template #default="{data, error, options}">
<view class="flex-row items-center justify-between py-7 font-13">
<view v-if="error">
<text style="color: red">{{ error }}</text>
</view>
<view v-else-if="data.length" class="flex-row">
<view v-for="(item,index) in data" :key="index" class="selected-item">
<text>{{ item.text }}</text>
</view>
</view>
<view class="color-9" v-else-if="!attrs.componentProps.disabled">
<text v-if="attrs.componentProps&&attrs.componentProps.placeholder">{{
attrs.componentProps.placeholder
}}
</text>
<text v-else>请选择{{ attrs.label }}</text>
</view>
<view v-else></view>
<uni-icons type="right" size="18" color="#999999" v-if="!attrs.componentProps.disabled"/>
<template #default="{ data, error, options }">
<view class="flex-row items-center justify-between py-7 font-13">
<view v-if="error">
<text style="color: red">{{ error }}</text>
</view>
<view v-else-if="data.length" class="flex-row">
<view
v-for="(item, index) in data"
:key="index"
class="selected-item"
>
<text>{{ item.text }}</text>
</view>
</template>
</view>
<view class="color-9" v-else-if="!attrs.componentProps.disabled">
<text
v-if="attrs.componentProps && attrs.componentProps.placeholder"
>{{ attrs.componentProps.placeholder }}
</text>
<text v-else>请选择{{ attrs.label }}</text>
</view>
<view v-else></view>
<uni-icons
type="right"
size="18"
color="#999999"
v-if="!attrs.componentProps.disabled"
/>
</view>
</template>
</uni-data-picker>
</view>
</template>
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
import { isFunction, map } from "lodash";
const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
const newValue = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const attrs = useAttrs()
get() {
return props.modelValue;
},
set(value) {
emit("update:modelValue", value);
},
});
const attrs = useAttrs();
if (!newValue.value) {
newValue.value = '0'
newValue.value = "0";
}
const range = ref(attrs.componentProps && attrs.componentProps.range || [])
const range = computed({
get() {
if (attrs.componentProps && attrs.componentProps.range) {
return attrs.componentProps.range;
} else {
return [];
}
},
set(value) {
attrs.componentProps.range = value;
},
});
const loadingShow = ref(false);
function initapi() {
if (attrs.componentProps.api && isFunction(attrs.componentProps.api)) {
loadingShow.value = true;
attrs.componentProps.api(attrs.componentProps.param || null).then((res) => {
attrs.componentProps.range =
res[attrs.componentProps.resultKey || "result"];
if (
attrs.componentProps.request &&
isFunction(attrs.componentProps.request)
) {
attrs.componentProps.request(attrs.componentProps.range);
}
loadingShow.value = false;
});
}
}
initapi();
function onchange(e) {
if (
attrs.componentProps.onChange &&
isFunction(attrs.componentProps.onChange)
) {
attrs.componentProps.onChange(e);
}
}
function onnodeclick() {
function onnodeclick() {}
}
function onpopupopened() {}
function onpopupopened() {
}
function onpopupclosed() {
}
function onpopupclosed() {}
</script>
<style lang="scss" scoped>
:deep(.uni-data-tree-dialog) {
top: 53%;
}
</style>
</style>

View File

@ -37,10 +37,10 @@ const newValue = computed({
const attrs: any = useAttrs()
function change(e: string) {
newValue.value = e
if (attrs.componentProps.change && isFunction(attrs.componentProps.change)) {
attrs.componentProps.change(e)
}
newValue.value = e
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,141 @@
<template>
<view class="py-7">
<u-loading-icon :show="loadingShow" style="justify-content: start" size="20"/>
<view @click="open" class="wh-full flex-row items-center justify-between" v-if="!loadingShow">
<view class="font-13 text-ellipsis-1" v-if="pickerValue">{{ pickerValue.join(',') }}</view>
<view class="font-13 color-9" v-else-if="!attrs.componentProps.disabled">
<text v-if="attrs.componentProps&&attrs.componentProps.placeholder">{{
attrs.componentProps.placeholder
}}
</text>
<text v-else>请选择{{ attrs.label }}</text>
</view>
<view v-else></view>
<uni-icons type="right" size="18" color="#999999" v-if="!attrs.componentProps.disabled"/>
</view>
<BasicTree ref="basicTreeRef"
:range="range"
:idKey="savaKey"
:rangeKey="rangeKey"
:title="'选择'+ attrs.label"
@confirm="confirm"
v-bind="attrs.componentProps"
/>
</view>
</template>
<script setup lang="ts">
import BasicTree from '@/components/BasicTree/Tree.vue';
/**
* BasicPicker 选择器
* @property title 顶部title
* @property range 渲染数据列表
* @property api 渲染数据接口
* @property param 接口参数
* @property resultKey 数据接口返回字段默认为result
* @property rangeKey 显示值的key
* @property savaKey 保存值的key
*
* @property @ok 点击确定事件
* @property @change 选择器change事件
*/
import {isFunction, map} from "lodash";
const loadingShow = ref(false)
const basicTreeRef = ref<any>(null)
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const pickerValue = ref<any>([])
const newValue = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
}
})
const attrs: any = useAttrs()
const rangeKey = ref(attrs.componentProps && attrs.componentProps.rangeKey || '')
const savaKey = ref(attrs.componentProps && attrs.componentProps.savaKey || '')
const range = computed({
get() {
if (attrs.componentProps && attrs.componentProps.range) {
return attrs.componentProps.range
} else {
return []
}
},
set(value) {
attrs.componentProps.range = value
}
})
function initapi() {
if (attrs.componentProps.api && isFunction(attrs.componentProps.api)) {
loadingShow.value = true
attrs.componentProps.api(attrs.componentProps.param || null).then((res: any) => {
attrs.componentProps.range = res[attrs.componentProps.resultKey || 'result']
if (attrs.componentProps.request && isFunction(attrs.componentProps.request)) {
attrs.componentProps.request(attrs.componentProps.range)
}
loadingShow.value = false
})
}
}
initapi()
function open() {
if (!attrs.componentProps.disabled) {
basicTreeRef.value._show()
}
}
function confirm(value: any) {
const valueDataID = map(value, (item) => {
return item[attrs.componentProps.savaKey]
})
newValue.value = valueDataID.join(',')
if (attrs.componentProps.ok && typeof attrs.componentProps.ok === 'function') {
attrs.componentProps.ok(value, attrs.componentProps.range)
}
}
function hx(rangeData: any, data: any) {
let valueList = data.split(',')
for (const key in rangeData) {
for (const vkey in valueList) {
if (rangeData[key][attrs.componentProps.savaKey] == valueList[vkey]) {
rangeData[key].checked = true;
if (!attrs.componentProps.multiple) {
pickerValue.value = []
}
pickerValue.value.push(rangeData[key][attrs.componentProps.rangeKey])
}
}
if (rangeData[key].children && rangeData[key].children.length > 0) {
hx(rangeData[key].children, data)
}
}
range.value = rangeData;
}
watchEffect(() => {
if (newValue.value || newValue.value == 0) {
if (attrs.componentProps.range && attrs.componentProps.range.length > 0) {
hx(attrs.componentProps.range, newValue.value.toString())
}
} else {
pickerValue.value = newValue.value
}
})
</script>

View File

@ -14,6 +14,7 @@
<FormBasicDataPicker v-bind="attrs" v-if="isShow('BasicDataPicker')" v-model="newValue"/>
<FormBasicSearchList v-bind="attrs" v-if="isShow('BasicSearchList')" v-model="newValue"/>
<FormBasicDateTimes v-bind="attrs" v-if="isShow('BasicDateTimes')" v-model="newValue"/>
<FormBasicTree v-bind="attrs" v-if="isShow('BasicTree')" v-model="newValue"/>
</view>
</template>

View File

@ -26,6 +26,7 @@ type Component =
| 'BasicDataPicker'
| 'BasicSearchList'
| 'BasicDateTimes'
| 'BasicTree'
interface FormsSchema {
field?: string,

View File

@ -407,7 +407,7 @@ export default {
background: #FFF;
margin: 10px 0;
width: 90vw;
height: 90vh;
height: 88vh;
align-self: center;
// pointer-events:none;
}

View File

@ -1,15 +1,16 @@
const ip: string = "dj.zhongjingzh.com";
const ip: string = "119.29.194.155:8893";
const fwqip: string = "yufangzc.com";
//打包服务器接口代理标识
const SERVERAGENT: string = "/";
const SERVERAGENT: string = "/jzd-api";
//本地代理url地址,配置了就启动代理,没配置就不启动代理
export const HOMEAGENT: string = "";
// 接口地址
export const BASE_URL: string =
process.env.NODE_ENV == "development" ? `http://${ip}/yqdj` : SERVERAGENT;
process.env.NODE_ENV == "development" ? `http://${ip}/zhxy` : SERVERAGENT;
// WebSocket地址
export const BASE_WS_URL: string = `wss://${ip}`;
//图片地址
export const BASE_IMAGE_URL: string = `http://${ip}`;
export const BASE_IMAGE_URL: string = process.env.NODE_ENV == "development" ? `http://${ip}` : `https://${fwqip}`;
//存token的key
export const AUTH_KEY: string = "satoken";
//token过期返回状态码
@ -17,7 +18,7 @@ export const RESULT_CODE_NOT_LOGIN: number = 10;
//是否打印接口日志
export const ISREQUESTLOG: boolean = false;
//是否打开登录页面拦截
export const ISROUTERINTERCEPT: boolean = false;
export const ISROUTERINTERCEPT: boolean = true;
//配置路由白名单
export const WHITELIST: WhiteList = [];
//主题颜色

View File

@ -1,5 +1,5 @@
{
"name": "模版",
"name": "智慧校园",
"appid": "__UNI__27C6F45",
"description": "",
"versionName": "1.0.0",
@ -207,9 +207,9 @@
"uniStatistics": {
"enable": false
},
"publicPath": "/",
"publicPath": "/zhxy-jzd",
"router": {
"base": "/",
"base": "/zhxy-jzd/",
"mode": "hash"
},
"sdkConfigs": {

View File

@ -91,13 +91,6 @@
}
}
},
{
"path": "pages/base/parentRegister/index",
"style": {
"navigationBarTitleText": "家长注册",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/class-schedule/index",
"style": {
@ -195,6 +188,26 @@
"navigationBarTitleText": "俱乐部选课",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/course-selection/notice",
"style": {
"navigationBarTitleText": "告知书",
"enablePullDownRefresh": false
}
},
{
"path": "pages/base/course-selection/enrolled",
"style": {
"navigationBarTitleText": "已报名",
"enablePullDownRefresh": false
}
} , {
"path": "pages/base/course-selection/notopen",
"style": {
"navigationBarTitleText": "未开放",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
<view class="course-info">
<image
class="course-image"
:src="courseDetail.image || '/static/images/course-default.jpg'"
:src="courseDetail.xkkcImg"
mode="aspectFill"
></image>
@ -21,11 +21,12 @@
<view class="course-location"
>上课地点{{ courseDetail.location }}</view
>
<view class="course-price"
>金额<text class="price-value"
>¥{{ courseDetail.price }}</text
></view
<view class="course-time"
>上课时间{{ courseDetail.studyTime }}</view
>
<view class="course-price">
金额<text class="price-value">¥{{ courseDetail.price }}</text>
</view>
</view>
</view>
</view>
@ -36,14 +37,25 @@
<view class="divider"></view>
<view class="teacher-info">
<image
class="teacher-avatar"
:src="teacherInfo.avatar || '/static/images/teacher-avatar.jpg'"
mode="aspectFill"
></image>
<!-- 教师头部信息区域 -->
<view class="teacher-header">
<image
class="teacher-avatar"
:src="teacherInfo.avatar"
mode="aspectFill"
></image>
<view class="teacher-basic-info">
<view class="teacher-name">{{ teacherInfo.name }}</view>
<view class="teacher-title">课程教师</view>
</view>
</view>
<view class="teacher-intro">
<text>{{ teacherInfo.introduction }}</text>
<!-- 教师简介区域 -->
<view class="teacher-intro-section">
<view class="intro-title">教师简介</view>
<view class="teacher-intro">
<text>{{ teacherInfo.introduction }}</text>
</view>
</view>
</view>
</view>
@ -64,13 +76,21 @@
<view class="divider"></view>
<view class="content-section">
<view
v-for="(phase, index) in teachingPlan"
:key="index"
class="teaching-phase"
>
<text>{{ phase }}</text>
</view>
<template v-if="teachingPlan && teachingPlan.length > 0">
<view
v-for="(phase, index) in teachingPlan"
:key="index"
class="teaching-phase"
>
<text>{{ phase }}</text>
</view>
</template>
<template v-else>
<view class="empty-data">
<u-icon name="info-circle" color="#C8C9CC" size="18"></u-icon>
<text>暂无教学计划</text>
</view>
</template>
</view>
</view>
</view>
@ -90,57 +110,81 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ref, onMounted, computed } from "vue";
import { navigateBack } from "@/utils/uniapp";
import { useDataStore } from "@/store/modules/data";
import { storeToRefs } from "pinia";
import { imagUrl } from "@/utils";
import { kcjhFindKcjhByKcIdApi } from "@/api/base/server";
//
interface CourseData {
id?: string;
kcmc?: string;
jsName?: string;
kcdd?: string;
kcje?: number;
studyTime?: string;
kcjsms?: string;
jxll?: string;
remark?: string;
xkkcImg?: string;
[key: string]: any; //
}
const useData = useDataStore();
const { kcData } = storeToRefs(useData);
// -
const teachingPlan = ref<string[]>([]);
const courseData = kcData.value as CourseData;
if (courseData && courseData.id) {
kcjhFindKcjhByKcIdApi({
xkkcId: courseData.id,
}).then((res) => {
if (res.resultCode == 1) {
teachingPlan.value = res.result.map(
(item: any) => item.jhjd + ":" + item.jhms
);
}
});
}
//
const courseDetail = ref({
id: 2,
title: "机器人创客",
teacher: "叶老师",
location: "第一教学楼302",
price: 142,
image: "/static/images/robot-course.jpg",
const courseDetail = computed(() => {
const data = (kcData.value as CourseData) || {};
return {
id: data.id || "",
title: data.kcmc || "暂无课程名称",
teacher: data.jsName || "暂无教师信息",
location: data.kcdd || "暂无地点信息",
price: data.kcje || 0,
studyTime: data.studyTime || "暂无上课时间",
xkkcImg: data.xkkcImg || "/static/images/robot-course.jpg", //
};
});
//
const teacherInfo = ref({
name: "叶老师",
avatar: "/static/images/teacher-avatar.jpg",
introduction:
"叶老师出生于1997年7月10日青少年机器人创客指导教师指导祥富小学-晟航水木年华获得2022年青少年机器人竞赛小学组三等奖。",
const teacherInfo = computed(() => {
const data = (kcData.value as CourseData) || {};
return {
name: data.jsName || "暂无教师信息",
avatar: imagUrl(data.jstx), //
introduction: data.kcjsms || "暂无教师介绍",
};
});
//
const teachingPhilosophy = ref(
'"做中学、玩中学"的教育理念,机器人课程最大的优势就是能够让孩子在"玩"中去探索,去体验属于他们自己的世界,很多孩子刚开始接触的时候都是出于兴趣。培养孩子更进一步的能力,提高孩子的学习效果也的确需要从兴趣开始。但真正激发孩子能力,形成学习动力的是对目标的追求。我就通过机器人赛事帮助孩子把"兴趣"变成学习的目标,继而助力孩子未来成长的道路。'
);
//
const teachingPlan = ref([
"第一阶段:了解机器人的组成,知道每个零件的名称及用途,认识机器人的结构。",
"第二阶段:在老师的引导下,分组搭建机器人,注意引导幼儿理解机器人的数据线连接和遥控器方向的关系。",
"第三阶段:学会操控机器人的移动方向,并练习把魔方根据要求推到指定位置。",
"第四阶段:组织幼儿参加创客机器人比赛。",
]);
//
const goBack = () => {
uni.navigateBack();
};
//
onMounted(() => {
// URLID
const courseId = uni.getStorageSync("currentCourseId") || 2;
// fetchCourseDetail(courseId);
const teachingPhilosophy = computed(() => {
const data = (kcData.value as CourseData) || {};
return data.jxll || "暂无教学理念信息";
});
</script>
<style lang="scss" scoped>
.course-detail {
min-height: 100vh;
background-color: #f5f7fa;
padding-bottom: 80px;
}
.nav-bar {
@ -195,7 +239,7 @@ onMounted(() => {
.course-image {
width: 120px;
height: 120px;
height: 138px;
border-radius: 8px;
margin-right: 15px;
}
@ -217,6 +261,12 @@ onMounted(() => {
margin-bottom: 8px;
}
.course-time {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.course-price {
font-size: 14px;
color: #666;
@ -231,19 +281,62 @@ onMounted(() => {
.teacher-info {
display: flex;
flex-direction: column;
.teacher-avatar {
width: 80px;
height: 100px;
border-radius: 4px;
margin-right: 15px;
.teacher-header {
display: flex;
align-items: center;
margin-bottom: 15px;
.teacher-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
margin-right: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
border: 2px solid #fff;
}
.teacher-basic-info {
.teacher-name {
font-size: 18px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.teacher-title {
font-size: 14px;
color: #666;
background-color: #f4f7fc;
padding: 2px 8px;
border-radius: 4px;
display: inline-block;
}
}
}
.teacher-intro {
flex: 1;
font-size: 14px;
color: #666;
line-height: 1.6;
.teacher-intro-section {
width: 100%;
.intro-title {
font-size: 15px;
font-weight: 500;
color: #555;
margin-bottom: 8px;
padding-left: 8px;
border-left: 3px solid #2879ff;
}
.teacher-intro {
font-size: 14px;
color: #666;
line-height: 1.6;
background-color: #f8f9fc;
padding: 12px;
border-radius: 8px;
text-align: justify;
}
}
}
@ -259,6 +352,19 @@ onMounted(() => {
margin-bottom: 0;
}
}
.empty-data {
display: flex;
align-items: center;
justify-content: center;
padding: 20px 0;
color: #909399;
font-size: 14px;
text {
margin-left: 8px;
}
}
}
.bottom-action {

View File

@ -0,0 +1,428 @@
<template>
<BasicLayout>
<view class="enrolled-page">
<!-- 已报名状态提示 -->
<view class="status-card">
<view class="status-icon">
<u-icon name="checkmark" size="32" color="#3FBF72"></u-icon>
</view>
<view class="status-text">已报名</view>
<view class="status-desc">该学生已成功报名以下兴趣课程</view>
</view>
<!-- 学生信息卡片 -->
<view class="info-card student-card">
<view class="card-title">学生信息</view>
<view class="divider"></view>
<view class="student-info">
<view class="student-avatar">
<image
:src="studentInfo.avatar || '/static/base/home/11222.png'"
mode="aspectFill"
></image>
</view>
<view class="student-details">
<view class="student-name">{{ studentInfo.xm }}</view>
<view class="student-class"
>{{ studentInfo.njmc }} {{ studentInfo.bjmc }}</view
>
</view>
</view>
</view>
<!-- 已报名课程信息 -->
<view class="info-card">
<view class="card-title">已报名课程</view>
<view class="divider"></view>
<view class="course-info">
<image
class="course-image"
:src="enrolledCourse.image || '/static/base/home/2211.png'"
mode="aspectFill"
></image>
<view class="course-details">
<view class="course-name">{{ enrolledCourse.title }}</view>
<view class="course-teacher"
>开课老师{{ enrolledCourse.teacher }}</view
>
<view class="course-time">上课时间{{ enrolledCourse.time }}</view>
<view class="course-location"
>上课地点{{ enrolledCourse.location }}</view
>
</view>
</view>
<!-- 报名信息 -->
<view class="enrollment-info">
<view class="info-item">
<text class="info-label">报名时间</text>
<text class="info-value">{{ enrolledCourse.enrollDate }}</text>
</view>
<view class="info-item">
<text class="info-label">课程费用</text>
<text class="info-value highlight">¥{{ enrolledCourse.fee }}</text>
</view>
<view class="info-item">
<text class="info-label">支付状态</text>
<text
class="info-value"
:class="enrolledCourse.isPaid ? 'paid' : 'unpaid'"
>
{{ enrolledCourse.isPaid ? "已支付" : "未支付" }}
</text>
</view>
</view>
</view>
<!-- 温馨提示 -->
<!-- <view class="notice-card">
<view class="notice-title">
<u-icon name="info-circle" size="18" color="#2879FF"></u-icon>
<text>温馨提示</text>
</view>
<view class="notice-content">
<text>1. 课程一经报名成功不可取消或更换</text>
<text>2. 如有特殊情况需要请假请提前与老师联系</text>
<text>3. 请按时上课迟到将影响学习效果</text>
</view>
</view> -->
</view>
<template #bottom>
<view class="bottom-actions" v-if="!enrolledCourse.isPaid">
<u-button text="继续支付" type="primary" @click="goPay"></u-button>
<!-- <u-button
text="返回选课"
:plain="true"
@click="goBack"
></u-button> -->
</view>
</template>
</BasicLayout>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import { useUserStore } from "@/store/modules/user";
const { getUser } = useUserStore();
//
const studentId = ref("");
onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
// @ts-ignore
const options = currentPage.$page?.options;
if (options && options.studentId) {
studentId.value = options.studentId;
}
});
//
const studentInfo = ref({
id: "",
xm: "加载中...",
njmc: "",
bjmc: "",
avatar: "",
});
//
const enrolledCourse = ref({
id: "",
title: "少儿声乐",
image: "",
teacher: "张老师",
time: "每周一 16:00-17:30",
location: "艺术楼 203教室",
enrollDate: "2023-09-15 14:30:45",
fee: 420,
isPaid: false,
});
//
const fetchStudentInfo = () => {
// ID
//
const student = getUser.xsList.find((s: any) => s.id === studentId.value);
if (student) {
studentInfo.value = student;
}
// TODO:
// const url = '/api/student/detail';
// const params = { studentId: studentId.value };
// return new Promise((resolve) => {
// // API
// setTimeout(() => {
// resolve();
// }, 500);
// });
};
//
const fetchEnrolledCourse = () => {
// TODO:
// const url = '/api/course/enrolled';
// const params = { studentId: studentId.value };
// return new Promise((resolve) => {
// // API
// setTimeout(() => {
// resolve();
// }, 500);
// });
//
setTimeout(() => {
enrolledCourse.value = {
id: "course123",
title: "少儿声乐",
image: "/static/base/home/2211.png",
teacher: "张老师",
time: "每周一 16:00-17:30",
location: "艺术楼 203教室",
enrollDate: "2023-09-15 14:30:45",
fee: 420,
isPaid: Math.random() > 0.5, //
};
}, 500);
};
//
const goPay = () => {
uni.navigateTo({
url: `/pages/base/course-selection/payment?courseId=${enrolledCourse.value.id}&studentId=${studentId.value}`,
});
};
//
const goBack = () => {
uni.navigateBack();
};
onMounted(() => {
uni.showLoading({ title: "加载中..." });
//
Promise.all([fetchStudentInfo(), fetchEnrolledCourse()]).finally(() => {
uni.hideLoading();
});
});
</script>
<style lang="scss" scoped>
.enrolled-page {
background-color: #f5f7fa;
padding: 15px;
}
.status-card {
background: linear-gradient(135deg, #3fbf72, #2ab559);
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
.status-icon {
width: 60px;
height: 60px;
background-color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
}
.status-text {
font-size: 22px;
font-weight: bold;
margin-bottom: 8px;
}
.status-desc {
font-size: 14px;
opacity: 0.8;
}
}
.info-card {
background-color: #fff;
border-radius: 12px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
&.student-card {
background-color: #eef4ff;
}
.card-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.divider {
height: 1px;
background-color: #eee;
margin-bottom: 15px;
}
}
.student-info {
display: flex;
align-items: center;
.student-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
margin-right: 15px;
image {
width: 100%;
height: 100%;
}
}
.student-details {
.student-name {
font-size: 18px;
font-weight: 500;
color: #333;
margin-bottom: 5px;
}
.student-class {
font-size: 14px;
color: #666;
}
}
}
.course-info {
display: flex;
margin-bottom: 15px;
.course-image {
width: 100px;
height: 100px;
border-radius: 8px;
margin-right: 15px;
flex-shrink: 0;
}
.course-details {
flex: 1;
.course-name {
font-size: 18px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.course-teacher,
.course-time,
.course-location {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
}
}
.enrollment-info {
background-color: #f9f9f9;
border-radius: 8px;
padding: 12px;
.info-item {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.info-label {
color: #666;
font-size: 14px;
}
.info-value {
font-size: 14px;
color: #333;
&.highlight {
color: #ff6b01;
font-weight: 500;
}
&.paid {
color: #3fbf72;
}
&.unpaid {
color: #ff5252;
}
}
}
}
.notice-card {
background-color: #fff9e6;
border-radius: 12px;
padding: 15px;
.notice-title {
display: flex;
align-items: center;
margin-bottom: 10px;
text {
margin-left: 5px;
font-size: 16px;
font-weight: 500;
color: #333;
}
}
.notice-content {
font-size: 14px;
color: #666;
text {
display: block;
margin-bottom: 5px;
&:last-child {
margin-bottom: 0;
}
}
}
}
.bottom-actions {
padding: 15px;
background-color: #fff;
display: flex;
justify-content: space-between;
:deep(.u-button) {
flex: 1;
margin: 0 5px;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
<template>
<BasicLayout>
<view class="p-15">
<view class="white-bg-color p-15 r-md">
<view> 各位家长</view>
<view class="notice-text">
随着素质教育的不断深入学生各项素质能力的培养越来越受到学校家庭的重视我校根据教育局的有关精神继续举办兴趣班请各位家长根据实际情况遵照"孩子自主,家长自愿"的原则选择兴趣班请点击下一步确认知晓告知内容
</view>
</view>
<BasicSign ref="signCompRef" title="签名"></BasicSign>
</view>
<template #bottom>
<view class="white-bg-color py-5">
<view class="flex-row items-center pb-10 pt-5">
<u-button
text="下一步"
class="mx-15"
type="primary"
@click="submit"
/>
</view>
</view>
</template>
</BasicLayout>
</template>
<script lang="ts" setup>
import { xkListApi } from "@/api/base/server";
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user";
const signCompRef = ref<any>(null);
const sign_file = ref<any>(null);
const { setData, getGlobal } = useDataStore();
async function submit() {
//
const data = await signCompRef.value.getSyncSignature();
sign_file.value = data.base64;
setData({
sign_file: sign_file.value,
});
if (getGlobal.type == 1) {
uni.reLaunch({
url: "/pages/base/course-selection/index",
});
}
if (getGlobal.type == 2) {
uni.reLaunch({
url: "/pages/base/course-selection/club-selection",
});
}
}
</script>
<style lang="scss" scoped>
.notice-text {
margin-top: 10px;
text-indent: 2em; /* 添加两个中文字符的缩进 */
}
</style>

View File

@ -0,0 +1,171 @@
<template>
<BasicLayout>
<view class="notopen-page">
<!-- 未开放状态图标 -->
<view class="status-icon">
<u-icon name="clock" size="80" color="#A0CFFF"></u-icon>
</view>
<!-- 状态说明 -->
<view class="status-text">
<view class="title">课程报名尚未开放</view>
<view class="subtitle">请耐心等待学校通知</view>
</view>
<!-- 提示信息 -->
<view class="info-card">
<view class="info-title">
<u-icon name="info-circle" size="18" color="#2879FF"></u-icon>
<text>温馨提示</text>
</view>
<view class="info-content">
<text>1. 学校将按计划开放兴趣课程报名敬请关注学校通知</text>
<text>2. 为保证公平课程报名将统一时间开放</text>
<text>3. 开放报名后您可以为孩子选择合适的兴趣课程</text>
<text>4. 如有疑问请联系班主任老师咨询详情</text>
</view>
</view>
<!-- 时间提示 -->
<!-- <view class="time-info">
<view class="time-title">预计开放时间</view>
<view class="time-value">9月15日 14:00</view>
</view> -->
<!-- 课程预览图 -->
<!-- <view class="preview-container">
<image
src="/static/base/home/2211.png"
mode="aspectFill"
class="preview-image"
></image>
</view> -->
</view>
</BasicLayout>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
//
const goHome = () => {
uni.reLaunch({
url: "/pages/base/home/index"
});
};
onMounted(() => {
// 访
});
</script>
<style lang="scss" scoped>
.notopen-page {
background-color: #f5f7fa;
padding: 30px 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.status-icon {
margin-top: 50px;
width: 140px;
height: 140px;
background-color: rgba(160, 207, 255, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30px;
}
.status-text {
text-align: center;
margin-bottom: 40px;
.title {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.subtitle {
font-size: 16px;
color: #666;
}
}
.info-card {
width: 100%;
background-color: #eef4ff;
border-radius: 12px;
padding: 20px;
margin-bottom: 30px;
.info-title {
display: flex;
align-items: center;
margin-bottom: 15px;
text {
margin-left: 8px;
font-size: 16px;
font-weight: 500;
color: #333;
}
}
.info-content {
text {
display: block;
margin-bottom: 10px;
font-size: 14px;
color: #666;
line-height: 1.5;
&:last-child {
margin-bottom: 0;
}
}
}
}
.time-info {
text-align: center;
margin-bottom: 30px;
.time-title {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.time-value {
font-size: 22px;
font-weight: bold;
color: #2879FF;
}
}
.preview-container {
width: 100%;
border-radius: 12px;
overflow: hidden;
margin-bottom: 30px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
.preview-image {
width: 100%;
height: 150px;
}
}
.bottom-actions {
padding: 15px;
background-color: #fff;
}
</style>

View File

@ -1,403 +0,0 @@
<template>
<BasicLayout>
<view class="page-container">
<view class="notice-box">
<text class="notice-text"
>特别提示家中家庭成员可多人注册可接收孩子在校情况学业情况等</text
>
</view>
<view
v-for="(student, index) in students"
:key="index"
class="form-section mb-15"
>
<view v-if="index > 0" class="remove-btn" @click="removeStudent(index)">
<u-icon name="minus-circle" color="#ff4d4f" size="18"></u-icon>
</view>
<view class="avatar-section">
<view class="avatar-uploader-container-rect">
<CustomUpload
@select="(event) => afterRead(event, index)"
@close="handleAvatarClose(index)"
:sourceType="['camera', 'album']"
>
<view class="avatar-placeholder">
<view class="wh-full flex-col-center">
<svg
t="1729656215869"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5302"
width="32"
height="32"
>
<path
d="M851.552 890.88 172.448 890.88c-74.592 0-135.296-60.672-135.296-135.296L37.152 370.752c0-74.624 60.672-135.328 135.296-135.328l132.16 0L302.912 195.904c0-34.624 28.192-62.816 62.816-62.816l302.016 0c29.408 0 53.312 23.904 53.312 53.312l0 49.024 130.464 0c74.592 0 135.296 60.672 135.296 135.328l0 384.832C986.816 830.208 926.144 890.88 851.552 890.88zM172.448 283.456c-48.128 0-87.296 39.168-87.296 87.328l0 384.832c0 48.128 39.168 87.296 87.296 87.296l679.104 0c48.128 0 87.296-39.168 87.296-87.296L938.848 370.752c0-48.16-39.168-87.328-87.296-87.328L716.8 283.424c-24.096 0-43.712-19.616-43.712-43.712L673.088 186.4c0-2.944-2.368-5.312-5.312-5.312l-302.016 0c-8.16 0-14.816 6.656-14.816 14.816L350.944 237.12c0 25.536-20.768 46.304-46.304 46.304L172.448 283.424zM512 755.84c-107.04 0-194.08-87.072-194.08-194.08S404.992 367.68 512 367.68s194.08 87.072 194.08 194.08S619.04 755.84 512 755.84zM512 415.68c-80.576 0-146.08 65.536-146.08 146.08S431.456 707.84 512 707.84s146.08-65.536 146.08-146.08S592.576 415.68 512 415.68zM816.8 438.016c-25.568 0-46.336-20.768-46.336-46.336s20.768-46.336 46.336-46.336 46.336 20.768 46.336 46.336S842.368 438.016 816.8 438.016zM816.8 390.016l-1.664 1.664c0 0.896 0.736 1.664 1.664 1.664L816.8 390.016z"
fill="#cdcdcd"
p-id="5303"
></path>
</svg>
</view>
</view>
</CustomUpload>
</view>
<text class="avatar-upload-note">上传学生人像用于校园进出</text>
</view>
<view class="student-info-form">
<u--form label-width="auto">
<u-form-item
label="姓名"
:prop="`students[${index}].name`"
required
borderBottom
>
<u--input
v-model="student.name"
placeholder="请输入子女姓名"
border="none"
inputAlign="right"
></u--input>
</u-form-item>
<u-form-item
label="身份证号"
:prop="`students[${index}].idCard`"
required
borderBottom
>
<u--input
v-model="student.idCard"
placeholder="请输入子女身份证号"
border="none"
inputAlign="right"
></u--input>
</u-form-item>
</u--form>
</view>
</view>
<view class="add-child-btn mb-15" @click="addMoreChildren">
<u-icon name="plus" color="#416AF2" size="20"></u-icon>
<text class="add-child-btn-text">新增多孩</text>
</view>
<BasicForm @register="register"></BasicForm>
</view>
<template #bottom>
<view class="white-bg-color py-5">
<view class="flex-row items-center pb-10 pt-5">
<u-button text="提交" class="mx-15" type="primary" @click="submit" />
</view>
</view>
</template>
</BasicLayout>
</template>
<script lang="ts" setup>
import { hideLoading, showLoading, showToast } from "@/utils/uniapp";
import { ref } from "vue";
import { attachmentUpload } from "@/api/system/upload";
import CustomUpload from "/src/components/BasicUpload/CustomUpload.vue";
import { useForm } from "@/components/BasicForm/hooks/useForm";
const [register, { getValue }] = useForm({
formsProps: { labelWidth: 100 },
schema: [
{ title: "监督人信息" },
{
field: "gsmc",
label: "与学生关系",
component: "BasicInput",
required: true,
componentProps: {},
},
{
field: "gsmc",
label: "姓名",
component: "BasicInput",
required: true,
componentProps: {},
},
{
field: "gsmc",
label: "手机号",
component: "BasicInput",
required: true,
componentProps: {},
},
{
field: "gsmc",
label: "工作单位",
component: "BasicInput",
required: true,
componentProps: {},
},
{
field: "gsmc",
label: "职务",
component: "BasicInput",
required: true,
componentProps: {},
},
{
field: "gsmc",
label: "家庭地址",
component: "BasicInput",
required: true,
componentProps: {},
},
],
});
const students = ref([
{
name: "",
idCard: "",
avatar: "",
avatarFile: null as UniNamespace.ChooseImageSuccessCallbackResult | null,
avatarServerPath: "",
},
]);
async function afterRead(event: any, index: number) {
if (!event.tempFilePaths || event.tempFilePaths.length === 0) {
showToast({ title: "图片选择失败", icon: "none" });
return;
}
const tempFilePath = event.tempFilePaths[0];
students.value[index].avatar = tempFilePath;
students.value[index].avatarFile = event;
showLoading({ title: "上传中" });
try {
const { result } = await attachmentUpload(tempFilePath);
if (result && result.length > 0 && result[0].filePath) {
students.value[index].avatarServerPath = result[0].filePath;
console.log(`Student ${index} avatar uploaded:`, result[0].filePath);
showToast({ title: "上传成功" });
} else {
showToast({ title: "上传失败,请重试", icon: "none" });
console.error("Upload result format error:", result);
}
} catch (error) {
showToast({ title: "上传出错", icon: "none" });
console.error("Upload error:", error);
} finally {
hideLoading();
}
}
function handleAvatarClose(index: number) {
students.value[index].avatar = "";
students.value[index].avatarFile = null;
students.value[index].avatarServerPath = "";
console.log(`Student ${index} avatar removed`);
}
function addMoreChildren() {
students.value.push({
name: "",
idCard: "",
avatar: "",
avatarFile: null,
avatarServerPath: "",
});
}
function removeStudent(index: number) {
if (students.value.length > 1) {
students.value.splice(index, 1);
} else {
showToast({ title: "至少需要一个学生信息", icon: "none" });
}
}
async function submit() {
for (const student of students.value) {
if (!student.name || !student.idCard) {
showToast({ title: "请完整填写所有学生信息", icon: "none" });
return;
}
}
const formData = await getValue();
console.log(formData);
}
</script>
<style lang="scss" scoped>
.page-container {
padding: 15px; /* Consistent padding */
}
.notice-box {
background-color: #fff1f0; /* Slightly lighter red */
padding: 12px 18px;
border-radius: 8px;
margin-bottom: 15px;
}
.notice-text {
color: #fa541c; /* Adjusted red color */
font-size: 13px;
}
.form-section {
background-color: #ffffff;
border-radius: 10px;
padding: 20px 20px 0 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.06);
/* Added margin-bottom directly here for spacing between sections */
margin-bottom: 5px;
position: relative;
}
.avatar-section {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 25px; /* Increased margin */
}
/* Keep avatar uploader styles relevant to CustomUpload */
/* Replace with rectangular styles */
.avatar-uploader-container-rect {
/* Assuming uni.rpx units based on class names like wi-180, he-240 */
width: 180rpx;
height: 240rpx;
margin: 0 auto 10px auto; /* mx-auto mb-10 */
border-radius: 6px; /* r-md approximation */
border: 1px solid #cccccc;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
background-color: #fafafa;
}
/* Remove old circular styles */
/* .avatar-uploader-container { ... } */
.avatar-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
cursor: pointer;
}
/* Keep inner placeholder for centering SVG */
.avatar-placeholder-inner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Remove old hint style */
/* .avatar-upload-hint { ... } */
.avatar-upload-note {
color: #999;
font-size: 13px;
}
.student-info-form {
margin-bottom: 20px; /* Restore original margin */
}
.form-title {
display: block;
font-size: 16px; /* Slightly larger title */
font-weight: 600; /* Bolder */
margin-bottom: 15px; /* Space below title */
}
/* Restore remove button style if needed, or adapt */
/* Updated remove button style */
.remove-btn {
position: absolute;
top: 10px; /* Closer to top edge */
right: 10px; /* Closer to right edge */
cursor: pointer;
z-index: 10;
/* Optional: add padding for easier clicking */
// padding: 5px;
}
.add-child-btn {
background-color: #ffffff;
border-radius: 10px;
padding: 15px; /* Increased padding */
display: flex;
justify-content: center;
align-items: center;
margin-top: 10px; /* Increased margin */
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.06);
cursor: pointer;
}
.add-child-btn-text {
color: #416af2;
margin-left: 8px;
font-size: 15px;
font-weight: 500;
}
/* Adjust uview form item styles */
::v-deep .u-form-item {
margin-bottom: 0; /* Remove default margin if any */
}
::v-deep .u-form-item__body {
padding: 15px 0 !important; /* Adjusted padding */
/* Align items vertically if label wraps */
// align-items: flex-start;
}
::v-deep .u-form-item__body__left {
/* Allow label to take necessary width, adjust as needed */
// flex: 0 0 80px;
}
::v-deep .u-form-item__body__left__text {
font-size: 15px;
color: #333;
line-height: 1.5; /* Improve line spacing if label wraps */
/* Ensure required asterisk is red and BEFORE the text */
display: flex; /* Use flex to control order */
align-items: center; /* Vertically center asterisk and text */
span {
color: #f56c6c;
/* Order asterisk first */
order: -1;
margin-right: 4px;
/* Adjust vertical alignment if needed */
// line-height: 1;
// display: inline-block;
// vertical-align: middle;
}
}
::v-deep .u-input {
text-align: right; /* Align input text to the right */
}
::v-deep .u-border-bottom {
/* Ensure border spans full width if needed, or adjust */
// left: 0 !important;
// right: 0 !important;
}
</style>

View File

@ -1,24 +1,62 @@
<template>
<view class="wh-full">
<BasicLoading :isShow="isShow" bgColor="#fff" isShowTitle textColor="#000" title="启动中..." :type="1"/>
<BasicLoading
:isShow="isShow"
bgColor="#fff"
isShowTitle
textColor="#000"
title="启动中..."
:type="1"
/>
</view>
</template>
<script lang="ts" setup>
import {onShow} from "@dcloudio/uni-app";
import {isTabBar} from "@/utils/uniapp";
import pages from '@/pages.json'
import { onLoad } from "@dcloudio/uni-app";
import pages from "@/pages.json";
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user";
import { checkOpenId } from "@/api/system/login";
import { isTabBar } from "@/utils/uniapp";
const isShow = ref(true)
onShow(async () => {
if (isTabBar()) {
uni.switchTab({
url: '/' + (pages as any).tabBar.list[0].pagePath
})
const { setGlobal, getGlobal } = useDataStore();
const { afterLoginAction, getToken } = useUserStore();
const { setFile, getFile } = useDataStore();
const isShow = ref(true);
function toHome(data: any) {
if (data.type == 1 || data.type == 2) {
uni.reLaunch({
url: "/pages/base/course-selection/notice",
});
} else {
uni.reLaunch({
url: '/' + pages.pages[1].path
})
url: "/pages/base/home/index",
});
}
})
}
onLoad(async (data: any) => {
setGlobal(data);
if (!getToken) {
if (data && data.openId) {
checkOpenId({ openId: data.openId, appCode: "JZ" })
.then(async (res) => {
if (res.resultCode == 1) {
if (res.result) {
afterLoginAction(res.result);
toHome(data);
} else {
uni.reLaunch({
url: "/pages/base/parentRegister/index",
});
}
}
})
.catch((err) => {});
}
} else {
toHome(data);
}
});
</script>

View File

@ -1,306 +1,442 @@
<template>
<view class="register-container p-30">
<!-- 顶部 Logo -->
<image
class="logo"
src="@/static/system/login/logo.png"
mode="aspectFit"
></image>
<!-- 表单区域 -->
<view class="form-card">
<!-- 头像和标题 -->
<BasicLayout>
<view class="page-container">
<view class="notice-box">
<text class="notice-text"
>特别提示家中家庭成员可多人注册可接收孩子在校情况学业情况等</text
>
</view>
<view
class="wi-180 he-240 mx-auto r-md mb-20"
style="border: 1px solid #cccccc"
v-for="(student, index) in students"
:key="index"
class="form-section mb-15"
>
<CustomUpload @select="afterRead" :sourceType="['camera']">
<view class="wh-full flex-col-center">
<svg
t="1729656215869"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5302"
width="32"
height="32"
<view v-if="index > 0" class="remove-btn" @click="removeStudent(index)">
<u-icon name="minus-circle" color="#ff4d4f" size="18"></u-icon>
</view>
<view class="avatar-section">
<view class="avatar-uploader-container-rect">
<CustomUpload
@select="(event:any) => afterRead(event, index)"
@close="handleAvatarClose(index)"
:sourceType="['camera', 'album']"
:value="student.xstx"
>
<path
d="M851.552 890.88 172.448 890.88c-74.592 0-135.296-60.672-135.296-135.296L37.152 370.752c0-74.624 60.672-135.328 135.296-135.328l132.16 0L302.912 195.904c0-34.624 28.192-62.816 62.816-62.816l302.016 0c29.408 0 53.312 23.904 53.312 53.312l0 49.024 130.464 0c74.592 0 135.296 60.672 135.296 135.328l0 384.832C986.816 830.208 926.144 890.88 851.552 890.88zM172.448 283.456c-48.128 0-87.296 39.168-87.296 87.328l0 384.832c0 48.128 39.168 87.296 87.296 87.296l679.104 0c48.128 0 87.296-39.168 87.296-87.296L938.848 370.752c0-48.16-39.168-87.328-87.296-87.328L716.8 283.424c-24.096 0-43.712-19.616-43.712-43.712L673.088 186.4c0-2.944-2.368-5.312-5.312-5.312l-302.016 0c-8.16 0-14.816 6.656-14.816 14.816L350.944 237.12c0 25.536-20.768 46.304-46.304 46.304L172.448 283.424zM512 755.84c-107.04 0-194.08-87.072-194.08-194.08S404.992 367.68 512 367.68s194.08 87.072 194.08 194.08S619.04 755.84 512 755.84zM512 415.68c-80.576 0-146.08 65.536-146.08 146.08S431.456 707.84 512 707.84s146.08-65.536 146.08-146.08S592.576 415.68 512 415.68zM816.8 438.016c-25.568 0-46.336-20.768-46.336-46.336s20.768-46.336 46.336-46.336 46.336 20.768 46.336 46.336S842.368 438.016 816.8 438.016zM816.8 390.016l-1.664 1.664c0 0.896 0.736 1.664 1.664 1.664L816.8 390.016z"
fill="#cdcdcd"
p-id="5303"
></path>
</svg>
<view class="avatar-placeholder">
<view class="wh-full flex-col-center">
<svg
t="1729656215869"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5302"
width="32"
height="32"
>
<path
d="M851.552 890.88 172.448 890.88c-74.592 0-135.296-60.672-135.296-135.296L37.152 370.752c0-74.624 60.672-135.328 135.296-135.328l132.16 0L302.912 195.904c0-34.624 28.192-62.816 62.816-62.816l302.016 0c29.408 0 53.312 23.904 53.312 53.312l0 49.024 130.464 0c74.592 0 135.296 60.672 135.296 135.328l0 384.832C986.816 830.208 926.144 890.88 851.552 890.88zM172.448 283.456c-48.128 0-87.296 39.168-87.296 87.328l0 384.832c0 48.128 39.168 87.296 87.296 87.296l679.104 0c48.128 0 87.296-39.168 87.296-87.296L938.848 370.752c0-48.16-39.168-87.328-87.296-87.328L716.8 283.424c-24.096 0-43.712-19.616-43.712-43.712L673.088 186.4c0-2.944-2.368-5.312-5.312-5.312l-302.016 0c-8.16 0-14.816 6.656-14.816 14.816L350.944 237.12c0 25.536-20.768 46.304-46.304 46.304L172.448 283.424zM512 755.84c-107.04 0-194.08-87.072-194.08-194.08S404.992 367.68 512 367.68s194.08 87.072 194.08 194.08S619.04 755.84 512 755.84zM512 415.68c-80.576 0-146.08 65.536-146.08 146.08S431.456 707.84 512 707.84s146.08-65.536 146.08-146.08S592.576 415.68 512 415.68zM816.8 438.016c-25.568 0-46.336-20.768-46.336-46.336s20.768-46.336 46.336-46.336 46.336 20.768 46.336 46.336S842.368 438.016 816.8 438.016zM816.8 390.016l-1.664 1.664c0 0.896 0.736 1.664 1.664 1.664L816.8 390.016z"
fill="#cdcdcd"
p-id="5303"
></path>
</svg>
</view>
</view>
</CustomUpload>
</view>
</CustomUpload>
<!-- <text class="verify-title">身份验证</text> -->
</view>
<text class="avatar-upload-note">上传学生人像用于校园进出</text>
</view>
<!-- 输入框 -->
<view class="input-group">
<view class="input-item">
<text class="label"><text class="required">*</text>姓名:</text>
<input
class="input-field"
type="text"
v-model="formData.name"
placeholder="请输入姓名"
/>
</view>
<view class="input-item">
<text class="label"><text class="required">*</text>手机号码:</text>
<input
class="input-field"
type="number"
v-model="formData.phone"
placeholder="请输入手机号码"
maxlength="11"
/>
</view>
<view class="input-item verification-code-item">
<text class="label"><text class="required">*</text>验证码:</text>
<input
class="input-field verification-code-input"
type="number"
v-model="formData.code"
placeholder="请输入验证码"
maxlength="6"
/>
<button
class="get-code-btn"
:disabled="isCountingDown"
@click="handleGetCode"
>
{{ countdownText }}
</button>
<view class="student-info-form">
<u--form label-width="auto">
<u-form-item
label="姓名"
:prop="`students[${index}].xcxm`"
required
borderBottom
>
<u--input
v-model="student.xcxm"
placeholder="请输入子女姓名"
border="none"
inputAlign="right"
></u--input>
</u-form-item>
<u-form-item
label="身份证号"
:prop="`students[${index}].xssfzh`"
required
borderBottom
>
<u--input
v-model="student.xssfzh"
placeholder="请输入子女身份证号"
border="none"
inputAlign="right"
></u--input>
</u-form-item>
</u--form>
</view>
</view>
<view class="add-child-btn mb-15" @click="addMoreChildren">
<u-icon name="plus" color="#416AF2" size="20"></u-icon>
<text class="add-child-btn-text">新增多孩</text>
</view>
<!-- 验证按钮 -->
<button class="verify-btn" @click="handleVerify">验证</button>
<BasicForm @register="register"></BasicForm>
</view>
</view>
<template #bottom>
<view class="white-bg-color py-5">
<view class="flex-row items-center pb-10 pt-5">
<u-button text="提交" class="mx-15" type="primary" @click="submit" />
</view>
</view>
</template>
</BasicLayout>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from "vue";
import CustomUpload from "/src/components/BasicUpload/CustomUpload.vue";
import { onUnmounted } from "vue";
import { hideLoading, showLoading } from "@/utils/uniapp";
import { hideLoading, showLoading, showToast } from "@/utils/uniapp";
import { ref } from "vue";
import { attachmentUpload } from "@/api/system/upload";
import CustomUpload from "/src/components/BasicUpload/CustomUpload.vue";
const formData = reactive({
name: "",
phone: "",
code: "",
avatar_url: "",
import { useForm } from "@/components/BasicForm/hooks/useForm";
import { dicApi } from "@/api/system/dic";
import { loginRegisterJzApi } from "@/api/base/server";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
const [register, { getValue }] = useForm({
formsProps: { labelWidth: 100 },
schema: [
{ title: "监督人信息" },
{
field: "jzxsgxId",
label: "与学生关系",
component: "BasicPicker",
componentProps: {
api: dicApi,
param: { pid: 1622287061 },
rangeKey: "dictionaryValue",
savaKey: "dictionaryCode",
},
},
{
field: "jzxm",
label: "家长姓名",
component: "BasicInput",
required: true,
componentProps: {},
},
{
field: "jzsj",
label: "家长手机号",
component: "BasicInput",
required: true,
componentProps: {},
},
{
field: "gzdw",
label: "家长工作单位",
component: "BasicInput",
componentProps: {},
},
{
field: "zw",
label: "职务",
component: "BasicInput",
componentProps: {},
},
{
field: "jtzz",
label: "家庭地址",
component: "BasicInput",
componentProps: {},
},
],
});
const countdown = ref(60);
const isCountingDown = ref(false);
let timer: NodeJS.Timeout | null = null;
const countdownText = computed(() => {
return isCountingDown.value ? `${countdown.value}s后重试` : "获取验证码";
});
const students = ref([
{
xcxm: "",
xssfzh: "",
xstx: "",
},
]);
const handleGetCode = () => {
if (isCountingDown.value) {
async function afterRead(event: any, index: number) {
if (!event.tempFilePaths || event.tempFilePaths.length === 0) {
showToast({ title: "图片选择失败", icon: "none" });
return;
}
if (!formData.phone) {
uni.showToast({ title: "请输入手机号码", icon: "none" });
return;
}
console.log("获取验证码,手机号:", formData.phone);
isCountingDown.value = true;
countdown.value = 60; //
timer = setInterval(() => {
if (countdown.value > 1) {
countdown.value--;
} else {
if (timer) clearInterval(timer);
timer = null;
isCountingDown.value = false;
}
}, 1000);
};
const handleVerify = () => {
if (
!formData.name ||
!formData.phone ||
!formData.code ||
!formData.avatar_url
) {
uni.showToast({ title: "请填写完整的验证信息", icon: "none" });
return;
}
console.log("提交验证信息:", formData);
uni.showToast({ title: "验证成功 (模拟)", icon: "success" });
};
async function afterRead(event: any) {
const tempFilePath = event.tempFilePaths[0];
showLoading({ title: "上传中" });
const { result } = await attachmentUpload(event.tempFilePaths[0]);
hideLoading();
formData.avatar_url = result[0].filePath;
try {
const { result } = await attachmentUpload(tempFilePath);
if (result && result.length > 0 && result[0].filePath) {
students.value[index].xstx = result[0].filePath;
console.log(`Student ${index} avatar uploaded:`, result[0].filePath);
showToast({ title: "上传成功" });
} else {
showToast({ title: "上传失败,请重试", icon: "none" });
console.error("Upload result format error:", result);
}
} catch (error) {
showToast({ title: "上传出错", icon: "none" });
console.error("Upload error:", error);
} finally {
hideLoading();
}
}
onUnmounted(() => {
if (timer) {
clearInterval(timer);
function handleAvatarClose(index: number) {
students.value[index].xstx = "";
}
function addMoreChildren() {
students.value.push({
xcxm: "",
xssfzh: "",
xstx: "",
});
}
function removeStudent(index: number) {
if (students.value.length > 1) {
students.value.splice(index, 1);
} else {
showToast({ title: "至少需要一个子女信息", icon: "none" });
}
});
}
function toHome() {
if (getGlobal.type == 1) {
uni.reLaunch({
url: "/pages/base/course-selection/notice",
});
} else if (getGlobal.type == 2) {
uni.reLaunch({
url: "/pages/base/course-selection/club-selection",
});
} else {
uni.reLaunch({
url: "/pages/base/home/index",
});
}
}
const { getGlobal } = useDataStore();
const { afterLoginAction } = useUserStore();
async function submit() {
for (const student of students.value) {
if (!student.xstx) {
showToast({ title: "请上传子女照片", icon: "none" });
return;
}
if (!student.xcxm) {
showToast({ title: "请输入子女姓名", icon: "none" });
return;
}
if (!student.xssfzh) {
showToast({ title: "请输入子女身份证号", icon: "none" });
return;
}
}
const formData = await getValue();
showLoading({ title: "提交中" });
try {
const res = await loginRegisterJzApi({
xsList: students.value,
...formData,
openId: getGlobal.openId,
appCode: "JZ",
});
hideLoading();
if (res.resultCode == 1) {
afterLoginAction(res.result);
toHome();
} else {
showToast({ title: res.message || "提交失败", icon: "none" });
}
} catch (error) {
console.log(error);
}
}
</script>
<style scoped lang="scss">
.register-container {
display: flex;
flex-direction: column;
align-items: center;
// height: 100vh; // 使 min-height
min-height: 100vh;
background: url("@/static/base/bg.jpg") no-repeat;
background-size: 100% 100%;
padding: 20px;
box-sizing: border-box;
<style lang="scss" scoped>
.page-container {
padding: 15px; /* Consistent padding */
}
.logo {
width: 250px; // logo
height: 60px; // logo
margin-top: 40px; //
margin-bottom: 30px;
.notice-box {
background-color: #fff1f0; /* Slightly lighter red */
padding: 12px 18px;
border-radius: 8px;
margin-bottom: 15px;
}
.form-card {
.notice-text {
color: #fa541c; /* Adjusted red color */
font-size: 13px;
}
.form-section {
background-color: #ffffff;
border-radius: 15px;
padding: 30px 25px;
box-sizing: border-box;
width: 100%;
max-width: 400px; //
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
align-items: center;
border-radius: 10px;
padding: 20px 20px 0 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.06);
/* Added margin-bottom directly here for spacing between sections */
margin-bottom: 5px;
position: relative;
}
.avatar-section {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 25px;
margin-bottom: 25px; /* Increased margin */
}
.avatar {
width: 70px;
height: 70px;
border-radius: 50%;
margin-bottom: 10px;
background-color: #f0f0f0; //
/* Keep avatar uploader styles relevant to CustomUpload */
/* Replace with rectangular styles */
.avatar-uploader-container-rect {
/* Assuming uni.rpx units based on class names like wi-180, he-240 */
width: 180rpx;
height: 240rpx;
margin: 0 auto 10px auto; /* mx-auto mb-10 */
border-radius: 6px; /* r-md approximation */
border: 1px solid #cccccc;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
background-color: #fafafa;
}
.verify-title {
font-size: 16px;
color: #333;
font-weight: bold;
}
/* Remove old circular styles */
/* .avatar-uploader-container { ... } */
.input-group {
.avatar-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
margin-bottom: 20px;
height: 100%;
cursor: pointer;
}
.input-item {
/* Keep inner placeholder for centering SVG */
.avatar-placeholder-inner {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
border-bottom: 1px solid #eee; // 线
padding-bottom: 10px;
justify-content: center;
}
.label {
width: 80px; //
font-size: 14px;
color: #666;
flex-shrink: 0; //
display: flex; // 使
align-items: center;
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.required {
color: red;
margin-right: 4px;
/* Remove old hint style */
/* .avatar-upload-hint { ... } */
.avatar-upload-note {
color: #999;
font-size: 13px;
}
.input-field {
flex-grow: 1;
font-size: 14px;
border: none; //
outline: none; //
padding: 5px 0; //
color: #333;
.student-info-form {
margin-bottom: 20px; /* Restore original margin */
}
.verification-code-item {
display: flex;
align-items: center;
justify-content: space-between; //
.form-title {
display: block;
font-size: 16px; /* Slightly larger title */
font-weight: 600; /* Bolder */
margin-bottom: 15px; /* Space below title */
}
.verification-code-input {
flex-grow: 1; //
margin-right: 10px; //
/* Restore remove button style if needed, or adapt */
/* Updated remove button style */
.remove-btn {
position: absolute;
top: 10px; /* Closer to top edge */
right: 10px; /* Closer to right edge */
cursor: pointer;
z-index: 10;
/* Optional: add padding for easier clicking */
// padding: 5px;
}
.get-code-btn {
.add-child-btn {
background-color: #ffffff;
color: #007aff; //
border: 1px solid #007aff; //
font-size: 12px;
padding: 8px 10px;
border-radius: 20px;
white-space: nowrap; //
line-height: 1; //
height: auto; //
margin: 0; //
flex-shrink: 0; //
border-radius: 10px;
padding: 15px; /* Increased padding */
display: flex;
justify-content: center;
align-items: center;
margin-top: 10px; /* Increased margin */
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.06);
cursor: pointer;
}
.get-code-btn[disabled] {
background-color: #f8f8f8;
color: #cccccc;
border-color: #cccccc;
}
//
.get-code-btn::after {
border: none;
.add-child-btn-text {
color: #416af2;
margin-left: 8px;
font-size: 15px;
font-weight: 500;
}
.verify-btn {
width: 100%;
height: 45px;
line-height: 45px;
background: linear-gradient(to right, #ff8c4a, #ff5e62); //
color: #ffffff;
font-size: 16px;
border-radius: 25px;
margin-top: 10px;
border: none; //
box-shadow: 0 2px 5px rgba(255, 100, 100, 0.3); //
}
//
.verify-btn::after {
border: none;
/* Adjust uview form item styles */
::v-deep .u-form-item {
margin-bottom: 0; /* Remove default margin if any */
}
// placeholder
input::placeholder {
color: #cccccc;
font-size: 14px;
::v-deep .u-form-item__body {
padding: 15px 0 !important; /* Adjusted padding */
/* Align items vertically if label wraps */
// align-items: flex-start;
}
::v-deep .u-form-item__body__left {
/* Allow label to take necessary width, adjust as needed */
// flex: 0 0 80px;
}
::v-deep .u-form-item__body__left__text {
font-size: 15px;
color: #333;
line-height: 1.5; /* Improve line spacing if label wraps */
/* Ensure required asterisk is red and BEFORE the text */
display: flex; /* Use flex to control order */
align-items: center; /* Vertically center asterisk and text */
span {
color: #f56c6c;
/* Order asterisk first */
order: -1;
margin-right: 4px;
/* Adjust vertical alignment if needed */
// line-height: 1;
// display: inline-block;
// vertical-align: middle;
}
}
::v-deep .u-input {
text-align: right; /* Align input text to the right */
}
::v-deep .u-border-bottom {
/* Ensure border spans full width if needed, or adjust */
// left: 0 !important;
// right: 0 !important;
}
</style>

View File

@ -1,23 +1,44 @@
import {defineStore} from "pinia";
import { defineStore } from "pinia";
export const useDataStore = defineStore({
id: 'data',
state: () => ({
data: {}
}),
getters: {
getData(): any {
return this.data
}
id: "data",
state: () => ({
data: {},
kcData: {},
global: {},
file: {},
}),
getters: {
getData(): any {
return this.data;
},
actions: {
setData(data: any) {
this.data = data
}
getGlobal(): any {
return this.global;
},
persist: {
enabled: true,
detached: true,
H5Storage: localStorage
getFile(): any {
return this.file;
},
})
getKcData(): any {
return this.kcData;
},
},
actions: {
setData(data: any) {
this.data = data;
},
setGlobal(data: any) {
this.global = data;
},
setFile(data: any) {
this.file = data;
},
setKcData(data: any) {
this.kcData = data;
},
},
persist: {
enabled: true,
detached: true,
H5Storage: localStorage,
},
});

View File

@ -80,7 +80,7 @@ export const useUserStore = defineStore({
if (value[AUTH_KEY]) {
this.setToken(value[AUTH_KEY])
}
authenticationApi({userId: value.userid}).then(({result}) => {
authenticationApi({userId: value.userId}).then(({result}) => {
if (result) {
this.setAuth(result)
}