保存数据
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
|
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
|
||||||
import { NvpMerchService } from '@/Service/Nvp/NvpMerchService'
|
|
||||||
onLaunch(() => {
|
onLaunch(() => {
|
||||||
console.log("App Launch");
|
console.log("App Launch");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
export class BaseConfig {
|
export class BaseConfig {
|
||||||
// protected static servesUrl: string = "http://192.168.0.190:8806";//线下
|
protected static servesUrl: string = "http://192.168.0.142:5298";
|
||||||
|
protected static imgUrl: string = "http://192.168.0.142:5298";
|
||||||
|
protected static mediaUrl: string = "http://192.168.0.142:5298/";
|
||||||
|
|
||||||
protected static servesUrl: string = "http://vp.xypays.cn";
|
// protected static servesUrl: string = "http://vp.xypays.cn";
|
||||||
protected static imgUrl: string = "http://vp.cloud.xypays.cn";
|
// protected static imgUrl: string = "http://vp.cloud.xypays.cn";
|
||||||
protected static mediaUrl: string = "http://byc1.xypays.cn/";
|
// protected static mediaUrl: string = "http://byc1.xypays.cn/";
|
||||||
protected static uploadUrl: string = "/TencentCos/GetUpLoadInfo";
|
protected static uploadUrl: string = "/TencentCos/GetUpLoadInfo";
|
||||||
protected static payuploadUrl: string = "http://pay.xypays.cn";
|
protected static payuploadUrl: string = "http://192.168.0.142:5298";
|
||||||
}
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****登录接口*****/
|
|
||||||
class NvpAddressService {
|
|
||||||
private static GetAddressListPath: string = '/Address/GetAddressList';
|
|
||||||
/*****收货地址*****/
|
|
||||||
static GetAddressList(page: number) {
|
|
||||||
var result = Service.Request(this.GetAddressListPath, "GET", { page });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DelUserAddressPath: string = '/Address/DelUserAddress';
|
|
||||||
/*****删除地址*****/
|
|
||||||
static DelUserAddress(uaId: string) {
|
|
||||||
var result = Service.Request(this.DelUserAddressPath, "POST", { uaId });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AddUserAddressPath: string = '/Address/AddUserAddress';
|
|
||||||
/*****添加地址*****/
|
|
||||||
static AddUserAddress(uaId: string, name: string, phone: string, province: string, city: string, county: string, address: string, isDefault: number) {
|
|
||||||
var result = Service.Request(this.AddUserAddressPath, "POST", { uaId, name, phone, province, city, county, address, isDefault });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UpdateUserAddressPath: string = '/Address/UpdateUserAddress';
|
|
||||||
/*****修改默认地址*****/
|
|
||||||
static UpdateUserAddress(uaId: string) {
|
|
||||||
var result = Service.Request(this.UpdateUserAddressPath, "POST", { uaId });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpAddressService
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****代理端接口*****/
|
|
||||||
class NvpAgentService {
|
|
||||||
private static LoginPath: string = '/Agent/Login';
|
|
||||||
/*****登录接口*****/
|
|
||||||
static Login(name: string, pwd: string) {
|
|
||||||
var result = Service.Request(this.LoginPath, "POST", { name, pwd });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetAgentAccInfoPath: string = '/Agent/GetAgentAccInfo';
|
|
||||||
/*****账户接口*****/
|
|
||||||
static GetAgentAccInfo() {
|
|
||||||
var result = Service.Request(this.GetAgentAccInfoPath, "GET", "");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetAgentAccLogPath: string = '/Agent/GetAgentAccLog';
|
|
||||||
/*****账户记录*****/
|
|
||||||
static GetAgentAccLog(code: string, page: number) {
|
|
||||||
var result = Service.Request(this.GetAgentAccLogPath, "GET", { code, page });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetAgentMerchPath: string = '/Agent/GetAgentMerch';
|
|
||||||
/*****获取代理开通商家*****/
|
|
||||||
static GetAgentMerch(type: number, page: number) {
|
|
||||||
var result = Service.Request(this.GetAgentMerchPath, "GET", { type, page });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetAgentHomePath: string = '/Agent/GetAgentHome';
|
|
||||||
/*****代理主页*****/
|
|
||||||
static GetAgentHome() {
|
|
||||||
var result = Service.Request(this.GetAgentHomePath, "GET", { });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpAgentService
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****用户接口*****/
|
|
||||||
class NvpApplyService {
|
|
||||||
// private static WithDrawPath: string = '/With/WithDraw';
|
|
||||||
// /*****佣金提现*****/
|
|
||||||
// static WithDraw(money: number, name: string, account: string) {
|
|
||||||
// var result = Service.Request(this.WithDrawPath, "POST", { money, name, account });
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
private static GetSiteMccCodeListPath: string = '/Apply/GetSiteMccCodeList';
|
|
||||||
/*****获取mcc列表*****/
|
|
||||||
static GetSiteMccCodeList(mercType:string, mchType: string) {
|
|
||||||
var result = Service.Request(this.GetSiteMccCodeListPath, "GET", {mercType, mchType });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static GetBankTypeListPath: string = '/Apply/GetBankTypeList';
|
|
||||||
/*****获取银行列表*****/
|
|
||||||
static GetBankTypeList(name:string) {
|
|
||||||
var result = Service.Request(this.GetBankTypeListPath, "GET", {name});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static GetAreaListPath: string = '/Apply/GetAreaList';
|
|
||||||
/*****获取银行地区列表*****/
|
|
||||||
static GetAreaList(areaCode:string) {
|
|
||||||
var result = Service.Request(this.GetAreaListPath, "GET", { areaCode});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static GetBankCodeListPath: string = '/Apply/GetBankCodeList';
|
|
||||||
/*****获取银行代码列表*****/
|
|
||||||
static GetBankCodeList(bankType:string,cityCode:string,name:string) {
|
|
||||||
var result = Service.Request(this.GetBankCodeListPath, "GET", {bankType,cityCode,name});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static SendApplyMerchPath: string = '/Apply/SendApplyMerch';
|
|
||||||
/*****进价提交*****/
|
|
||||||
static SendApplyMerch(para:any) {
|
|
||||||
var result = Service.Request(this.SendApplyMerchPath, "POST", para);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetAgentMerchLogPath: string = '/Apply/GetAgentMerchLog';
|
|
||||||
/*****获取待审核*****/
|
|
||||||
static GetAgentMerchLog() {
|
|
||||||
var result = Service.Request(this.GetAgentMerchLogPath, "GET", {});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static AuditApplyPath: string = '/Apply/AuditApply';
|
|
||||||
/*****确认资料*****/
|
|
||||||
static AuditApply(outId:string) {
|
|
||||||
var result = Service.Request(this.AuditApplyPath, "POST", {outId});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SetPayFeePath: string = '/Apply/SetPayFee';
|
|
||||||
/*****确认资料*****/
|
|
||||||
static SetPayFee(outId:string) {
|
|
||||||
var result = Service.Request(this.SetPayFeePath, "POST", {outId});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static GetAssortListPath: string = '/Apply/GetAssortList';
|
|
||||||
/*****获取v派分类*****/
|
|
||||||
static GetAssortList(code:string) {
|
|
||||||
var result = Service.Request(this.GetAssortListPath, "GET", {code,parent:'0'});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static GetAgentMerchPath: string = '/Agent/GetAgentMerch';
|
|
||||||
/*****获取已开通商家*****/
|
|
||||||
static GetAgentMerch(type:number,page:number) {
|
|
||||||
var result = Service.Request(this.GetAgentMerchPath, "GET", {type,page});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static GetAppMerchInfoPath: string = '/Apply/GetAppMerchInfo';
|
|
||||||
/*****获取银盛已填写信息*****/
|
|
||||||
static GetAppMerchInfo(outId:string) {
|
|
||||||
var result = Service.Request(this.GetAppMerchInfoPath, "GET", {outId});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static UpdateMerchPath: string = '/Apply/UpdateMerch';
|
|
||||||
/*****修改银盛信息*****/
|
|
||||||
static UpdateMerch(obj:any) {
|
|
||||||
var result = Service.Request(this.UpdateMerchPath, "POST", obj);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static UploadImgPath: string = '/Apply/UploadImg';
|
|
||||||
/*****修改银盛图片*****/
|
|
||||||
static UploadImg(outId:string,picType:string,img:string) {
|
|
||||||
var result = Service.Request(this.UploadImgPath, "POST", {outId,picType,img});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static GetAgentApplyPath: string = '/Apply/GetAgentApply';
|
|
||||||
/*****获取添加的商家*****/
|
|
||||||
static GetAgentApply(serch:string,page:number) {
|
|
||||||
var result = Service.Request(this.GetAgentApplyPath, "GET", {serch,page});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetCategoryPath: string = '/Apply/GetCategory';
|
|
||||||
/*****获取商家类型*****/
|
|
||||||
static GetCategory() {
|
|
||||||
var result = Service.Request(this.GetCategoryPath, "GET", {});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static AddMerchInfoPath: string = '/Apply/AddMerchInfo';
|
|
||||||
/*****添加商户*****/
|
|
||||||
static AddMerchInfo(obj:any) {
|
|
||||||
var result = Service.Request(this.AddMerchInfoPath, "POST", obj);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static BandAppIdPath: string = '/Agent/BandAppId';
|
|
||||||
/*****绑定appid*****/
|
|
||||||
static BandAppId(merchId:string) {
|
|
||||||
var result = Service.Request(this.BandAppIdPath, "POST", {merchId});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpApplyService
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****登录接口*****/
|
|
||||||
class NvpBankService {
|
|
||||||
private static GetPageBankListPath: string = '/Bank/GetPageBankList';
|
|
||||||
/*****用户银行卡列表*****/
|
|
||||||
static GetPageBankList(page: number) {
|
|
||||||
var result = Service.Request(this.GetPageBankListPath, "GET", { page });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetBankListPath: string = '/Bank/GetBankList';
|
|
||||||
/*****用户银行卡列表*****/
|
|
||||||
static GetBankList() {
|
|
||||||
var result = Service.Request(this.GetBankListPath, "GET", "");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetUnitBankListPath: string = '/Bank/GetUnitBankList';
|
|
||||||
/*****银行卡列表*****/
|
|
||||||
static GetUnitBankList() {
|
|
||||||
var result = Service.Request(this.GetUnitBankListPath, "GET", "");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AddUserBankPath: string = '/Bank/AddUserBank';
|
|
||||||
/*****添加银行卡*****/
|
|
||||||
static AddUserBank(account: string, bank: string, name: string) {
|
|
||||||
var result = Service.Request(this.AddUserBankPath, "POST", { account, bank, name });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UpdateBankPath: string = '/Bank/UpdateBank';
|
|
||||||
/*****修改银行卡*****/
|
|
||||||
static UpdateBank(ubId: string, bank: string, name: string, account: string) {
|
|
||||||
var result = Service.Request(this.UpdateBankPath, "POST", { ubId, bank, name, account });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DelUserBankPath: string = '/Bank/DelUserBank';
|
|
||||||
/*****删除银行卡*****/
|
|
||||||
static DelUserBank(ubId: string) {
|
|
||||||
var result = Service.Request(this.DelUserBankPath, "POST", { ubId });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpBankService
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****登录接口*****/
|
|
||||||
class NvpLoginService {
|
|
||||||
private static GetWxLoginOpenIdPath: string = '/Login/GetWxLoginOpenId';
|
|
||||||
/*****小程序根据code获取openId*****/
|
|
||||||
static GetWxLoginOpenId(code: string, type: number) {
|
|
||||||
var result = Service.Request(this.GetWxLoginOpenIdPath, "GET", { code, type });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AppletLoginPath: string = '/Login/AppletLogin';
|
|
||||||
/*****普通登陆接口(小程序)*****/
|
|
||||||
static AppletLogin(openId: string, channel: string) {
|
|
||||||
var result = Service.Request(this.AppletLoginPath, "POST", { openId, channel });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RegistPath: string = '/Login/Regist';
|
|
||||||
/*****注册接口*****/
|
|
||||||
static Regist(remNo: string, openId: string, uniopenId: string, channel: string, type: number) {
|
|
||||||
var result = Service.Request(this.RegistPath, "POST", { remNo, openId, uniopenId, channel, type });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetNumberPhonePath: string = '/Login/GetNumberPhone';
|
|
||||||
/*****获取手机号*****/
|
|
||||||
static GetNumberPhone(code: string) {
|
|
||||||
var result = Service.Request(this.GetNumberPhonePath, "GET", { code });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpLoginService
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****设备接口*****/
|
|
||||||
class NvpMachineService {
|
|
||||||
|
|
||||||
private static GetMerchListPath: string = '/Machine/GetMerchList';
|
|
||||||
/*****商户设备*****/
|
|
||||||
static GetMerchList(merchId: string) {
|
|
||||||
var result = Service.Request(this.GetMerchListPath, "GET", { merchId });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DelMerchMachinePath: string = '/Machine/DelMerchMachine';
|
|
||||||
/*****删除设备*****/
|
|
||||||
static DelMerchMachine(merchId: string, machineId: string) {
|
|
||||||
var result = Service.Request(this.DelMerchMachinePath, "POST", { merchId, machineId });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AddMachinePath: string = '/Machine/AddMachine';
|
|
||||||
/*****添加设备*****/
|
|
||||||
static AddMachine(merchId: string, payCode: string) {
|
|
||||||
var result = Service.Request(this.AddMachinePath, "POST", { merchId, payCode });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpMachineService
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****用户接口*****/
|
|
||||||
class NvpMerchService {
|
|
||||||
|
|
||||||
|
|
||||||
private static GetMerchInfoPath: string = '/Merch/GetMerchInfo';
|
|
||||||
/*****获取商家信息*****/
|
|
||||||
static GetMerchInfo(merchId: string,lon:number,lat:number) {
|
|
||||||
var result = Service.Request(this.GetMerchInfoPath, "GET", { merchId,lon,lat});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static GetMerchOrderPath: string = '/Merch/GetMerchOrder';
|
|
||||||
/*****获取商家营业数据*****/
|
|
||||||
static GetMerchOrder(merchId: string) {
|
|
||||||
var result = Service.Request(this.GetMerchOrderPath, "GET", { merchId });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static UpdateMerchInfoPath: string = '/Merch/UpdateMerchInfo';
|
|
||||||
/*****修改商家信息*****/
|
|
||||||
static UpdateMerchInfo(merchId: string, logo: string ,name:string, phone:string,province:string,city:string,county:string,address:string,lon:string,lat:string,sTime:string,eTime:string,showImg:string) {
|
|
||||||
var result = Service.Request(this.UpdateMerchInfoPath, "POST", { merchId, logo,name ,phone,province,city,county,address,lon,lat,sTime,eTime,showImg});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static GetMerchAccPath: string = '/Merch/GetMerchAcc';
|
|
||||||
/*****获取商家佣金数据*****/
|
|
||||||
static GetMerchAcc(merchId: string) {
|
|
||||||
var result = Service.Request(this.GetMerchAccPath, "GET", { merchId });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetMerchAccLogPath: string = '/Merch/GetMerchAccLog';
|
|
||||||
/*****获取商家佣金记录*****/
|
|
||||||
static GetMerchAccLog(merchId: string,op:string,page:number) {
|
|
||||||
var result = Service.Request(this.GetMerchAccLogPath, "GET", { merchId,op,page });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static MerchWithDrawPath: string = '/Merch/MerchWithDraw';
|
|
||||||
/*****商家佣金提现*****/
|
|
||||||
static MerchWithDraw(merchId: string, money: number ,name:string, account:string) {
|
|
||||||
var result = Service.Request(this.MerchWithDrawPath, "POST", { merchId, money,name,account});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static GetMerchWithLogPath: string = '/Merch/GetMerchWithLog';
|
|
||||||
/*****商家佣金提现记录*****/
|
|
||||||
static GetMerchWithLog(merchId: string) {
|
|
||||||
var result = Service.Request(this.GetMerchWithLogPath, "GET", { merchId });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetMerchTicketListPath: string = '/Merch/GetMerchTicketList';
|
|
||||||
/*****商家优惠券列表*****/
|
|
||||||
static GetMerchTicketList(merchId: string) {
|
|
||||||
var result = Service.Request(this.GetMerchTicketListPath, "GET", { merchId });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static AddMerchTicketPath: string = '/Merch/AddMerchTicket';
|
|
||||||
/*****商家优惠券修改添加*****/
|
|
||||||
static AddMerchTicket(ticketId: string, merchId: string ,code:string, atkAcc:number,redAcc:number,count:number,useTime:Number,state:Number,addTime:string,endTime:string) {
|
|
||||||
var result = Service.Request(this.AddMerchTicketPath, "POST", { ticketId, merchId,code,atkAcc,redAcc,count,useTime,state,addTime,endTime});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static DelMerchTicketPath: string = '/Merch/DelMerchTicket';
|
|
||||||
/*****商家删除优惠券*****/
|
|
||||||
static DelMerchTicket(ticketId: string) {
|
|
||||||
var result = Service.Request(this.DelMerchTicketPath, "POST", { ticketId});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static GetTicketInfoPath: string = '/Merch/GetTicketInfo';
|
|
||||||
/*****获取优惠券详情*****/
|
|
||||||
static GetTicketInfo(ticketId: string) {
|
|
||||||
var result = Service.Request(this.GetTicketInfoPath, "GET", { ticketId });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static GetAppVersionPath: string = '/Login/GetAppVersion';
|
|
||||||
/*****更新*****/
|
|
||||||
static GetAppVersion() {
|
|
||||||
var result = Service.Request(this.GetAppVersionPath, "GET", {type:2});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpMerchService
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****公共接口*****/
|
|
||||||
class NvpPubService {
|
|
||||||
private static GetIndexPath: string = '/Pub/GetIndex';
|
|
||||||
/*****主页信息*****/
|
|
||||||
static GetIndex() {
|
|
||||||
var result = Service.Request(this.GetIndexPath, "GET", "");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetIndexDataPath: string = '/Pub/GetIndexData';
|
|
||||||
/*****获取首页数据*****/
|
|
||||||
static GetIndexData(lon: number, lat: number, city: string, county: string, sort: number, page: number) {
|
|
||||||
var result = Service.Request(this.GetIndexDataPath, "GET", { lon, lat, city, county, sort, page });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetMenuDataPath: string = '/Pub/GetMenuData';
|
|
||||||
/*****获取分类*****/
|
|
||||||
static GetMenuData(type: string, parent: string) {
|
|
||||||
var result = Service.Request(this.GetMenuDataPath, "GET", { type, parent });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetMerchDataPath: string = '/Pub/GetMerchData';
|
|
||||||
/*****获取店铺*****/
|
|
||||||
static GetMerchData(code: string, serch: string, assId: string, lon: number, lat: number, city: string, county: string, sort: number, page: number, limit: number) {
|
|
||||||
var result = Service.Request(this.GetMerchDataPath, "GET", { code, serch, assId, lon, lat, city, county, sort, page, limit });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetRandomMerchPath: string = '/Pub/GetRandomMerch';
|
|
||||||
/*****随机获取商家*****/
|
|
||||||
static GetRandomMerch(count: number, lon: number, lat: number, city: string, county: string) {
|
|
||||||
var result = Service.Request(this.GetRandomMerchPath, "GET", { count, lon, lat, city, county });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpPubService
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****腾讯云存储*****/
|
|
||||||
class NvpTencentCosService {
|
|
||||||
private static GetAuthorizationPath: string = '/TencentCos/GetAuthorization';
|
|
||||||
/*****获取云存储配置*****/
|
|
||||||
static GetAuthorization() {
|
|
||||||
var result = Service.Request(this.GetAuthorizationPath, "GET", "");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetUpLoadInfoPath: string = '/TencentCos/GetUpLoadInfo';
|
|
||||||
/*****获取上传地址*****/
|
|
||||||
static GetUpLoadInfo(code: string, fileName: string, desire: string) {
|
|
||||||
var result = Service.Request(this.GetUpLoadInfoPath, "GET", { code, fileName, desire });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpTencentCosService
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****用户接口*****/
|
|
||||||
class NvpUserService {
|
|
||||||
private static GetUserInfoPath: string = '/User/GetUserInfo';
|
|
||||||
/*****用户基础信息*****/
|
|
||||||
static GetUserInfo() {
|
|
||||||
var result = Service.Request(this.GetUserInfoPath, "GET", "");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UpdateUserInfoPath: string = '/User/UpdateUserInfo';
|
|
||||||
/*****修改用户信息*****/
|
|
||||||
static UpdateUserInfo(par: string, smsCode: string, type: number) {
|
|
||||||
var result = Service.Request(this.UpdateUserInfoPath, "POST", { par, smsCode, type });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetUserAccLogPath: string = '/User/GetUserAccLog';
|
|
||||||
/*****获取账户记录*****/
|
|
||||||
static GetUserAccLog(code: string, accType: string, page: number) {
|
|
||||||
var result = Service.Request(this.GetUserAccLogPath, "GET", { code, accType, page });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetUserCommissionInfoPath: string = '/User/GetUserCommissionInfo';
|
|
||||||
/*****用户佣金账户*****/
|
|
||||||
static GetUserCommissionInfo() {
|
|
||||||
var result = Service.Request(this.GetUserCommissionInfoPath, "GET", "");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetUserCommissionLogPath: string = '/User/GetUserCommissionLog';
|
|
||||||
/*****用户佣金记录*****/
|
|
||||||
static GetUserCommissionLog(code: string, accType: string, page: number) {
|
|
||||||
var result = Service.Request(this.GetUserCommissionLogPath, "GET", { code, accType, page });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpUserService
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Service } from '@/Service/Service';
|
|
||||||
/*****用户接口*****/
|
|
||||||
class NvpWithService {
|
|
||||||
private static WithDrawPath: string = '/With/WithDraw';
|
|
||||||
/*****佣金提现*****/
|
|
||||||
static WithDraw(money: number, name: string, account: string) {
|
|
||||||
var result = Service.Request(this.WithDrawPath, "POST", { money, name, account });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GetUserWithLogPath: string = '/With/GetUserWithLog';
|
|
||||||
/*****用户提现记录*****/
|
|
||||||
static GetUserWithLog(page: number) {
|
|
||||||
var result = Service.Request(this.GetUserWithLogPath, "GET", { page });
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
Service,
|
|
||||||
NvpWithService
|
|
||||||
}
|
|
||||||
@@ -336,7 +336,7 @@ export class Service extends BaseConfig {
|
|||||||
static uploadH5(path, dic, callback) {
|
static uploadH5(path, dic, callback) {
|
||||||
console.log(this.payuploadUrl,'xxx')
|
console.log(this.payuploadUrl,'xxx')
|
||||||
uni.uploadFile({
|
uni.uploadFile({
|
||||||
url: this.payuploadUrl+'/Upload/UploadFile',
|
url: this.payuploadUrl+'/api/Upload/UploadImg',
|
||||||
method: "POST",
|
method: "POST",
|
||||||
header: {
|
header: {
|
||||||
'Authorization': 'Bearer ' + Service.GetUserToken(),
|
'Authorization': 'Bearer ' + Service.GetUserToken(),
|
||||||
|
|||||||
49
src/Service/swimming/PlanService.ts
Normal file
49
src/Service/swimming/PlanService.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****项目接口*****/
|
||||||
|
class PlanService {
|
||||||
|
private static GetPlanListPath : string = '/api/Plan/GetPlanList';
|
||||||
|
/*****计划列表接口*****/
|
||||||
|
static GetPlanList( planType:string, page:string) {
|
||||||
|
var result = Service.Request(this.GetPlanListPath, "GET", { planType, page});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetPlanInfoPath : string = '/api/Plan/GetPlanInfo';
|
||||||
|
/*****计划详情接口*****/
|
||||||
|
static GetPlanInfo(planId:string) {
|
||||||
|
var result = Service.Request(this.GetPlanInfoPath, "GET", {planId});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static AddPlanPath : string = '/api/Plan/AddPlan';
|
||||||
|
/*****添加修改计划接口*****/
|
||||||
|
static AddPlan(data:any) {
|
||||||
|
var result = Service.Request(this.AddPlanPath, "POST", data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static DeletePlanPath : string = '/api/Plan/DeletePlan';
|
||||||
|
/*****删除计划接口*****/
|
||||||
|
static DeletePlan(planId:string) {
|
||||||
|
var result = Service.Request(this.DeletePlanPath, "POST", { planId});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static GetNoSelectStudentPath : string = '/api/Plan/GetNoSelectStudent';
|
||||||
|
/*****查询项目分组未选择的学生接口*****/
|
||||||
|
static GetNoSelectStudent(planId:string) {
|
||||||
|
var result = Service.Request(this.GetNoSelectStudentPath, "GET", { planId});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
PlanService
|
||||||
|
}
|
||||||
14
src/Service/swimming/loginService.ts
Normal file
14
src/Service/swimming/loginService.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****登录接口*****/
|
||||||
|
class loginService {
|
||||||
|
private static LoginPath : string = '/api/Login/Login';
|
||||||
|
/*****登录接口*****/
|
||||||
|
static Login(code : string) {
|
||||||
|
var result = Service.Request(this.LoginPath, "POST", { code });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
loginService
|
||||||
|
}
|
||||||
47
src/Service/swimming/studentService.ts
Normal file
47
src/Service/swimming/studentService.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****登录接口*****/
|
||||||
|
class studentService {
|
||||||
|
private static GetStudentListPath : string = '/api/Student/GetStudentList';
|
||||||
|
/*****学生列表不分页接口*****/
|
||||||
|
static GetStudentList() {
|
||||||
|
var result = Service.Request(this.GetStudentListPath, "GET", { });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetStudentInfoPath : string = '/api/Student/GetStudentInfo';
|
||||||
|
/*****学生详情接口*****/
|
||||||
|
static GetStudentInfo(studentId : string) {
|
||||||
|
var result = Service.Request(this.GetStudentInfoPath, "GET", { studentId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AddPath : string = '/api/Student/Add';
|
||||||
|
/*****添加学生接口*****/
|
||||||
|
static Add(data:any) {
|
||||||
|
var result = Service.Request(this.AddPath, "POST", data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static DeletePath : string = '/api/Student/Delete';
|
||||||
|
/*****删除学生接口*****/
|
||||||
|
static Delete(studentId:string) {
|
||||||
|
var result = Service.Request(this.DeletePath, "POST", {studentId });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GetStudentListPagePath : string = '/api/Student/GetStudentListPage';
|
||||||
|
/*****学生列表分页接口*****/
|
||||||
|
static GetStudentListPage(page : string) {
|
||||||
|
var result = Service.Request(this.GetStudentListPagePath, "GET", { page });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
studentService
|
||||||
|
}
|
||||||
14
src/Service/swimming/upLoadService.ts
Normal file
14
src/Service/swimming/upLoadService.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****上传接口*****/
|
||||||
|
class loginService {
|
||||||
|
private static UploadImgPath : string = '/api/Upload/UploadImg';
|
||||||
|
/*****上传接口*****/
|
||||||
|
static UploadImg(file : string,path:string) {
|
||||||
|
var result = Service.Request(this.UploadImgPath, "POST", { file, path});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
loginService
|
||||||
|
}
|
||||||
37
src/Service/swimming/userService.ts
Normal file
37
src/Service/swimming/userService.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Service } from '@/Service/Service';
|
||||||
|
/*****用户接口*****/
|
||||||
|
class userService {
|
||||||
|
private static GetUserInfoPath : string = '/api/User/GetUserInfo';
|
||||||
|
/*****获取用户信息接口*****/
|
||||||
|
static GetUserInfo() {
|
||||||
|
var result = Service.Request(this.GetUserInfoPath, "GET", {});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static IsCheakUserVerifyPath : string = '/api/User/IsCheakUserVerify';
|
||||||
|
/*****是否可以使用接口*****/
|
||||||
|
static IsCheakUserVerify() {
|
||||||
|
var result = Service.Request(this.IsCheakUserVerifyPath, "GET", {});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UpdateUserInfoPath : string = '/api/User/UpdateUserInfo';
|
||||||
|
/*****修改用户信息接口*****/
|
||||||
|
static UpdateUserInfo(name:string,phone:string,headImg:string) {
|
||||||
|
var result = Service.Request(this.UpdateUserInfoPath, "POST", {name,phone,headImg});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// private static GetStudentInfoPath : string = '/api/Student/GetStudentInfo';
|
||||||
|
// /*****根据学生id查详情接口*****/
|
||||||
|
// static GetStudentInfo(studentId:string) {
|
||||||
|
// var result = Service.Request(this.GetStudentInfoPath, "GET", {studentId});
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
export {
|
||||||
|
Service,
|
||||||
|
userService
|
||||||
|
}
|
||||||
@@ -34,8 +34,7 @@
|
|||||||
{
|
{
|
||||||
"path": "swiming",
|
"path": "swiming",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "游泳项目",
|
"navigationBarTitleText": "游泳项目"
|
||||||
"navigationStyle": "custom"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -77,8 +76,26 @@
|
|||||||
{
|
{
|
||||||
"path": "project",
|
"path": "project",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "包干",
|
"navigationBarTitleText": "包干"
|
||||||
"navigationStyle": "custom"
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "plan",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "训练计划"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hunyang",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "混氧"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "addHunyang",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "创建项目"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -364,7 +364,7 @@
|
|||||||
selectedStudentIds.value.push(studentId)
|
selectedStudentIds.value.push(studentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStudentBestTime = (studentId : string) : number => {
|
const getStudentBestTime = (studentId : string) : number => {
|
||||||
const records = mockTrainingRecords[selectedProjectId.value] || []
|
const records = mockTrainingRecords[selectedProjectId.value] || []
|
||||||
const studentRecords = records.filter(r => r.studentId === studentId)
|
const studentRecords = records.filter(r => r.studentId === studentId)
|
||||||
@@ -444,132 +444,132 @@
|
|||||||
.subtitle {
|
.subtitle {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-count {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-select-section {
|
||||||
|
background-color: #fff;
|
||||||
|
margin: 0 20rpx 20rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 28rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.picker-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 20rpx;
|
justify-content: space-between;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 1rpx solid #e8e8e8;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
.section-title {
|
&:active {
|
||||||
font-size: 30rpx;
|
background-color: #f0f0f0;
|
||||||
font-weight: 600;
|
border-color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-text {
|
||||||
|
font-size: 28rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.select-count {
|
.student-select-section {
|
||||||
font-size: 24rpx;
|
background-color: #fff;
|
||||||
color: #52c41a;
|
margin: 0 20rpx 20rpx;
|
||||||
}
|
border-radius: 16rpx;
|
||||||
|
padding: 28rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
margin-bottom: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-select-section {
|
.student-list {
|
||||||
background-color: #fff;
|
display: grid;
|
||||||
margin: 0 20rpx 20rpx;
|
grid-template-columns: repeat(4, 1fr);
|
||||||
border-radius: 16rpx;
|
gap: 16rpx;
|
||||||
padding: 28rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.picker-wrapper {
|
.student-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
padding: 20rpx 10rpx;
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
border: 1rpx solid #e8e8e8;
|
border: 2rpx solid transparent;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #f0f0f0;
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: #f0f9eb;
|
||||||
border-color: #52c41a;
|
border-color: #52c41a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-text {
|
.student-avatar {
|
||||||
font-size: 28rpx;
|
width: 60rpx;
|
||||||
color: #333;
|
height: 60rpx;
|
||||||
}
|
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||||
}
|
border-radius: 50%;
|
||||||
}
|
|
||||||
|
|
||||||
.student-select-section {
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 0 20rpx 20rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 28rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.section-header {
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-list {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: 16rpx;
|
|
||||||
|
|
||||||
.student-item {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20rpx 10rpx;
|
justify-content: center;
|
||||||
background-color: #f8f8f8;
|
margin-bottom: 12rpx;
|
||||||
border-radius: 12rpx;
|
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
||||||
border: 2rpx solid transparent;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:active {
|
.avatar-text {
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background-color: #f0f9eb;
|
|
||||||
border-color: #52c41a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-avatar {
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
|
||||||
|
|
||||||
.avatar-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-name {
|
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #333;
|
font-weight: 700;
|
||||||
text-align: center;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.check-icon {
|
.student-name {
|
||||||
position: absolute;
|
font-size: 24rpx;
|
||||||
top: 8rpx;
|
color: #333;
|
||||||
right: 8rpx;
|
text-align: center;
|
||||||
width: 28rpx;
|
}
|
||||||
height: 28rpx;
|
|
||||||
background-color: #52c41a;
|
.check-icon {
|
||||||
border-radius: 50%;
|
position: absolute;
|
||||||
display: flex;
|
top: 8rpx;
|
||||||
align-items: center;
|
right: 8rpx;
|
||||||
justify-content: center;
|
width: 28rpx;
|
||||||
}
|
height: 28rpx;
|
||||||
|
background-color: #52c41a;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
<view class="paragraph-container">
|
<view class="paragraph-container">
|
||||||
<view class="header-section">
|
<view class="header-section">
|
||||||
<view class="header-title">
|
<view class="header-title">
|
||||||
<text class="title">">分段数据</text>
|
<text class="title">分段数据</text>
|
||||||
<text class="subtitle">学生分段训练成绩分析</text>
|
<text class="subtitle">学生分段训练成绩分析</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="select-section">
|
<view class="select-section">
|
||||||
<view class="select-label">
|
<view class="select-label">
|
||||||
<text class="label-text">">选择项目</text>
|
<text class="label-text">选择项目</text>
|
||||||
</view>
|
</view>
|
||||||
<picker mode="selector" :range="projectOptions" range-key="name" :value="selectedProjectIndex"
|
<picker mode="selector" :range="projectOptions" range-key="name" :value="selectedProjectIndex"
|
||||||
@change="handleProjectChange">
|
@change="handleProjectChange">
|
||||||
@@ -192,18 +192,18 @@
|
|||||||
const mockData: Record<string, Record<string, any[]>> = {
|
const mockData: Record<string, Record<string, any[]>> = {
|
||||||
'1': {
|
'1': {
|
||||||
'2026-03-12': [
|
'2026-03-12': [
|
||||||
{ name: '王小明', projectName: '100米'自由泳', segment20: 0.037, segment70: 0.128, segment120: 0.219, segment170: 0.311 },
|
{ name: '王小明', projectName: '100米自由泳', segment20: 0.037, segment70: 0.128, segment120: 0.219, segment170: 0.311 },
|
||||||
{ name: '张小红', projectName: '100米'自由泳', segment20: 0.042, segment70: 0.145, segment120: 0.248, segment170: 0.352 },
|
{ name: '张小红', projectName: '100米自由泳', segment20: 0.042, segment70: 0.145, segment120: 0.248, segment170: 0.352 },
|
||||||
{ name: '李小龙', projectName: '100米'自由泳', segment20: 0.032, segment70: 0.112, segment120: 0.192, segment170: 0.272 }
|
{ name: '李小龙', projectName: '100米自由泳', segment20: 0.032, segment70: 0.112, segment120: 0.192, segment170: 0.272 }
|
||||||
],
|
],
|
||||||
'2026-03-14': [
|
'2026-03-14': [
|
||||||
{ name: '王小明', projectName: '100米'自由泳', segment20: 0.035, segment70: 0.122, segment120: 0.209, segment170: 0.296 },
|
{ name: '王小明', projectName: '100米自由泳', segment20: 0.035, segment70: 0.122, segment120: 0.209, segment170: 0.296 },
|
||||||
{ name: '张小红', projectName: '100米'自由泳', segment20: 0.040, segment70: 0.138, segment120: 0.236, segment170: 0.334 }
|
{ name: '张小红', projectName: '100米自由泳', segment20: 0.040, segment70: 0.138, segment120: 0.236, segment170: 0.334 }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'2': {
|
'2': {
|
||||||
'2026-03-16': [
|
'2026-03-16': [
|
||||||
{ name: '赵小芳', projectName: '200米'自由泳', segment20: 0.038, segment70: 0.133, segment120: 0.228, segment170: 0.323 }
|
{ name: '赵小芳', projectName: '200米自由泳', segment20: 0.038, segment70: 0.133, segment120: 0.228, segment170: 0.323 }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'3': {
|
'3': {
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
},
|
},
|
||||||
'4': {
|
'4': {
|
||||||
'2026-03-24': [
|
'2026-03-24': [
|
||||||
{ name: '周小丽', projectName: '100米'蝶泳', segment20: 0.041, segment70: 0.143, segment120: 0.245, segment170: 0.347 }
|
{ name: '周小丽', projectName: '100米蝶泳', segment20: 0.041, segment70: 0.143, segment120: 0.245, segment170: 0.347 }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,7 +503,7 @@
|
|||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
margin-top: 16rpx;
|
margin-top: 16rpx;
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06), 0 0 0 1rpx rgba(0, 0kt, 0, 0.04) inset;
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@@ -613,7 +613,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.female {
|
&.female {
|
||||||
color color: #fa8c16;
|
color: #fa8c16;
|
||||||
background: linear-gradient(135deg, rgba(250, 140, 22, 0.1) 0%, rgba(255, 169, 64, 0.05) 100%);
|
background: linear-gradient(135deg, rgba(250, 140, 22, 0.1) 0%, rgba(255, 169, 64, 0.05) 100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -654,7 +654,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table-header {
|
.table-header {
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
// background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
||||||
|
|
||||||
.header-row {
|
.header-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -664,7 +664,7 @@
|
|||||||
padding: 24rpx 16rpx;
|
padding: 24rpx 16rpx;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #fff;
|
// color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-right: 1rpx solid rgba(255, 255, 255, 0.1);
|
border-right: 1rpx solid rgba(255, 255, 255, 0.1);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 包干项目模块 -->
|
<!-- 包干项目模块 -->
|
||||||
<view @click="showTimingProjectModal(2)" class="create-card package-card">
|
<view @click="Service.GoPage('/pages/userFunc/project')" class="create-card package-card">
|
||||||
<view class="card-icon">
|
<view class="card-icon">
|
||||||
<view class="icon-circle package-icon">
|
<view class="icon-circle package-icon">
|
||||||
<u-icon name="grid" size="40" color="#fff"></u-icon>
|
<u-icon name="grid" size="40" color="#fff"></u-icon>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 分段项目模块 -->
|
<!-- 分段项目模块 -->
|
||||||
<view @click="createSegmentProject" class="create-card segment-card">
|
<view @click="showTimingProjectModal(2)" class="create-card segment-card">
|
||||||
<view class="card-icon">
|
<view class="card-icon">
|
||||||
<view class="icon-circle segment-icon">
|
<view class="icon-circle segment-icon">
|
||||||
<u-icon name="list" size="40" color="#fff"></u-icon>
|
<u-icon name="list" size="40" color="#fff"></u-icon>
|
||||||
@@ -59,6 +59,22 @@
|
|||||||
<u-icon name="arrow-right" size="20" color="#1890ff"></u-icon>
|
<u-icon name="arrow-right" size="20" color="#1890ff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 混氧项目模块 -->
|
||||||
|
<view @click="showTimingProjectModal(3)" class="create-card hunyang-card">
|
||||||
|
<view class="card-icon">
|
||||||
|
<view class="icon-circle hunyang-icon">
|
||||||
|
<u-icon name="grid" size="40" color="#fff"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="card-info">
|
||||||
|
<text class="card-title">混氧项目</text>
|
||||||
|
<text class="card-desc">记录混氧训练的计时数据</text>
|
||||||
|
</view>
|
||||||
|
<view class="card-arrow">
|
||||||
|
<u-icon name="arrow-right" size="20" color="#1890ff"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -66,15 +82,14 @@
|
|||||||
<view v-if="showTimingModal" class="modal-overlay" @click="closeTimingProjectModal"></view>
|
<view v-if="showTimingModal" class="modal-overlay" @click="closeTimingProjectModal"></view>
|
||||||
<view v-if="showTimingModal" class="project-list-modal">
|
<view v-if="showTimingModal" class="project-list-modal">
|
||||||
<view class="modal-header">
|
<view class="modal-header">
|
||||||
<text class="modal-title">计时项目</text>
|
<text class="modal-title"> {{ currentIndex==1?"计时项目":(currentIndex==2?"分段项目":"混氧项目") }} </text>
|
||||||
<view class="modal-close" @click="closeTimingProjectModal">
|
<view class="modal-close" @click="closeTimingProjectModal">
|
||||||
<u-icon name="close" size="20" color="#999"></u-icon>
|
<u-icon name="close" size="20" color="#999"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="modal-content">
|
<view class="modal-content">
|
||||||
<!-- 新增按钮 -->
|
<view class="add-btn" @click="createTimingProject">
|
||||||
<view v-if="currentIndex!==2" class="add-btn" @click="createTimingProject">
|
|
||||||
<u-icon name="plus" size="18" color="#1890ff"></u-icon>
|
<u-icon name="plus" size="18" color="#1890ff"></u-icon>
|
||||||
<text class="add-btn-text">新增项目</text>
|
<text class="add-btn-text">新增项目</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -83,8 +98,8 @@
|
|||||||
<view class="project-list">
|
<view class="project-list">
|
||||||
<text class="list-title" v-if="projects.length > 0">项目列表</text>
|
<text class="list-title" v-if="projects.length > 0">项目列表</text>
|
||||||
<view v-if="projects.length > 0" class="list-container">
|
<view v-if="projects.length > 0" class="list-container">
|
||||||
<view v-for="project in projects" :key="project.id" class="project-item"
|
<view v-for="project in projects" :key="project.planId" class="project-item"
|
||||||
@click=" goPageFunc() ">
|
@click="handleProjectClick(project)">
|
||||||
<view class="item-icon">
|
<view class="item-icon">
|
||||||
<view class="icon-bg">
|
<view class="icon-bg">
|
||||||
<text class="icon-text">{{ project.name.charAt(0) }}</text>
|
<text class="icon-text">{{ project.name.charAt(0) }}</text>
|
||||||
@@ -92,7 +107,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="item-info">
|
<view class="item-info">
|
||||||
<text class="item-name">{{ project.name }}</text>
|
<text class="item-name">{{ project.name }}</text>
|
||||||
<text class="item-count">{{ project.studentCount }}人</text>
|
<text v-if="currentIndex!==3" class="item-count">{{ project.users.length }}人</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -103,140 +118,131 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { loginService } from '@/Service/swimming/loginService'
|
||||||
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
|
||||||
// 项目类型
|
|
||||||
interface Project {
|
|
||||||
id : string
|
let currentIndex = ref(0)
|
||||||
name : string
|
|
||||||
startType : string // together | interval
|
|
||||||
intervalSeconds : string
|
|
||||||
laneType : string // single | multi
|
|
||||||
laneCount : string
|
|
||||||
lanePersonCount : string
|
|
||||||
studentCount : number
|
|
||||||
students : {
|
|
||||||
id : string
|
|
||||||
name : string
|
|
||||||
gender : string
|
|
||||||
age : string
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentIndex=ref(0)
|
// 分页相关
|
||||||
|
let page = ref(1)
|
||||||
|
let status = ref('loadmore')
|
||||||
|
|
||||||
// 模拟项目数据
|
// 模拟项目数据
|
||||||
const projects = ref<Project[]>([
|
const projects = ref<Array<any>>([])
|
||||||
{
|
|
||||||
id: '001',
|
|
||||||
name: '自由泳500米',
|
|
||||||
startType: 'together',
|
|
||||||
intervalSeconds: '',
|
|
||||||
laneType: 'multi',
|
|
||||||
laneCount: '4',
|
|
||||||
lanePersonCount: '2',
|
|
||||||
studentCount: 8,
|
|
||||||
students: [
|
|
||||||
{ id: 's1', name: '张三', gender: '男', age: '12' },
|
|
||||||
{ id: 's2', name: '李四', gender: '女', age: '13' },
|
|
||||||
{ id: 's3', name: '王五', gender: '男', age: '11' },
|
|
||||||
{ id: 's4', name: '赵六', gender: '女', age: '12' },
|
|
||||||
{ id: 's5', name: '钱七', gender: '男', age: '14' },
|
|
||||||
{ id: 's6', name: '孙八', gender: '女', age: '10' },
|
|
||||||
{ id: 's7', name: '周九', gender: '男', age: '13' },
|
|
||||||
{ id: 's8', name: '吴十', gender: '女', age: '12' }
|
|
||||||
]
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '002',
|
|
||||||
name: '蛙泳200米',
|
|
||||||
startType: 'interval',
|
|
||||||
intervalSeconds: '30',
|
|
||||||
laneType: 'single',
|
|
||||||
laneCount: '',
|
|
||||||
lanePersonCount: '',
|
|
||||||
studentCount: 5,
|
|
||||||
students: [
|
|
||||||
{ id: 's1', name: '陈十一', gender: '男', age: '14' },
|
|
||||||
{ id: 's2', name: '林十二', gender: '女', age: '12' },
|
|
||||||
{ id: 's3', name: '黄十三', gender: '男', age: '11' },
|
|
||||||
{ id: 's4', name: '周十四', gender: '女', age: '13' },
|
|
||||||
{ id: 's5', name: '吴十五', gender: '男', age: '10' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// 弹窗状态
|
// 弹窗状态
|
||||||
const showTimingModal = ref(false)
|
const showTimingModal = ref(false)
|
||||||
const showDetailModal = ref(false)
|
|
||||||
const currentProject = ref<Project | null>(null)
|
onLoad(() => {
|
||||||
|
if (!Service.GetUserToken()) {
|
||||||
|
login()
|
||||||
// 生命周期
|
}
|
||||||
onMounted(() => {
|
|
||||||
// TODO: 实际应从接口获取项目列表
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const login = () => {
|
||||||
|
uni.getProvider({
|
||||||
|
service: 'oauth',
|
||||||
|
success: function (res : any) {
|
||||||
|
uni.login({
|
||||||
|
onlyAuthorize: true,
|
||||||
|
provider: res.provider,
|
||||||
|
success: function (loginRes) {
|
||||||
|
loginService.Login(
|
||||||
|
loginRes.code,
|
||||||
|
).then(content => {
|
||||||
|
if (content.code == 0) {
|
||||||
|
Service.SetUserToken(content.data.token)
|
||||||
|
} else {
|
||||||
|
Service.Msg(content.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: function (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onUnmounted(() => {
|
// 获取项目列表数据
|
||||||
// 清理
|
const getData = () => {
|
||||||
})
|
projects.value = []
|
||||||
|
page.value = 1
|
||||||
|
status.value = 'loadmore'
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目列表
|
||||||
|
const getList = () => {
|
||||||
|
if (status.value == 'loading' || status.value == 'nomore') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status.value = 'loading'
|
||||||
|
PlanService.GetPlanList(currentIndex.value==1?'计时项目':(currentIndex.value==2?'分段项目':'混氧项目'), page.value.toString()).then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
projects.value = [...projects.value, ...res.data]
|
||||||
|
status.value = res.data.length == 10 ? 'loadmore' : 'nomore'
|
||||||
|
page.value++
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 显示计时项目列表弹窗
|
// 显示计时项目列表弹窗
|
||||||
const showTimingProjectModal = (index:any) => {
|
const showTimingProjectModal = (index : any) => {
|
||||||
|
uni.hideTabBar()
|
||||||
|
|
||||||
showTimingModal.value = true
|
showTimingModal.value = true
|
||||||
currentIndex.value=index
|
currentIndex.value = index
|
||||||
|
getData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭计时项目列表弹窗
|
// 关闭计时项目列表弹窗
|
||||||
const closeTimingProjectModal = () => {
|
const closeTimingProjectModal = () => {
|
||||||
showTimingModal.value = false
|
showTimingModal.value = false
|
||||||
|
uni.showTabBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建计时项目
|
// 创建计时项目
|
||||||
const createTimingProject = () => {
|
const createTimingProject = () => {
|
||||||
Service.GoPage('/pages/userFunc/setCourse')
|
if (currentIndex.value == 3) {
|
||||||
}
|
Service.GoPage('/pages/userFunc/addHunyang')
|
||||||
|
} else {
|
||||||
// 创建包干项目
|
Service.GoPage('/pages/userFunc/setCourse?type=' + currentIndex.value)
|
||||||
const createPackageProject = () => {
|
}
|
||||||
Service.GoPage('/pages/userFunc/setCourse')
|
|
||||||
}
|
|
||||||
|
}
|
||||||
// 创建分段项目
|
|
||||||
const createSegmentProject = () => {
|
// 处理项目点击事件
|
||||||
Service.GoPage('/pages/userFunc/setCourse')
|
const handleProjectClick = (project:any) => {
|
||||||
}
|
if (currentIndex.value === 1) {
|
||||||
|
showTimingModal.value = false
|
||||||
// 显示项目详情
|
Service.GoPage('/pages/userFunc/swiming?id='+project.planId)
|
||||||
const showProjectDetail = (project : Project) => {
|
|
||||||
currentProject.value = project
|
}
|
||||||
showTimingModal.value = false
|
else if (currentIndex.value === 2) {
|
||||||
showDetailModal.value = true
|
showTimingModal.value = false
|
||||||
}
|
Service.GoPage('/pages/userFunc/segmentation?id='+project.planId)
|
||||||
|
}
|
||||||
// 关闭弹窗
|
else {
|
||||||
const closeDetailModal = () => {
|
showTimingModal.value = false
|
||||||
showDetailModal.value = false
|
Service.GoPage('/pages/userFunc/hunyang?id='+project.planId)
|
||||||
currentProject.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const goPageFunc=()=>{
|
|
||||||
if(currentIndex.value==1){
|
|
||||||
Service.GoPage('/pages/userFunc/swiming')
|
|
||||||
}else{
|
|
||||||
Service.GoPage('/pages/userFunc/project')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -447,6 +453,11 @@
|
|||||||
&.segment-icon {
|
&.segment-icon {
|
||||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 50%, #389e0d 100%);
|
background: linear-gradient(135deg, #52c41a 0%, #73d13d 50%, #389e0d 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 混氧图标 - 紫色渐变 */
|
||||||
|
&.hunyang-icon {
|
||||||
|
background: linear-gradient(135deg, #722ed1 0%, #9254de 50%, #531dab 100%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
@@ -483,7 +494,7 @@
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.card-arrow {
|
.card-arrow {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -550,7 +561,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* 计时项目列表弹窗 */
|
/* 计时项目列表弹窗 */
|
||||||
.project-list-modal {
|
.project-list-modal {
|
||||||
@@ -934,4 +945,75 @@
|
|||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 包干项目选项样式 */
|
||||||
|
.package-options {
|
||||||
|
margin-bottom: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
padding: 24rpx 20rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: 2rpx solid transparent;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-item.active {
|
||||||
|
background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-item:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-circle {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3rpx solid #d9d9d9;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-item.active .radio-circle {
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-dot {
|
||||||
|
width: 18rpx;
|
||||||
|
height: 18rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -3,15 +3,15 @@
|
|||||||
<!-- 用户信息卡片 -->
|
<!-- 用户信息卡片 -->
|
||||||
<view class="user-card">
|
<view class="user-card">
|
||||||
<view class="user-avatar">
|
<view class="user-avatar">
|
||||||
<view class="avatar-circle">
|
<view class="avatar-circle" v-if="!userInfo.headImg">
|
||||||
<u-icon name="account" size="52" color="#fff"></u-icon>
|
<u-icon name="account" size="52" color="#fff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
|
<image v-else class="avatar-image" :src="Service.GetMateUrlByImg(userInfo.headImg)" mode="aspectFill"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="user-info">
|
<view class="user-info">
|
||||||
<text class="user-nickname">游泳爱好者</text>
|
<text class="user-nickname">{{ userInfo.name }}</text>
|
||||||
<view class="user-phone-wrapper">
|
<view class="user-phone-wrapper">
|
||||||
<text class="user-phone">138****8888</text>
|
<text class="user-phone">{{ userInfo.phone || '暂无手机号' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view @click="Service.GoPage('/pages/userFunc/set')" class="user-edit">
|
<view @click="Service.GoPage('/pages/userFunc/set')" class="user-edit">
|
||||||
@@ -22,25 +22,17 @@
|
|||||||
<!-- 统计数据区域 -->
|
<!-- 统计数据区域 -->
|
||||||
<view class="stats-section">
|
<view class="stats-section">
|
||||||
<view class="stats-card">
|
<view class="stats-card">
|
||||||
<view class="stat-item" >
|
<view class="stat-item">
|
||||||
|
|
||||||
<view class="stat-info">
|
<view class="stat-info">
|
||||||
<text class="stat-value">12</text>
|
<text class="stat-value">{{ userInfo.projectCount || 0 }}</text>
|
||||||
<text class="stat-label">我的项目</text>
|
<text class="stat-label">我的项目</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-divider"></view>
|
<view class="stat-divider"></view>
|
||||||
<view class="stat-item" >
|
<view class="stat-item">
|
||||||
|
|
||||||
<view class="stat-info">
|
<view class="stat-info">
|
||||||
<text class="stat-value">8</text>
|
<text class="stat-value">{{ userInfo.studentCount || 0 }}</text>
|
||||||
<text class="stat-label">记录数</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="stat-divider"></view>
|
|
||||||
<view class="stat-item" >
|
|
||||||
<view class="stat-info">
|
|
||||||
<text class="stat-value">8</text>
|
|
||||||
<text class="stat-label">学员数</text>
|
<text class="stat-label">学员数</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -50,6 +42,7 @@
|
|||||||
<!-- 功能菜单区域 -->
|
<!-- 功能菜单区域 -->
|
||||||
<view class="menu-section">
|
<view class="menu-section">
|
||||||
<view class="menu-card">
|
<view class="menu-card">
|
||||||
|
<!-- 项目管理菜单项 -->
|
||||||
<view class="menu-item" @click="Service.GoPage('/pages/userFunc/projectList')">
|
<view class="menu-item" @click="Service.GoPage('/pages/userFunc/projectList')">
|
||||||
<view class="menu-icon-bg project-icon-bg">
|
<view class="menu-icon-bg project-icon-bg">
|
||||||
<u-icon name="list" size="28" color="#fff"></u-icon>
|
<u-icon name="list" size="28" color="#fff"></u-icon>
|
||||||
@@ -70,6 +63,8 @@
|
|||||||
|
|
||||||
<view class="menu-divider"></view>
|
<view class="menu-divider"></view>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 学员管理菜单项 -->
|
||||||
<view class="menu-item" @click="Service.GoPage('/pages/userFunc/student')">
|
<view class="menu-item" @click="Service.GoPage('/pages/userFunc/student')">
|
||||||
<view class="menu-icon-bg academy-icon-bg">
|
<view class="menu-icon-bg academy-icon-bg">
|
||||||
<u-icon name="grid" size="28" color="#fff"></u-icon>
|
<u-icon name="grid" size="28" color="#fff"></u-icon>
|
||||||
@@ -90,6 +85,7 @@
|
|||||||
|
|
||||||
<view class="menu-divider"></view>
|
<view class="menu-divider"></view>
|
||||||
|
|
||||||
|
<!-- 数据分析菜单项 -->
|
||||||
<view class="menu-item" @click="Service.GoPage('/pages/userFunc/analyze')">
|
<view class="menu-item" @click="Service.GoPage('/pages/userFunc/analyze')">
|
||||||
<view class="menu-icon-bg analysis-icon-bg">
|
<view class="menu-icon-bg analysis-icon-bg">
|
||||||
<u-icon name="order" size="28" color="#fff"></u-icon>
|
<u-icon name="order" size="28" color="#fff"></u-icon>
|
||||||
@@ -114,19 +110,43 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onShow, onLoad } from "@dcloudio/uni-app"
|
import { onShow, onLoad } from "@dcloudio/uni-app"
|
||||||
|
import { reactive, ref } from "vue"
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
|
import { userService } from '@/Service/swimming/userService'
|
||||||
|
|
||||||
|
|
||||||
|
let userInfo = ref<any>({})
|
||||||
|
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
|
loadUserInfo()
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const loadUserInfo = () => {
|
||||||
|
userService.GetUserInfo().then((content) => {
|
||||||
|
if (content.code == 0) {
|
||||||
|
userInfo.value=content.data.userInfo
|
||||||
|
} else {
|
||||||
|
Service.Msg(content.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转到训练计划页面
|
||||||
|
* 点击"训练计划"菜单项时触发
|
||||||
|
* 功能说明:
|
||||||
|
* - 跳转到训练计划列表页面
|
||||||
|
* - 用户可以查看、添加、编辑训练计划
|
||||||
|
*/
|
||||||
|
const goToTrainingPlans = () => {
|
||||||
|
Service.GoPage('/pages/userFunc/plan')
|
||||||
|
}
|
||||||
|
|
||||||
// 跳转到设置
|
// 跳转到设置
|
||||||
const goToSettings = () => {
|
const goToSettings = () => {
|
||||||
@@ -203,6 +223,15 @@
|
|||||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar-image {
|
||||||
|
width: 130rpx;
|
||||||
|
height: 130rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 5rpx solid rgba(255, 255, 255, 0.4);
|
||||||
|
backdrop-filter: blur(20rpx);
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.avatar-badge {
|
.avatar-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -410,14 +439,22 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 项目管理图标背景色 - 蓝色渐变 */
|
||||||
&.project-icon-bg {
|
&.project-icon-bg {
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #096dd9 100%);
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #096dd9 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 训练计划图标背景色 - 紫色渐变 */
|
||||||
|
&.plan-icon-bg {
|
||||||
|
background: linear-gradient(135deg, #722ed1 0%, #9254de 50%, #531dab 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 学员管理图标背景色 - 绿色渐变 */
|
||||||
&.academy-icon-bg {
|
&.academy-icon-bg {
|
||||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 50%, #389e0d 100%);
|
background: linear-gradient(135deg, #52c41a 0%, #73d13d 50%, #389e0d 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 数据分析图标背景色 - 橙色渐变 */
|
||||||
&.analysis-icon-bg {
|
&.analysis-icon-bg {
|
||||||
background: linear-gradient(135deg, #faad14 0%, #ffc53d 50%, #d48806 100%);
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 50%, #d48806 100%);
|
||||||
}
|
}
|
||||||
@@ -454,16 +491,25 @@
|
|||||||
font-size: 20rpx;
|
font-size: 20rpx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
|
/* 项目管理标签样式 - 蓝色 */
|
||||||
&.project-tag {
|
&.project-tag {
|
||||||
background: linear-gradient(135deg, rgba(24, 144, 255, 0.12) 0%, rgba(24, 144, 255, 0.08) 100%);
|
background: linear-gradient(135deg, rgba(24, 144, 255, 0.12) 0%, rgba(24, 144, 255, 0.08) 100%);
|
||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 训练计划标签样式 - 紫色 */
|
||||||
|
&.plan-tag {
|
||||||
|
background: linear-gradient(135deg, rgba(114, 46, 209, 0.12) 0%, rgba(114, 46, 209, 0.08) 100%);
|
||||||
|
color: #722ed1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 学员管理标签样式 - 绿色 */
|
||||||
&.academy-tag {
|
&.academy-tag {
|
||||||
background: linear-gradient(135deg, rgba(82, 196, 26, 0.12) 0%, rgba(82, 196, 26, 0.08) 100%);
|
background: linear-gradient(135deg, rgba(82, 196, 26, 0.12) 0%, rgba(82, 196, 26, 0.08) 100%);
|
||||||
color: #52c41a;
|
color: #52c41a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 数据分析标签样式 - 橙色 */
|
||||||
&.analysis-tag {
|
&.analysis-tag {
|
||||||
background: linear-gradient(135deg, rgba(250, 173, 20, 0.12) 0%, rgba(250, 173, 20, 0.08) 100%);
|
background: linear-gradient(135deg, rgba(250, 173, 20, 0.12) 0%, rgba(250, 173, 20, 0.08) 100%);
|
||||||
color: #faad14;
|
color: #faad14;
|
||||||
|
|||||||
577
src/pages/userFunc/addHunyang.vue
Normal file
577
src/pages/userFunc/addHunyang.vue
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
<template>
|
||||||
|
<view class="add-hunyang-container">
|
||||||
|
<!-- 表单区域 -->
|
||||||
|
<view class="form-section">
|
||||||
|
<!-- 项目名称 -->
|
||||||
|
<view class="form-card">
|
||||||
|
<view class="form-title">项目信息</view>
|
||||||
|
<view class="form-group">
|
||||||
|
<text class="form-label">项目名称</text>
|
||||||
|
<input class="form-input" v-model="projectName" placeholder="请输入项目名称"
|
||||||
|
placeholder-class="input-placeholder" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 计划总时长 -->
|
||||||
|
<view class="total-time-section">
|
||||||
|
<view class="total-time-label">计划总时长</view>
|
||||||
|
<view class="total-time-value">{{ formatTotalTime(totalDuration) }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 计划列表 -->
|
||||||
|
<view class="form-card">
|
||||||
|
<view class="form-title">计划列表</view>
|
||||||
|
|
||||||
|
<!-- 计划项列表 -->
|
||||||
|
<view v-for="(item, index) in planList" :key="index" class="plan-item">
|
||||||
|
<view class="plan-item-header">
|
||||||
|
<text class="plan-item-title">计划 {{ index + 1 }}</text>
|
||||||
|
<view class="delete-plan-btn" @click="deletePlanItem(index)">
|
||||||
|
<u-icon name="trash" size="16" color="#fff"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="plan-item-content">
|
||||||
|
<!-- 目标时间 - 分秒选择 -->
|
||||||
|
<view class="input-group">
|
||||||
|
<text class="input-label">目标时间</text>
|
||||||
|
<view class="time-selector">
|
||||||
|
<view class="time-input-wrapper">
|
||||||
|
<input class="time-input" v-model="item.targetSeconds" type="number"
|
||||||
|
placeholder="" />
|
||||||
|
<text class="time-unit">秒</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 休息时长 - 秒选择 -->
|
||||||
|
<view class="input-group">
|
||||||
|
<text class="input-label">休息时长</text>
|
||||||
|
<view class="rest-selector">
|
||||||
|
<input class="rest-input" v-model="item.restTime" type="number" placeholder="请输入休息时长" />
|
||||||
|
<text class="time-unit">秒</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 圈数 -->
|
||||||
|
<view class="input-group">
|
||||||
|
<text class="input-label">圈数</text>
|
||||||
|
<view class="lap-selector">
|
||||||
|
<input class="lap-input" v-model="item.lapCount" type="number" placeholder="请输入圈数" />
|
||||||
|
<text class="time-unit">圈</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 添加计划按钮 -->
|
||||||
|
<view class="add-plan-btn" @click="addPlanItem">
|
||||||
|
<u-icon name="plus-circle" size="20" color="#1890ff"></u-icon>
|
||||||
|
<text class="add-plan-text">添加计划</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部按钮区域 -->
|
||||||
|
<view class="bottom-actions">
|
||||||
|
<view class="action-buttons">
|
||||||
|
<button class="cancel-btn" @click="handleCancel">取消</button>
|
||||||
|
<button class="confirm-btn" @click="handleSave">保存</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { Service } from '@/Service/Service'
|
||||||
|
import { studentService } from '@/Service/swimming/studentService'
|
||||||
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||||
|
|
||||||
|
|
||||||
|
// 计划项接口
|
||||||
|
interface PlanItem {
|
||||||
|
targetMinutes : string
|
||||||
|
targetSeconds : string
|
||||||
|
restTime : string
|
||||||
|
lapCount : string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 项目名称
|
||||||
|
const projectName = ref('')
|
||||||
|
|
||||||
|
// 计划列表
|
||||||
|
const planList = ref<PlanItem[]>([])
|
||||||
|
let planId = ref('')
|
||||||
|
|
||||||
|
onLoad((data : any) => {
|
||||||
|
planId.value = data.id
|
||||||
|
if(planId.value ){
|
||||||
|
getPlanInfo()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 获取计划详情
|
||||||
|
const getPlanInfo = () => {
|
||||||
|
PlanService.GetPlanInfo(planId.value).then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
projectName.value=res.data.plan.name
|
||||||
|
JSON.parse(res.data.plan.project).map((item:any)=>{
|
||||||
|
planList.value.push({
|
||||||
|
targetMinutes:'',
|
||||||
|
targetSeconds : item.target,
|
||||||
|
restTime : item.rest,
|
||||||
|
lapCount : item.circle
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 显示错误信息
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算计划总时长(秒)
|
||||||
|
const totalDuration = computed(() => {
|
||||||
|
let total = 0
|
||||||
|
planList.value.forEach(item => {
|
||||||
|
const targetMinutes = parseInt(item.targetMinutes) || 0
|
||||||
|
const targetSeconds = parseInt(item.targetSeconds) || 0
|
||||||
|
const targetTime = targetMinutes * 60 + targetSeconds
|
||||||
|
const restTime = parseInt(item.restTime) || 0
|
||||||
|
const lapCount = parseInt(item.lapCount) || 0
|
||||||
|
|
||||||
|
// 计算公式:(目标时间 + 休息时间) * 圈数
|
||||||
|
total += (targetTime + restTime) * lapCount
|
||||||
|
})
|
||||||
|
return total
|
||||||
|
})
|
||||||
|
|
||||||
|
// 格式化总时长显示(时:分:秒)
|
||||||
|
const formatTotalTime = (seconds : number) : string => {
|
||||||
|
if (seconds <= 0) return '00:00:00'
|
||||||
|
|
||||||
|
const hours = Math.floor(seconds / 3600)
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
const secs = Math.floor(seconds % 60)
|
||||||
|
|
||||||
|
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成唯一ID
|
||||||
|
const generateId = () : string => {
|
||||||
|
return Date.now().toString() + Math.random().toString(36).substr(2, 9)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加计划项
|
||||||
|
const addPlanItem = () => {
|
||||||
|
const newItem : PlanItem = {
|
||||||
|
id: generateId(),
|
||||||
|
targetMinutes: '',
|
||||||
|
targetSeconds: '',
|
||||||
|
restTime: '',
|
||||||
|
lapCount: ''
|
||||||
|
}
|
||||||
|
planList.value.push(newItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除计划项
|
||||||
|
const deletePlanItem = (index : number) => {
|
||||||
|
uni.showModal({
|
||||||
|
title: '确认删除',
|
||||||
|
content: '确定要删除该计划吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
planList.value.splice(index, 1)
|
||||||
|
Service.Msg('删除成功')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证表单
|
||||||
|
const validateForm = () : boolean => {
|
||||||
|
if (!projectName.value.trim()) {
|
||||||
|
Service.Msg('请输入项目名称')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (planList.value.length === 0) {
|
||||||
|
Service.Msg('请至少添加一个计划')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < planList.value.length; i++) {
|
||||||
|
const item = planList.value[i]
|
||||||
|
|
||||||
|
if (!item.targetSeconds) {
|
||||||
|
Service.Msg(`计划 ${i + 1} 请输入目标时间`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const seconds = parseInt(item.targetSeconds) || 0
|
||||||
|
if (seconds === 0) {
|
||||||
|
Service.Msg(`计划 ${i + 1} 目标时间不能为0`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.restTime || parseInt(item.restTime) < 0) {
|
||||||
|
Service.Msg(`计划 ${i + 1} 请输入有效的休息时长`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.lapCount || parseInt(item.lapCount) <= 0) {
|
||||||
|
Service.Msg(`计划 ${i + 1} 请输入有效的圈数`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
const handleSave = () => {
|
||||||
|
if (!validateForm()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let plan = planList.value.map(item => ({
|
||||||
|
target: (parseInt(item.targetMinutes) || 0) * 60 + (parseInt(item.targetSeconds) || 0),
|
||||||
|
rest: parseInt(item.restTime),
|
||||||
|
circle: parseInt(item.lapCount)
|
||||||
|
}))
|
||||||
|
// 整理数据
|
||||||
|
const data = {
|
||||||
|
planId: planId.value,
|
||||||
|
name: projectName.value,
|
||||||
|
planType: '混氧项目',
|
||||||
|
departType: '',
|
||||||
|
interval: '',
|
||||||
|
groupInt: '',
|
||||||
|
subsectionDistance: '',
|
||||||
|
subsectionInt: '',
|
||||||
|
users: '',
|
||||||
|
project: JSON.stringify(plan),
|
||||||
|
group: '',
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('保存的数据:', data)
|
||||||
|
PlanService.AddPlan(data).then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
Service.Msg('添加成功!')
|
||||||
|
setTimeout(() => {
|
||||||
|
Service.GoPageBack()
|
||||||
|
}, 1000)
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
uni.showModal({
|
||||||
|
title: '确认取消',
|
||||||
|
content: '确定要取消吗?已输入的内容将不会保存。',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
Service.GoPageBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
page {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-hunyang-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding-bottom: 140rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单区域 */
|
||||||
|
.form-section {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 32rpx 28rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
|
.form-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单输入 */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 0 4rpx rgba(24, 144, 255, 0.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 计划总时长 */
|
||||||
|
.total-time-section {
|
||||||
|
margin-top: 28rpx;
|
||||||
|
padding-top: 24rpx;
|
||||||
|
border-top: 2rpx solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.total-time-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-time-value {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #1890ff;
|
||||||
|
font-family: 'DIN Alternate', 'Helvetica Neue', monospace;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%);
|
||||||
|
padding: 12rpx 24rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 2rpx solid #91d5ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 计划项 */
|
||||||
|
.plan-item {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
border: 2rpx solid #f0f0f0;
|
||||||
|
|
||||||
|
.plan-item-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.plan-item-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-plan-btn {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
background: linear-gradient(135deg, #ff4d4f 0%, #d9363e 100%);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(255, 77, 79, 0.3);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-item-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 输入组 */
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.input-label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
min-width: 120rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 时间选择器 */
|
||||||
|
.time-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.time-input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.time-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-unit {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-left: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-separator {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
padding: 0 4rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 休息选择器 */
|
||||||
|
.rest-selector,
|
||||||
|
.lap-selector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.rest-input,
|
||||||
|
.lap-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-unit {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加计划按钮 */
|
||||||
|
.add-plan-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: 2rpx dashed #91d5ff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
background: linear-gradient(135deg, #e6f7ff 0%, #d6f4ff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-plan-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部操作按钮 */
|
||||||
|
.bottom-actions {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1rpx solid #f0f0f0;
|
||||||
|
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||||
|
z-index: 99;
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn,
|
||||||
|
.confirm-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 88rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: none;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #e8e8e8;
|
||||||
|
transform: scale(0.96);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 6rpx 16rpx rgba(24, 144, 255, 0.35);
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
box-shadow: 0 3rpx 8rpx rgba(24, 144, 255, 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
693
src/pages/userFunc/hunyang.vue
Normal file
693
src/pages/userFunc/hunyang.vue
Normal file
@@ -0,0 +1,693 @@
|
|||||||
|
<template>
|
||||||
|
<view class="hunyang-container">
|
||||||
|
<!--
|
||||||
|
混氧训练页面主容器
|
||||||
|
包含:头部信息、倒计时装置、底部控制按钮
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- 项目信息头部 -->
|
||||||
|
<view class="card-section header-section" style="position: relative;">
|
||||||
|
<view class="header-content">
|
||||||
|
<!-- 训练项目名称 -->
|
||||||
|
<view class="project-name">{{ name }}</view>
|
||||||
|
<!-- 总训练时长显示 -->
|
||||||
|
<view class="total-duration">
|
||||||
|
<text class="duration-label">计划总时长:</text>
|
||||||
|
<!-- 格式化显示总时长 -->
|
||||||
|
<text class="duration-value">{{ formatTotalDuration(totalDuration) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="" style="position: absolute; top: 20rpx; right: 30rpx; ">
|
||||||
|
<up-icon @click="Service.GoPage('/pages/userFunc/addHunyang?id='+planId)" name="setting"
|
||||||
|
size="22"></up-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 多个倒计时装置区域 -->
|
||||||
|
<view class="card-section timer-section">
|
||||||
|
<!-- 区域标题 -->
|
||||||
|
<view class="section-title">倒计时装置</view>
|
||||||
|
|
||||||
|
<view class="multi-timers-wrapper">
|
||||||
|
<!--
|
||||||
|
使用v-for循环渲染每个训练计划
|
||||||
|
:active类用于高亮当前正在执行的计划
|
||||||
|
-->
|
||||||
|
<view v-for="(plan, index) in plans" :key="plan.id" class="timer-item"
|
||||||
|
:class="{ active: currentPlanIndex === index }">
|
||||||
|
<view class="timer-horizontal">
|
||||||
|
<!--
|
||||||
|
训练状态显示区域
|
||||||
|
根据不同状态显示:休息中/已完成/计划X
|
||||||
|
-->
|
||||||
|
<view class="timer-status" :class="{ resting: timerStates[index].isResting }">
|
||||||
|
<text v-if="timerStates[index].isResting" class="status-label">休息中</text>
|
||||||
|
<text v-else-if="timerStates[index].isCompleted" class="status-label">已完成</text>
|
||||||
|
<text v-else class="status-label">计划{{ index + 1 }}</text>
|
||||||
|
</view>
|
||||||
|
<!--
|
||||||
|
倒计时显示区域
|
||||||
|
根据状态显示训练时间或休息时间
|
||||||
|
-->
|
||||||
|
<view class="timer-display" :class="{ resting: timerStates[index].isResting }">
|
||||||
|
<text v-if="timerStates[index].isResting"
|
||||||
|
class="display-time">{{ formatRestCountdown(timerStates[index].restCountdown) }}</text>
|
||||||
|
<text v-else class="display-time">{{ formatCountdown(timerStates[index].countdown) }}</text>
|
||||||
|
</view>
|
||||||
|
<!--
|
||||||
|
完成圈数显示
|
||||||
|
显示已完成的圈数/总圈数
|
||||||
|
-->
|
||||||
|
<view class="timer-laps">
|
||||||
|
<text class="laps-text">{{ timerStates[index].completedLaps }}/{{ plan.circle }}圈</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 计划详细信息 -->
|
||||||
|
<view class="plan-info-bar">
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="info-label">目标:</text>
|
||||||
|
<text class="info-value">{{ plan.target }}秒</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="info-label">休息:</text>
|
||||||
|
<text class="info-value">{{ plan.rest }}秒</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空状态提示 -->
|
||||||
|
<view v-if="plans.length === 0" class="empty-timer">
|
||||||
|
<text class="empty-text">暂无训练计划,请添加</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部控制按钮区域 -->
|
||||||
|
<view class="bottom-controls">
|
||||||
|
<view class="controls-inner">
|
||||||
|
<!--
|
||||||
|
开始按钮 - 仅在未运行时显示
|
||||||
|
点击开始所有训练计划
|
||||||
|
-->
|
||||||
|
<view v-if="!isRunning" class="control-btn start-btn" @click="startAll">
|
||||||
|
<u-icon name="play-circle" size="32" color="#fff"></u-icon>
|
||||||
|
<text class="btn-text">开始</text>
|
||||||
|
</view>
|
||||||
|
<!--
|
||||||
|
暂停按钮 - 仅在运行时显示
|
||||||
|
点击暂停所有训练
|
||||||
|
-->
|
||||||
|
<view v-else class="control-btn pause-btn" @click="pauseAll">
|
||||||
|
<u-icon name="pause-circle" size="32" color="#fff"></u-icon>
|
||||||
|
<text class="btn-text">暂停</text>
|
||||||
|
</view>
|
||||||
|
<!--
|
||||||
|
重置按钮
|
||||||
|
点击重置所有训练状态
|
||||||
|
-->
|
||||||
|
<view class="control-btn reset-btn" @click="resetAll">
|
||||||
|
<u-icon name="reload" size="32" color="#fff"></u-icon>
|
||||||
|
<text class="btn-text">重置</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 保存悬浮按钮 -->
|
||||||
|
<view class="save-float-btn" @click="saveData">
|
||||||
|
<text class="btn-text">保存</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 导入必要的Vue组合式API和项目服务
|
||||||
|
import { ref, computed, onUnmounted } from 'vue'
|
||||||
|
import { Service } from '@/Service/Service'
|
||||||
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||||
|
|
||||||
|
// 定义训练计划的数据结构接口
|
||||||
|
interface PlanItem {
|
||||||
|
id : string // 计划唯一标识
|
||||||
|
targetTime : number // 目标训练时间(秒)
|
||||||
|
restTime : number // 休息时间(秒)
|
||||||
|
lapCount : number // 训练圈数
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义计时器状态的数据结构接口
|
||||||
|
interface TimerState {
|
||||||
|
isRunning : boolean // 是否正在运行
|
||||||
|
isResting : boolean // 是否处于休息状态
|
||||||
|
isCompleted : boolean // 是否已完成
|
||||||
|
countdown : number // 倒计时(秒)
|
||||||
|
restCountdown : number // 休息倒计时(秒)
|
||||||
|
completedLaps : number // 已完成圈数
|
||||||
|
}
|
||||||
|
|
||||||
|
// 训练计划列表,使用ref实现响应式
|
||||||
|
const plans = ref<Array<any>>([
|
||||||
|
])
|
||||||
|
|
||||||
|
// 每个计划的计时器状态,与plans一一对应
|
||||||
|
const timerStates = ref<TimerState[]>([])
|
||||||
|
// 当前正在执行的训练计划索引
|
||||||
|
const currentPlanIndex = ref(0)
|
||||||
|
// 训练是否正在运行的状态
|
||||||
|
const isRunning = ref(false)
|
||||||
|
// 主计时器间隔ID,用于控制全局计时
|
||||||
|
const mainInterval = ref<number | null>(null)
|
||||||
|
|
||||||
|
// 训练计划ID,从页面参数获取
|
||||||
|
let planId = ref('')
|
||||||
|
|
||||||
|
let name = ref('')
|
||||||
|
|
||||||
|
// 页面加载时触发,获取训练计划信息
|
||||||
|
onLoad((data : any) => {
|
||||||
|
planId.value = data.id
|
||||||
|
|
||||||
|
})
|
||||||
|
onShow(() => {
|
||||||
|
getPlanInfo()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取计划详情
|
||||||
|
// 通过PlanService获取指定ID的训练计划,并初始化计时器状态
|
||||||
|
const getPlanInfo = () => {
|
||||||
|
PlanService.GetPlanInfo(planId.value).then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
// 解析计划数据并初始化计时器状态
|
||||||
|
name.value = res.data.plan.name
|
||||||
|
plans.value = JSON.parse(res.data.plan.project)
|
||||||
|
initTimerStates()
|
||||||
|
} else {
|
||||||
|
// 显示错误信息
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化计时器状态
|
||||||
|
// 为每个计划创建对应的计时器状态对象,重置所有状态
|
||||||
|
const initTimerStates = () => {
|
||||||
|
timerStates.value = plans.value.map(() => ({
|
||||||
|
isRunning: false,
|
||||||
|
isResting: false,
|
||||||
|
isCompleted: false,
|
||||||
|
countdown: 0,
|
||||||
|
restCountdown: 0,
|
||||||
|
completedLaps: 0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始所有训练计划
|
||||||
|
// 检查是否有计划,如果没有则提示用户
|
||||||
|
// 设置运行状态并启动第一个计划
|
||||||
|
const startAll = () => {
|
||||||
|
if (plans.value.length === 0) {
|
||||||
|
Service.Msg('请先添加训练计划')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning.value = true
|
||||||
|
|
||||||
|
// 启动指定索引的计划
|
||||||
|
const startPlan = (index : number) => {
|
||||||
|
// 如果索引超出范围,停止所有训练并显示完成消息
|
||||||
|
if (index >= plans.value.length) {
|
||||||
|
stopAll()
|
||||||
|
Service.Msg('所有计划训练完成!', 'success')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置当前计划索引并获取对应的状态
|
||||||
|
currentPlanIndex.value = index
|
||||||
|
const plan = plans.value[index]
|
||||||
|
const state = timerStates.value[index]
|
||||||
|
|
||||||
|
// 设置计划为运行状态,并初始化倒计时
|
||||||
|
state.isRunning = true
|
||||||
|
if (state.countdown === 0 && !state.isResting) {
|
||||||
|
state.countdown = plan.target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动当前计划
|
||||||
|
startPlan(currentPlanIndex.value)
|
||||||
|
|
||||||
|
// 设置主计时器,每10毫秒更新一次
|
||||||
|
mainInterval.value = setInterval(() => {
|
||||||
|
const index = currentPlanIndex.value
|
||||||
|
// 如果索引超出范围,停止所有训练
|
||||||
|
if (index >= plans.value.length) {
|
||||||
|
stopAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = plans.value[index]
|
||||||
|
const state = timerStates.value[index]
|
||||||
|
|
||||||
|
// 如果处于休息状态,更新休息倒计时
|
||||||
|
if (state.isResting) {
|
||||||
|
state.restCountdown -= 0.01
|
||||||
|
if (state.restCountdown <= 0) {
|
||||||
|
// 休息结束,重置休息状态并开始训练
|
||||||
|
state.isResting = false
|
||||||
|
state.restCountdown = 0
|
||||||
|
state.countdown = plan.target
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 训练状态,更新训练倒计时
|
||||||
|
state.countdown -= 0.01
|
||||||
|
if (state.countdown <= 0) {
|
||||||
|
// 训练完成一圈,增加完成圈数
|
||||||
|
state.completedLaps += 1
|
||||||
|
|
||||||
|
// 如果达到总圈数,标记为已完成并启动下一个计划
|
||||||
|
if (state.completedLaps >= plan.circle) {
|
||||||
|
state.isRunning = false
|
||||||
|
state.isCompleted = true
|
||||||
|
currentPlanIndex.value += 1
|
||||||
|
startPlan(currentPlanIndex.value)
|
||||||
|
} else {
|
||||||
|
// 否则进入休息状态
|
||||||
|
state.isResting = true
|
||||||
|
state.restCountdown = plan.rest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂停所有训练
|
||||||
|
// 调用stopAll方法暂停训练
|
||||||
|
const pauseAll = () => {
|
||||||
|
stopAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止所有训练
|
||||||
|
// 重置运行状态,清除主计时器
|
||||||
|
const stopAll = () => {
|
||||||
|
isRunning.value = false
|
||||||
|
// 重置所有计划的运行状态
|
||||||
|
timerStates.value.forEach(state => {
|
||||||
|
state.isRunning = false
|
||||||
|
})
|
||||||
|
// 清除主计时器
|
||||||
|
if (mainInterval.value) {
|
||||||
|
clearInterval(mainInterval.value)
|
||||||
|
mainInterval.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置所有训练状态
|
||||||
|
// 停止训练,重置当前计划索引,重置所有计时器状态
|
||||||
|
const resetAll = () => {
|
||||||
|
stopAll()
|
||||||
|
currentPlanIndex.value = 0
|
||||||
|
// 重置所有计时器状态
|
||||||
|
timerStates.value.forEach(state => {
|
||||||
|
state.isResting = false
|
||||||
|
state.isCompleted = false
|
||||||
|
state.countdown = 0
|
||||||
|
state.restCountdown = 0
|
||||||
|
state.completedLaps = 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化倒计时显示
|
||||||
|
// 将秒数格式化为"分:秒"格式
|
||||||
|
const formatCountdown = (seconds : number) : string => {
|
||||||
|
const totalSeconds = Math.max(0, Math.floor(seconds))
|
||||||
|
const minutes = Math.floor(totalSeconds / 60)
|
||||||
|
const secs = totalSeconds % 60
|
||||||
|
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化休息倒计时显示
|
||||||
|
// 将秒数格式化为"分:秒"格式
|
||||||
|
const formatRestCountdown = (seconds : number) : string => {
|
||||||
|
const totalSeconds = Math.max(0, Math.floor(seconds))
|
||||||
|
const minutes = Math.floor(totalSeconds / 60)
|
||||||
|
const secs = totalSeconds % 60
|
||||||
|
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化总时长显示
|
||||||
|
// 将秒数格式化为"分:秒"格式
|
||||||
|
const formatTotalDuration = (seconds : number) : string => {
|
||||||
|
if (seconds === 0) return '0分0秒'
|
||||||
|
const minutes = Math.floor(seconds / 60)
|
||||||
|
const secs = Math.floor(seconds % 60)
|
||||||
|
return `${minutes}分${secs}秒`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 计算总训练时长
|
||||||
|
// 根据所有计划的训练时间和休息时间计算总时长
|
||||||
|
const totalDuration = computed(() => {
|
||||||
|
return plans.value.reduce((total, plan) => {
|
||||||
|
const planTime = plan.target * plan.circle
|
||||||
|
const restTime = plan.rest * (plan.circle - 1)
|
||||||
|
return total + planTime + restTime
|
||||||
|
}, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 保存数据
|
||||||
|
const saveData = () => {
|
||||||
|
if (plans.value.length === 0) {
|
||||||
|
Service.Msg('没有可保存的训练计划')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建要保存的数据
|
||||||
|
const saveData = {
|
||||||
|
name: name.value,
|
||||||
|
project: JSON.stringify(plans.value),
|
||||||
|
totalDuration: totalDuration.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 页面卸载时触发
|
||||||
|
// 确保停止所有训练,避免内存泄漏
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 格式化总时长显示
|
||||||
|
// 将秒数格式化为"小时:分:秒"或"分:秒"或"秒"格式
|
||||||
|
|
||||||
|
|
||||||
|
// 页面卸载时触发
|
||||||
|
// 确保停止所有训练,避免内存泄漏
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopAll()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.hunyang-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 20rpx;
|
||||||
|
padding-bottom: 240rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-section {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-section {
|
||||||
|
background: #ffffff;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-duration {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 12rpx 20rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 16rpx;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 4rpx;
|
||||||
|
height: 20rpx;
|
||||||
|
background: #4a90e2;
|
||||||
|
border-radius: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-timer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40rpx 20rpx;
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-section {
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-timers-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-item {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
border: 1rpx solid #e0e0e0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #4a90e2;
|
||||||
|
box-shadow: 0 0 0 2rpx rgba(74, 144, 226, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.99);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-horizontal {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background: #e6f0ff;
|
||||||
|
border: 1rpx solid #d0e3ff;
|
||||||
|
min-width: 100rpx;
|
||||||
|
|
||||||
|
&.resting {
|
||||||
|
background: #fff8e6;
|
||||||
|
border-color: #ffe0b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #4a90e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.resting .status-label {
|
||||||
|
color: #ff9800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-display {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.display-time {
|
||||||
|
font-size: 56rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: 'Helvetica Neue', monospace;
|
||||||
|
color: #4a90e2;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.resting .display-time {
|
||||||
|
color: #ff9800;
|
||||||
|
animation: countdownFlash 0.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-laps {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
min-width: 100rpx;
|
||||||
|
|
||||||
|
.laps-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #666666;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-controls {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 16rpx 24rpx 24rpx;
|
||||||
|
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||||
|
z-index: 100;
|
||||||
|
border-top: 1rpx solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-inner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
max-width: 750rpx;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 20rpx 16rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-btn {
|
||||||
|
background-color: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pause-btn {
|
||||||
|
background-color: #ff9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-btn {
|
||||||
|
background-color: #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-info-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 30rpx;
|
||||||
|
padding-top: 10rpx;
|
||||||
|
border-top: 1rpx dashed #e0e0e0;
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #333333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes countdownFlash {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 保存悬浮按钮样式 */
|
||||||
|
.save-float-btn {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 240rpx;
|
||||||
|
right: 30rpx;
|
||||||
|
background-color: #2196f3;
|
||||||
|
padding: 40rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(33, 150, 243, 0.3);
|
||||||
|
z-index: 101;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-float-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-float-btn .btn-text {
|
||||||
|
font-size: 20rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
394
src/pages/userFunc/plan.vue
Normal file
394
src/pages/userFunc/plan.vue
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
<template>
|
||||||
|
<view class="plan-container">
|
||||||
|
<!-- 训练计划列表 -->
|
||||||
|
<view class="plan-list-section">
|
||||||
|
<view class="section-header">
|
||||||
|
<text class="section-title">我的训练计划</text>
|
||||||
|
<text class="plan-count">{{ trainingPlans.length }}个计划</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 计划列表 -->
|
||||||
|
<view class="plan-list">
|
||||||
|
<view v-for="(plan, index) in trainingPlans" :key="plan.id" class="plan-card">
|
||||||
|
<!-- 计划信息 -->
|
||||||
|
<view class="plan-info">
|
||||||
|
<view class="plan-icon">
|
||||||
|
<view class="icon-circle">
|
||||||
|
<u-icon name="calendar" size="24" color="#fff"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="plan-detail">
|
||||||
|
<text class="plan-name">{{ plan.name }}</text>
|
||||||
|
<view class="plan-meta">
|
||||||
|
<view class="meta-tag">
|
||||||
|
<text class="tag-text">{{ plan.laps }}圈</text>
|
||||||
|
</view>
|
||||||
|
<view class="meta-tag">
|
||||||
|
<text class="tag-text">混氧</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 删除按钮 -->
|
||||||
|
<view class="delete-btn">
|
||||||
|
<u-icon name="trash" size="20" color="#ff4d4f"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<view v-if="trainingPlans.length === 0" class="empty-state">
|
||||||
|
<view class="empty-icon">
|
||||||
|
<u-icon name="calendar" size="80" color="#d9d9d9"></u-icon>
|
||||||
|
</view>
|
||||||
|
<text class="empty-text">暂无训练计划</text>
|
||||||
|
<text class="empty-desc">点击下方按钮添加第一个训练计划</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部添加计划按钮 -->
|
||||||
|
<view class="bottom-section">
|
||||||
|
<view class="add-plan-btn" @click="Service.GoPage('/pages/userFunc/addplan')">
|
||||||
|
<u-icon name="plus" size="24" color="#fff"></u-icon>
|
||||||
|
<text class="add-btn-text">添加训练计划</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { Service } from '@/Service/Service'
|
||||||
|
|
||||||
|
interface TrainingPlan {
|
||||||
|
id : string
|
||||||
|
name : string
|
||||||
|
laps : string
|
||||||
|
}
|
||||||
|
|
||||||
|
const trainingPlans = ref<TrainingPlan[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: '自由泳500米基础训练',
|
||||||
|
laps: '10'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: '蛙泳200米进阶训练',
|
||||||
|
laps: '4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
name: '混合泳1000米挑战',
|
||||||
|
laps: '20'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
name: '蝶泳400米专项训练',
|
||||||
|
laps: '8'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
page {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding-bottom: 180rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 计划列表区域 */
|
||||||
|
.plan-list-section {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
padding: 0 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 16rpx;
|
||||||
|
|
||||||
|
/* 标题左侧装饰条 */
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 6rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
background: linear-gradient(180deg, #1890ff 0%, #096dd9 100%);
|
||||||
|
border-radius: 3rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-count {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: 600;
|
||||||
|
background: linear-gradient(135deg, rgba(24, 144, 255, 0.1) 0%, rgba(24, 144, 255, 0.05) 100%);
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 计划列表 */
|
||||||
|
.plan-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 单个计划卡片 - 美化版 */
|
||||||
|
.plan-card {
|
||||||
|
background: linear-gradient(135deg, #fff 0%, #fafbfc 100%);
|
||||||
|
border-radius: 28rpx;
|
||||||
|
padding: 28rpx 24rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08), 0 0 0 1rpx rgba(0, 0, 0, 0.04) inset;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
/* 卡片背景装饰 */
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
background: radial-gradient(circle, rgba(24, 144, 255, 0.08) 0%, transparent 70%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98) translateY(2rpx);
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧序号 */
|
||||||
|
.plan-index {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.index-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 计划信息区域 */
|
||||||
|
.plan-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-circle {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #096dd9 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(24, 144, 255, 0.25);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 8rpx;
|
||||||
|
right: 8rpx;
|
||||||
|
width: 16rpx;
|
||||||
|
height: 16rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-detail {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-name {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.3;
|
||||||
|
background: linear-gradient(135deg, #333 0%, #666 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-tag {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
background: linear-gradient(135deg, rgba(24, 144, 255, 0.1) 0%, rgba(24, 144, 255, 0.05) 100%);
|
||||||
|
border-radius: 16rpx;
|
||||||
|
border: 1rpx solid rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 删除按钮 - 美化版 */
|
||||||
|
.delete-btn {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, rgba(255, 77, 79, 0.1) 0%, rgba(255, 77, 79, 0.05) 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 2rpx solid rgba(255, 77, 79, 0.2);
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: linear-gradient(135deg, #ff4d4f 0%, #d9363e 100%);
|
||||||
|
transform: scale(0.9);
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 77, 79, 0.4);
|
||||||
|
border-color: transparent;
|
||||||
|
|
||||||
|
u-icon {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空状态 */
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 140rpx 40rpx;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #8c8c8c;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-desc {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #bfbfbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部添加计划按钮 */
|
||||||
|
.bottom-section {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: linear-gradient(180deg, transparent 0%, rgba(245, 245, 245, 0.95) 20%, #f5f5f5 100%);
|
||||||
|
padding: 24rpx 20rpx 40rpx;
|
||||||
|
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-plan-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
width: 100%;
|
||||||
|
height: 100rpx;
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #36cfc9 100%);
|
||||||
|
background-size: 200% 200%;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
box-shadow: 0 12rpx 32rpx rgba(24, 144, 255, 0.35), 0 0 0 1rpx rgba(255, 255, 255, 0.5) inset;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
/* 光泽效果 */
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
|
||||||
|
animation: shimmer 3s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.96);
|
||||||
|
box-shadow: 0 6rpx 20rpx rgba(24, 144, 255, 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn-text {
|
||||||
|
font-size: 34rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else class="projects-list">
|
<view v-else class="projects-list">
|
||||||
<view v-for="(project, index) in projects" :key="project.id" class="project-card"
|
<view v-for="(project, index) in projects" :key="project.planId" class="project-card"
|
||||||
@click="viewProjectDetail(project)">
|
@click="viewProjectDetail(project)">
|
||||||
<view class="project-delete" @click.stop="deleteProject(project, index)">
|
<view class="project-delete" @click.stop="deleteProject(project, index)">
|
||||||
<u-icon name="trash" size="18" color="#ff4d4f"></u-icon>
|
<u-icon name="trash" size="18" color="#ff4d4f"></u-icon>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<view class="project-stats">
|
<view class="project-stats">
|
||||||
<view class="stat-badge">
|
<view class="stat-badge">
|
||||||
<u-icon name="account" size="14" color="#1890ff"></u-icon>
|
<u-icon name="account" size="14" color="#1890ff"></u-icon>
|
||||||
<text class="badge-text">{{ project.studentCount }}位学员</text>
|
<text class="badge-text">{{ project.users.length }}位学员</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="project-time">
|
<view class="project-time">
|
||||||
<u-icon name="clock" size="14" color="#999"></u-icon>
|
<u-icon name="clock" size="14" color="#999"></u-icon>
|
||||||
<text class="time-text">{{ project.createTime }}</text>
|
<text class="time-text">{{ Service.formatDate(project.addTime,1) }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
@@ -57,69 +57,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onShow, onLoad } from "@dcloudio/uni-app"
|
import { onShow, onLoad, onReachBottom } from "@dcloudio/uni-app"
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
// 定义项目类型
|
|
||||||
interface Project {
|
|
||||||
id : string
|
// 分页相关
|
||||||
name : string
|
let page = ref(1)
|
||||||
mode : '计时' | '包干' | '分段'
|
let status = ref('loadmore')
|
||||||
createTime : string
|
|
||||||
studentCount : number
|
|
||||||
recordCount : number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 项目列表数据
|
// 项目列表数据
|
||||||
const projects = ref<Project[]>([
|
const projects = ref<Array<any>>([])
|
||||||
{
|
|
||||||
id: '001',
|
|
||||||
name: '自由泳50米',
|
|
||||||
mode: '计时',
|
|
||||||
createTime: '2024-01-15 14:30',
|
|
||||||
studentCount: 8,
|
|
||||||
recordCount: 24
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '002',
|
|
||||||
name: '蛙泳100米',
|
|
||||||
mode: '包干',
|
|
||||||
createTime: '2024-01-14 10:15',
|
|
||||||
studentCount: 6,
|
|
||||||
recordCount: 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '003',
|
|
||||||
name: '仰泳200米',
|
|
||||||
mode: '分段',
|
|
||||||
createTime: '2024-01-13 16:45',
|
|
||||||
studentCount: 5,
|
|
||||||
recordCount: 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '004',
|
|
||||||
name: '蝶泳50米',
|
|
||||||
mode: '计时',
|
|
||||||
createTime: '2024-01-12 09:20',
|
|
||||||
studentCount: 4,
|
|
||||||
recordCount: 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '005',
|
|
||||||
name: '混合泳100米',
|
|
||||||
mode: '包干',
|
|
||||||
createTime: '2024-01-11 15:00',
|
|
||||||
studentCount: 7,
|
|
||||||
recordCount: 21
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
|
getData()
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onReachBottom(() => {
|
||||||
|
getList()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取模式对应的样式类
|
// 获取模式对应的样式类
|
||||||
@@ -133,15 +89,46 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查看项目详情
|
// 查看项目详情
|
||||||
const viewProjectDetail = (project : Project) => {
|
const viewProjectDetail = (project : any) => {
|
||||||
Service.Msg(`查看「${project.name}」详情`)
|
Service.Msg(`查看「${project.name}」详情`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除项目
|
// 删除项目
|
||||||
const deleteProject = (project : Project, index : number) => {
|
const deleteProject = (project : any, index : number) => {
|
||||||
Service.Confirm(`确定要删除「${project.name}」吗?`, () => {
|
Service.Confirm(`确定要删除「${project.name}」吗?`, () => {
|
||||||
projects.value.splice(index, 1)
|
PlanService.DeletePlan(project.planId).then(res => {
|
||||||
Service.Msg('删除成功')
|
if (res.code == 0) {
|
||||||
|
getData()
|
||||||
|
Service.Msg('删除成功')
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目列表数据
|
||||||
|
const getData = () => {
|
||||||
|
projects.value = []
|
||||||
|
page.value = 1
|
||||||
|
status.value = 'loadmore'
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取项目列表
|
||||||
|
const getList = () => {
|
||||||
|
if (status.value == 'loading' || status.value == 'nomore') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status.value = 'loading'
|
||||||
|
PlanService.GetPlanList( '', page.value.toString()).then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
projects.value = [...projects.value, ...res.data]
|
||||||
|
status.value = res.data.length == 10 ? 'loadmore' : 'nomore'
|
||||||
|
page.value++
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="segmentation-container">
|
<view class="segmentation-container">
|
||||||
|
<!-- 配置区域 -->
|
||||||
|
<view class="config-section">
|
||||||
|
<view class="config-header">
|
||||||
|
<text class="config-title">分段设置</text>
|
||||||
|
|
||||||
|
<u-icon @click="Service.GoPage('/pages/userFunc/setCourse?id='+planId+'&type=2')" name="setting" size="24"
|
||||||
|
color="#1890ff"></u-icon>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
<view class="config-info">
|
||||||
|
<text class="info-text">总距离: {{ totalDistance }}米 ({{ segmentCount }}段 × {{ segmentDistance }}米)</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 总计时器区域 -->
|
<!-- 总计时器区域 -->
|
||||||
<view class="total-time-section">
|
<view class="total-time-section">
|
||||||
<view class="timer-circle">
|
<view class="timer-bar">
|
||||||
<view class="circle-content">
|
<view class="timer-time">{{ formatTime(currentTime) }}</view>
|
||||||
<view class="timer-sound">
|
|
||||||
<text class="sound-text">计时器</text>
|
|
||||||
</view>
|
|
||||||
<view class="timer-time">{{ formatTime(currentTime) }}</view>
|
|
||||||
<view class="timer-duration">{{ formatDuration(currentTime) }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="timer-controls">
|
|
||||||
<u-icon name="reload" size="32" color="#22a6f2" @click="resetAll"></u-icon>
|
|
||||||
<u-icon :name="isRunning ? 'pause-circle' : 'play-circle'" size="32" color="#22a6f2"
|
|
||||||
@click="toggleTimer"></u-icon>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -26,96 +30,180 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="students-list">
|
<view class="students-list">
|
||||||
<view v-for="(student, index) in students" :key="student.id" class="student-item">
|
<view v-for="(student, index) in students" :key="student.id" class="student-item"
|
||||||
|
:class="{ 'not-started': !student.hasStarted }">
|
||||||
<view class="student-header">
|
<view class="student-header">
|
||||||
<view class="student-number">{{ student.number }}</view>
|
<view class="student-number" :class="{ 'started': student.hasStarted }">{{ student.number }}
|
||||||
|
</view>
|
||||||
<view class="student-info">
|
<view class="student-info">
|
||||||
<text class="student-name">{{ student.name }}</text>
|
<text class="student-name">{{ student.name }}</text>
|
||||||
<text class="student-lane">{{ student.lane }}</text>
|
<!-- <view class="start-status" v-if="startMode === 1 && !student.hasStarted">
|
||||||
|
<text class="status-text">等待</text>
|
||||||
|
</view> -->
|
||||||
|
<text class="total-time-text"
|
||||||
|
v-if="student.segments.length > 0">{{ formatSimpleTime(getLastSegmentTime(student)) }}</text>
|
||||||
|
<view class="record-badge"
|
||||||
|
:class="{ 'completed': student.segments.length >= Number(segmentCount) }">
|
||||||
|
<text class="badge-text">{{ student.segments.length }}/{{ segmentCount }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="student-buttons">
|
<view class="student-buttons">
|
||||||
<button class="record-btn" @click="recordStudentSegment(student)">
|
<button class="record-btn" :disabled="!student.hasStarted"
|
||||||
<u-icon name="checkmark" size="18" color="#fff"></u-icon>
|
@click="recordStudentSegment(student)">
|
||||||
|
|
||||||
<text class="btn-text">记录</text>
|
<text class="btn-text">记录</text>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="view-btn" @click="showStudentRecords(student)">
|
||||||
|
|
||||||
|
<text class="btn-text">详情</text>
|
||||||
|
</button>
|
||||||
<button class="reset-btn" @click="resetStudent(student)">
|
<button class="reset-btn" @click="resetStudent(student)">
|
||||||
<u-icon name="reload" size="18" color="#fff"></u-icon>
|
|
||||||
<text class="btn-text">重置</text>
|
<text class="btn-text">重置</text>
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 分段时间块 -->
|
|
||||||
<view class="segments-list">
|
|
||||||
<view v-for="(segment, segIndex) in student.segments" :key="segIndex" class="segment-item">
|
|
||||||
<text class="segment-label">第{{ segIndex + 1 }}段</text>
|
|
||||||
<text class="segment-time">{{ segment.time ? formatTime(segment.time) : '--:--.--' }}</text>
|
|
||||||
</view>
|
|
||||||
<view v-if="student.segments.length<4" class="segment-item empty">
|
|
||||||
<text class="segment-label">第{{ student.segments.length + 1 }}段</text>
|
|
||||||
<text class="segment-time">--:--.--</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 保存按钮 -->
|
<!-- 底部操作栏 -->
|
||||||
<view class="save-btn-wrapper">
|
<view class="bottom-actions">
|
||||||
<button class="save-btn" @click="saveData">
|
<view class="action-btn reset-action" @click="resetAll">
|
||||||
<text class="btn-text">保存</text>
|
<u-icon name="reload" size="24" color="#fff"></u-icon>
|
||||||
</button>
|
<text class="action-text">重置</text>
|
||||||
|
</view>
|
||||||
|
<view class="action-btn timer-action" :class="{ 'running': isRunning }" @click="toggleTimer">
|
||||||
|
<u-icon :name="isRunning ? 'pause-circle' : 'play-circle'" size="28" color="#fff"></u-icon>
|
||||||
|
<text class="action-text">{{ isRunning ? '暂停' : '开始' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="action-btn save-action" @click="saveData">
|
||||||
|
<u-icon name="checkmark" size="24" color="#fff"></u-icon>
|
||||||
|
<text class="action-text">保存</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 记录弹窗 -->
|
||||||
|
<u-popup :show="showRecordPopup" mode="bottom" :round="20" @close="closeRecordPopup">
|
||||||
|
<view class="record-popup">
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">{{ currentStudent?.name }} - 分段记录</text>
|
||||||
|
<view class="close-btn" @click="closeRecordPopup">
|
||||||
|
<u-icon name="close" size="24" color="#999"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-summary">
|
||||||
|
<view class="summary-item">
|
||||||
|
<text class="summary-label">总距离</text>
|
||||||
|
<text class="summary-value">{{ totalDistance }}米</text>
|
||||||
|
</view>
|
||||||
|
<view class="summary-item">
|
||||||
|
<text class="summary-label">已记录</text>
|
||||||
|
<text
|
||||||
|
class="summary-value">{{ currentStudent?.segments?.length || 0 }}/{{ segmentCount }}段</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="record-list"
|
||||||
|
v-if="currentStudent && currentStudent.segments && currentStudent.segments.length > 0">
|
||||||
|
<view class="record-header">
|
||||||
|
<text class="record-cell header-cell">分段</text>
|
||||||
|
<text class="record-cell header-cell">距离</text>
|
||||||
|
<text class="record-cell header-cell">累计时间</text>
|
||||||
|
<text class="record-cell header-cell">分段用时</text>
|
||||||
|
</view>
|
||||||
|
<view v-for="(segment, index) in currentStudent.segments" :key="index" class="record-item">
|
||||||
|
<text class="record-cell">第{{ index + 1 }}段</text>
|
||||||
|
<text class="record-cell">{{ segmentDistance }}米</text>
|
||||||
|
<text class="record-cell time-cell">{{ formatTime(segment.time) }}</text>
|
||||||
|
<text class="record-cell time-cell">{{ formatTimeDiff(index, segment.time) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="empty-record" v-else>
|
||||||
|
<u-icon name="info-circle" size="48" color="#ccc"></u-icon>
|
||||||
|
<text class="empty-text">暂无记录数据</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-footer">
|
||||||
|
<button class="popup-btn close-popup" @click="closeRecordPopup">关闭</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onUnmounted } from 'vue'
|
import { ref, onUnmounted, computed } from 'vue'
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||||
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
|
||||||
|
// 分段配置
|
||||||
|
const segmentDistance = ref(50)
|
||||||
|
const segmentCount = ref(4)
|
||||||
|
|
||||||
|
// 出发模式: 0-一起出发, 1-间隔出发
|
||||||
|
const startMode = ref(0)
|
||||||
|
// 间隔时间(秒)
|
||||||
|
const intervalTime = ref(10)
|
||||||
|
|
||||||
// 最大分段数
|
// 计算总距离
|
||||||
const maxSegments = 4
|
const totalDistance = computed(() => {
|
||||||
|
return Number(segmentDistance.value) * Number(segmentCount.value)
|
||||||
|
})
|
||||||
|
|
||||||
// 学生列表
|
// 学生列表
|
||||||
const students = ref<Array<any>>([
|
const students = ref<Array<any>>([])
|
||||||
{
|
|
||||||
id: '1',
|
// 弹窗状态
|
||||||
number: '01',
|
const showRecordPopup = ref(false)
|
||||||
name: '张三',
|
const currentStudent = ref<any>(null)
|
||||||
lane: '第一泳道',
|
|
||||||
segments: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
number: '02',
|
|
||||||
name: '李四',
|
|
||||||
lane: '第二泳道',
|
|
||||||
segments: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
number: '03',
|
|
||||||
name: '王五',
|
|
||||||
lane: '第三泳道',
|
|
||||||
segments: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
number: '04',
|
|
||||||
name: '赵六',
|
|
||||||
lane: '第四泳道',
|
|
||||||
segments: []
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// 计时器状态
|
// 计时器状态
|
||||||
const isRunning = ref(false)
|
const isRunning = ref(false)
|
||||||
const currentTime = ref(0)
|
const currentTime = ref(0)
|
||||||
let timerInterval : number | null = null
|
let timerInterval : number | null = null
|
||||||
let startTime : number = 0
|
let startTime : number = 0
|
||||||
|
let intervalStartTimer : number | null = null
|
||||||
|
|
||||||
|
let planId = ref('')
|
||||||
|
onLoad((data : any) => {
|
||||||
|
planId.value = data.id
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
onShow(()=>{
|
||||||
|
getPlanInfo()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取计划详情
|
||||||
|
const getPlanInfo = () => {
|
||||||
|
PlanService.GetPlanInfo(planId.value).then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
// planName.value = res.data.plan.name
|
||||||
|
segmentDistance.value=res.data.plan.subsectionDistance/res.data.plan.subsectionInt
|
||||||
|
segmentCount.value=res.data.plan.subsectionInt
|
||||||
|
// 将计划数据转换为选手数据
|
||||||
|
// athletes.value = res.data.plan.users.
|
||||||
|
students.value=res.data.plan.users.map((item:any,index:any)=>{
|
||||||
|
return {
|
||||||
|
id: item.studentId,
|
||||||
|
number: index+1,
|
||||||
|
name: item.name,
|
||||||
|
segments: [],
|
||||||
|
hasStarted: res.data.plan.departType == '间隔出发'?false:true,
|
||||||
|
startTime: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
startMode.value = res.data.plan.departType == '间隔出发' ? 1 : 0
|
||||||
|
intervalTime.value = res.data.plan.interval
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 格式化时间显示
|
// 格式化时间显示
|
||||||
const formatTime = (seconds : number) : string => {
|
const formatTime = (seconds : number) : string => {
|
||||||
@@ -142,6 +230,34 @@
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 格式化时间差
|
||||||
|
const formatTimeDiff = (index : number, currentTime : number) : string => {
|
||||||
|
if (index === 0) {
|
||||||
|
return '00:00:00'
|
||||||
|
}
|
||||||
|
if (!currentStudent.value || !currentStudent.value.segments[index - 1]) {
|
||||||
|
return '00:00:00'
|
||||||
|
}
|
||||||
|
const prevTime = currentStudent.value.segments[index - 1].time
|
||||||
|
const diff = currentTime - prevTime
|
||||||
|
return formatTime(diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取学生最后一次记录的累计时间
|
||||||
|
const getLastSegmentTime = (student : any) : number => {
|
||||||
|
if (!student.segments || student.segments.length === 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return student.segments[student.segments.length - 1].time
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简化时间格式化(分:秒)
|
||||||
|
const formatSimpleTime = (seconds : number) : string => {
|
||||||
|
const mins = Math.floor(seconds / 60)
|
||||||
|
const secs = Math.floor(seconds % 60)
|
||||||
|
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
// 开始计时
|
// 开始计时
|
||||||
const startTimer = () => {
|
const startTimer = () => {
|
||||||
if (isRunning.value) return
|
if (isRunning.value) return
|
||||||
@@ -153,6 +269,32 @@
|
|||||||
const elapsed = (Date.now() - startTime) / 1000
|
const elapsed = (Date.now() - startTime) / 1000
|
||||||
currentTime.value = elapsed
|
currentTime.value = elapsed
|
||||||
}, 10)
|
}, 10)
|
||||||
|
|
||||||
|
// 间隔出发模式
|
||||||
|
if (startMode.value === 1) {
|
||||||
|
// 第一个学生立即出发
|
||||||
|
if (students.value.length > 0) {
|
||||||
|
students.value[0].hasStarted = true
|
||||||
|
students.value[0].startTime = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置间隔出发定时器
|
||||||
|
const interval = intervalTime.value * 1000
|
||||||
|
let startedCount = 1
|
||||||
|
|
||||||
|
intervalStartTimer = setInterval(() => {
|
||||||
|
if (startedCount < students.value.length) {
|
||||||
|
const student = students.value[startedCount]
|
||||||
|
student.hasStarted = true
|
||||||
|
student.startTime = (Date.now() - startTime) / 1000
|
||||||
|
startedCount++
|
||||||
|
Service.Msg(`${student.name} 已出发`)
|
||||||
|
} else {
|
||||||
|
clearInterval(intervalStartTimer as number)
|
||||||
|
intervalStartTimer = null
|
||||||
|
}
|
||||||
|
}, interval)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 停止计时
|
// 停止计时
|
||||||
@@ -164,6 +306,10 @@
|
|||||||
clearInterval(timerInterval)
|
clearInterval(timerInterval)
|
||||||
timerInterval = null
|
timerInterval = null
|
||||||
}
|
}
|
||||||
|
if (intervalStartTimer) {
|
||||||
|
clearInterval(intervalStartTimer)
|
||||||
|
intervalStartTimer = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换计时器状态
|
// 切换计时器状态
|
||||||
@@ -176,27 +322,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录学生分段
|
// 记录学生分段
|
||||||
const recordStudentSegment = (student : Student) => {
|
const recordStudentSegment = (student : any) => {
|
||||||
|
console.log(22222);
|
||||||
if (!isRunning.value) {
|
if (!isRunning.value) {
|
||||||
Service.Msg('请先开始计时')
|
Service.Msg('请先开始计时')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (student.segments.length >= maxSegments) {
|
if (!student.hasStarted) {
|
||||||
Service.Msg('已达到最大分段数')
|
Service.Msg('该学生尚未出发')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxSeg = Number(segmentCount.value)
|
||||||
|
if (student.segments.length >= maxSeg) {
|
||||||
|
Service.Msg(`已达到最大分段数(${maxSeg}段)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let recordTime = currentTime.value
|
||||||
|
// 间隔出发模式下,使用学生自己的开始时间
|
||||||
|
if (startMode.value === 1) {
|
||||||
|
recordTime = currentTime.value - student.startTime
|
||||||
|
}
|
||||||
|
|
||||||
student.segments.push({
|
student.segments.push({
|
||||||
time: currentTime.value
|
time: recordTime
|
||||||
})
|
})
|
||||||
|
|
||||||
Service.Msg(`${student.name} 第${student.segments.length}段已记录`)
|
Service.Msg(`${student.name} 第${student.segments.length}段已记录`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置学生
|
// 重置学生
|
||||||
const resetStudent = (student : Student) => {
|
const resetStudent = (student : any) => {
|
||||||
student.segments = []
|
student.segments = []
|
||||||
|
student.hasStarted = false
|
||||||
|
student.startTime = 0
|
||||||
Service.Msg(`${student.name} 已重置`)
|
Service.Msg(`${student.name} 已重置`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +367,8 @@
|
|||||||
currentTime.value = 0
|
currentTime.value = 0
|
||||||
students.value.forEach(student => {
|
students.value.forEach(student => {
|
||||||
student.segments = []
|
student.segments = []
|
||||||
|
student.hasStarted = startMode.value == 1?false:true,
|
||||||
|
student.startTime = 0
|
||||||
})
|
})
|
||||||
Service.Msg('已全部重置')
|
Service.Msg('已全部重置')
|
||||||
}
|
}
|
||||||
@@ -228,11 +391,26 @@
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示学生记录弹窗
|
||||||
|
const showStudentRecords = (student : any) => {
|
||||||
|
currentStudent.value = student
|
||||||
|
showRecordPopup.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭记录弹窗
|
||||||
|
const closeRecordPopup = () => {
|
||||||
|
showRecordPopup.value = false
|
||||||
|
currentStudent.value = null
|
||||||
|
}
|
||||||
|
|
||||||
// 页面卸载时清理计时器
|
// 页面卸载时清理计时器
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (timerInterval) {
|
if (timerInterval) {
|
||||||
clearInterval(timerInterval)
|
clearInterval(timerInterval)
|
||||||
}
|
}
|
||||||
|
if (intervalStartTimer) {
|
||||||
|
clearInterval(intervalStartTimer)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -241,83 +419,65 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #f6f6f6;
|
background-color: #f6f6f6;
|
||||||
padding: 20rpx 20rpx;
|
padding: 20rpx 20rpx;
|
||||||
padding-bottom: 200rpx;
|
padding-bottom: 180rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 配置区域 */
|
||||||
|
.config-section {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.config-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
|
||||||
|
.config-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.config-info {
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
|
||||||
|
.info-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 总计时器区域 */
|
/* 总计时器区域 */
|
||||||
.total-time-section {
|
.total-time-section {
|
||||||
margin-top: 20rpx;
|
margin-top: 20rpx;
|
||||||
margin-bottom: 40rpx;
|
margin-bottom: 30rpx;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer-circle {
|
.timer-bar {
|
||||||
width: 350rpx;
|
|
||||||
height: 360rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 20rpx;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
|
||||||
box-shadow: 0 4rpx 16rpx rgba(250, 140, 22, 0.3);
|
|
||||||
|
|
||||||
.circle-content {
|
|
||||||
// background-color: #faad14;
|
|
||||||
padding: 30rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 16rpx solid #faad14;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timer-sound {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
padding: 8rpx 16rpx;
|
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
|
||||||
border-radius: 20rpx;
|
|
||||||
|
|
||||||
.sound-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.timer-time {
|
.timer-time {
|
||||||
font-size: 48rpx;
|
font-size: 80rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
color: #ff4d4f;
|
||||||
|
font-family: 'DIN Alternate', monospace;
|
||||||
.timer-duration {
|
|
||||||
font-size: 24rpx;
|
|
||||||
opacity: 0.9;
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
padding: 4rpx 12rpx;
|
|
||||||
border-radius: 4rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timer-controls {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
width: 280rpx;
|
|
||||||
|
|
||||||
u-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,10 +514,13 @@
|
|||||||
margin-bottom: 20rpx;
|
margin-bottom: 20rpx;
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
&.not-started {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
.student-header {
|
.student-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 24rpx;
|
|
||||||
|
|
||||||
.student-number {
|
.student-number {
|
||||||
width: 56rpx;
|
width: 56rpx;
|
||||||
@@ -371,22 +534,62 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-right: 20rpx;
|
margin-right: 20rpx;
|
||||||
|
|
||||||
|
&.started {
|
||||||
|
background-color: #52c41a;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-info {
|
.student-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
|
||||||
.student-name {
|
.student-name {
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 4rpx;
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.student-lane {
|
.start-status {
|
||||||
|
background-color: #faad14;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-time-text {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
font-family: 'DIN Alternate', monospace;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-badge {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
|
||||||
|
.badge-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.completed {
|
||||||
|
background-color: #52c41a;
|
||||||
|
|
||||||
|
.badge-text {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,6 +598,7 @@
|
|||||||
gap: 12rpx;
|
gap: 12rpx;
|
||||||
|
|
||||||
.record-btn,
|
.record-btn,
|
||||||
|
.view-btn,
|
||||||
.reset-btn {
|
.reset-btn {
|
||||||
height: 56rpx;
|
height: 56rpx;
|
||||||
padding: 0 20rpx;
|
padding: 0 20rpx;
|
||||||
@@ -415,6 +619,15 @@
|
|||||||
|
|
||||||
.record-btn {
|
.record-btn {
|
||||||
background-color: #52c41a;
|
background-color: #52c41a;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-btn {
|
||||||
|
background-color: #1890ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reset-btn {
|
.reset-btn {
|
||||||
@@ -422,91 +635,201 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 分段时间列表 */
|
/* 记录弹窗 */
|
||||||
.segments-list {
|
.record-popup {
|
||||||
display: flex;
|
background-color: #fff;
|
||||||
flex-wrap: wrap;
|
border-radius: 20rpx 20rpx 0 0;
|
||||||
gap: 16rpx;
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.segment-item {
|
.popup-header {
|
||||||
flex: 1;
|
display: flex;
|
||||||
min-width: 140rpx;
|
justify-content: space-between;
|
||||||
background-color: #f5f5f5;
|
align-items: center;
|
||||||
border-radius: 12rpx;
|
padding: 30rpx;
|
||||||
padding: 20rpx 16rpx;
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&.empty {
|
.popup-title {
|
||||||
opacity: 0.5;
|
font-size: 32rpx;
|
||||||
}
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
.segment-label {
|
.close-btn {
|
||||||
font-size: 22rpx;
|
padding: 10rpx;
|
||||||
color: #666;
|
cursor: pointer;
|
||||||
display: block;
|
}
|
||||||
margin-bottom: 8rpx;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.segment-time {
|
.popup-summary {
|
||||||
font-size: 26rpx;
|
display: flex;
|
||||||
font-weight: 600;
|
gap: 20rpx;
|
||||||
color: #333;
|
padding: 20rpx 30rpx;
|
||||||
font-family: 'DIN Alternate', monospace;
|
background-color: #f6f6f6;
|
||||||
}
|
|
||||||
|
.summary-item {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.summary-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-value {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 30rpx 20rpx;
|
||||||
|
|
||||||
|
.record-header {
|
||||||
|
display: flex;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
|
||||||
|
.record-cell {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
padding: 16rpx 8rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cell {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-item {
|
||||||
|
display: flex;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
border: 1rpx solid #f0f0f0;
|
||||||
|
|
||||||
|
.record-cell {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
padding: 20rpx 8rpx;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
&.time-cell {
|
||||||
|
font-family: 'DIN Alternate', monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1890ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* 保存按钮 */
|
.empty-record {
|
||||||
.save-btn-wrapper {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 40rpx;
|
|
||||||
right: 30rpx;
|
|
||||||
z-index: 100;
|
|
||||||
|
|
||||||
.save-btn {
|
|
||||||
width: 140rpx;
|
|
||||||
height: 140rpx;
|
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
|
||||||
border-radius: 50%;
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8rpx;
|
padding: 80rpx 0;
|
||||||
box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.5),
|
|
||||||
0 4rpx 12rpx rgba(24, 144, 255, 0.3);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&::before {
|
.empty-text {
|
||||||
content: '';
|
font-size: 28rpx;
|
||||||
position: absolute;
|
color: #999;
|
||||||
top: 0;
|
margin-top: 20rpx;
|
||||||
left: 0;
|
}
|
||||||
right: 0;
|
}
|
||||||
bottom: 0;
|
|
||||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, transparent 50%);
|
.popup-footer {
|
||||||
pointer-events: none;
|
padding: 20rpx 30rpx 40rpx;
|
||||||
|
border-top: 1rpx solid #f0f0f0;
|
||||||
|
|
||||||
|
.popup-btn {
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: none;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
.close-popup {
|
||||||
transform: scale(0.95);
|
background-color: #f5f5f5;
|
||||||
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.4),
|
color: #666;
|
||||||
0 2rpx 6rpx rgba(24, 144, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 1rpx;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 底部操作栏 */
|
||||||
|
.bottom-actions {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20rpx 30rpx 40rpx;
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10rpx;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
.action-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-action {
|
||||||
|
background-color: #faad14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-action {
|
||||||
|
background-color: #52c41a;
|
||||||
|
flex: 1.5;
|
||||||
|
|
||||||
|
&.running {
|
||||||
|
background-color: #ff4d4f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-action {
|
||||||
|
background-color: #1890ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<view class="avatar-section">
|
<view class="avatar-section">
|
||||||
<view class="avatar-wrapper">
|
<view class="avatar-wrapper">
|
||||||
<view class="avatar-circle">
|
<view class="avatar-circle">
|
||||||
<u-icon name="account" size="64" color="#fff"></u-icon>
|
<img :src="Service.GetMateUrlByImg(userInfo.headImg)" alt="" style="width: 100%; height: 100%;" />
|
||||||
</view>
|
</view>
|
||||||
<view class="avatar-edit" @click="changeAvatar">
|
<view class="avatar-edit" @click="changeAvatar">
|
||||||
<u-icon name="camera" size="20" color="#fff"></u-icon>
|
<u-icon name="camera" size="20" color="#fff"></u-icon>
|
||||||
@@ -67,24 +67,32 @@
|
|||||||
import { onShow, onLoad } from "@dcloudio/uni-app"
|
import { onShow, onLoad } from "@dcloudio/uni-app"
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
// 用户信息
|
import { userService } from '@/Service/swimming/userService'
|
||||||
interface UserInfo {
|
|
||||||
name: string
|
const userInfo = ref<any>({
|
||||||
phone: string
|
headImg:'',
|
||||||
}
|
name: '',
|
||||||
|
phone: ''
|
||||||
const userInfo = ref<UserInfo>({
|
|
||||||
name: '游泳爱好者',
|
|
||||||
phone: '13888888888'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
// 可以在这里从本地存储加载用户信息
|
loadUserInfo()
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const loadUserInfo = () => {
|
||||||
|
userService.GetUserInfo().then((content) => {
|
||||||
|
if (content.code == 0) {
|
||||||
|
userInfo.value=content.data.userInfo
|
||||||
|
} else {
|
||||||
|
Service.Msg(content.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 更换头像
|
// 更换头像
|
||||||
const changeAvatar = () => {
|
const changeAvatar = () => {
|
||||||
@@ -93,12 +101,15 @@
|
|||||||
sizeType: ['compressed'],
|
sizeType: ['compressed'],
|
||||||
sourceType: ['album', 'camera'],
|
sourceType: ['album', 'camera'],
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
Service.Msg('头像更换成功', 'success')
|
let path = res.tempFiles[0].path
|
||||||
|
Service.uploadH5(path, 'Avatar', data => {
|
||||||
|
userInfo.value.headImg = data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存用户信息
|
// 保存用户信息
|
||||||
const saveUserInfo = () => {
|
const saveUserInfo = () => {
|
||||||
if (!userInfo.value.name.trim()) {
|
if (!userInfo.value.name.trim()) {
|
||||||
Service.Msg('请输入用户姓名')
|
Service.Msg('请输入用户姓名')
|
||||||
@@ -117,13 +128,17 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟保存成功
|
// 调用UpdateUserInfo接口
|
||||||
Service.Msg('保存成功', 'success')
|
userService.UpdateUserInfo(userInfo.value.name, userInfo.value.phone, userInfo.value.headImg).then((content) => {
|
||||||
|
if (content.code == 0) {
|
||||||
// 保存成功后可以返回上一页
|
Service.Msg('保存成功', 'success')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Service.GoPageBack()
|
Service.GoPageBack()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
} else {
|
||||||
|
Service.Msg(content.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -198,6 +213,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.3);
|
box-shadow: 0 8rpx 24rpx rgba(24, 144, 255, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-edit {
|
.avatar-edit {
|
||||||
|
|||||||
@@ -41,38 +41,14 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 泳道设置 -->
|
<!-- 组别设置 -->
|
||||||
<view class="form-card">
|
<view v-if="type==1" class="form-card">
|
||||||
<view class="form-title">泳道设置</view>
|
<view class="form-title">组别设置</view>
|
||||||
<view class="radio-group">
|
<view class="group-options">
|
||||||
<view class="radio-item" :class="{ active: courseData.laneType === 'single' }"
|
|
||||||
@click="selectSingleLane">
|
|
||||||
<view class="radio-icon">
|
|
||||||
<view v-if="courseData.laneType === 'single'" class="radio-inner"></view>
|
|
||||||
</view>
|
|
||||||
<text>一个泳道</text>
|
|
||||||
</view>
|
|
||||||
<view class="radio-item" :class="{ active: courseData.laneType === 'multi' }"
|
|
||||||
@click="selectMultiLane">
|
|
||||||
<view class="radio-icon">
|
|
||||||
<view v-if="courseData.laneType === 'multi'" class="radio-inner"></view>
|
|
||||||
</view>
|
|
||||||
<text>多个泳道</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view v-if="courseData.laneType === 'multi'" class="multi-lane-options">
|
|
||||||
<view class="lane-input-wrapper">
|
|
||||||
<text class="lane-label">泳道个数</text>
|
|
||||||
<view class="lane-input">
|
|
||||||
<input class="number-input" v-model="courseData.laneCount" type="number"
|
|
||||||
placeholder="请输入泳道个数" />
|
|
||||||
<text class="unit-text">个</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="person-input-wrapper">
|
<view class="person-input-wrapper">
|
||||||
<text class="person-label">一个泳道人数</text>
|
<text class="person-label">每组人数</text>
|
||||||
<view class="person-input">
|
<view class="person-input">
|
||||||
<input class="number-input" v-model="courseData.lanePersonCount" type="number"
|
<input class="number-input" v-model="courseData.groupPersonCount" type="number"
|
||||||
placeholder="请输入人数" />
|
placeholder="请输入人数" />
|
||||||
<text class="unit-text">人</text>
|
<text class="unit-text">人</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -80,11 +56,34 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 分段设置 -->
|
||||||
|
<view v-if="type==2" class="form-card">
|
||||||
|
<view class="form-title">分段设置</view>
|
||||||
|
<view class="segment-options">
|
||||||
|
<view class="segment-input-wrapper">
|
||||||
|
<text class="segment-label">分段距离</text>
|
||||||
|
<view class="segment-input">
|
||||||
|
<input class="number-input" v-model="courseData.segmentDistance" type="number"
|
||||||
|
placeholder="请输入距离" />
|
||||||
|
<text class="unit-text">米</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="segment-count-wrapper">
|
||||||
|
<text class="segment-count-label">分段数</text>
|
||||||
|
<view class="segment-count-input">
|
||||||
|
<input class="number-input" v-model="courseData.segmentCount" type="number"
|
||||||
|
placeholder="请输入段数" />
|
||||||
|
<text class="unit-text">段</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 学生列表 -->
|
<!-- 学生列表 -->
|
||||||
<view class="form-card">
|
<view class="form-card">
|
||||||
<view class="student-header">
|
<view class="student-header">
|
||||||
<view class="header-left">
|
<view class="header-left">
|
||||||
<text class="form-title">选择学生</text>
|
<text class="form-title" style="margin-bottom: 0;">选择学生</text>
|
||||||
<text class="student-count">已选({{ selectedStudentIds.length }}/{{ allStudents.length }})</text>
|
<text class="student-count">已选({{ selectedStudentIds.length }}/{{ allStudents.length }})</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="header-actions">
|
<view class="header-actions">
|
||||||
@@ -111,11 +110,12 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else class="student-list">
|
<view v-else class="student-list">
|
||||||
<view v-for="student in allStudents" :key="student.id" class="student-item"
|
<view v-for="student in allStudents" :key="student.studentId" class="student-item"
|
||||||
:class="{ checked: selectedStudentIds.includes(student.id) }"
|
:class="{ checked: selectedStudentIds.includes(student.studentId) }"
|
||||||
@click="toggleStudentSelect(student.id)">
|
@click="toggleStudentSelect(student.studentId)">
|
||||||
<view class="student-checkbox" :class="{ checked: selectedStudentIds.includes(student.id) }">
|
<view class="student-checkbox"
|
||||||
<u-icon v-if="selectedStudentIds.includes(student.id)" name="checkmark" size="14"
|
:class="{ checked: selectedStudentIds.includes(student.studentId) }">
|
||||||
|
<u-icon v-if="selectedStudentIds.includes(student.studentId)" name="checkmark" size="14"
|
||||||
color="#fff"></u-icon>
|
color="#fff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<view class="student-avatar">
|
<view class="student-avatar">
|
||||||
@@ -123,12 +123,6 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="student-info">
|
<view class="student-info">
|
||||||
<view class="student-name">{{ student.name }}</view>
|
<view class="student-name">{{ student.name }}</view>
|
||||||
<view class="student-meta">
|
|
||||||
<text class="gender-badge" :class="student.gender === '男' ? 'male' : 'female'">
|
|
||||||
{{ student.gender }}
|
|
||||||
</text>
|
|
||||||
<text class="age-text">{{ student.age }}岁</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -139,10 +133,10 @@
|
|||||||
<text class="preview-title">已选学生</text>
|
<text class="preview-title">已选学生</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="preview-list">
|
<view class="preview-list">
|
||||||
<view v-for="(student, index) in selectedStudents" :key="student.id" class="preview-item">
|
<view v-for="(student, index) in selectedStudents" :key="student.studentId" class="preview-item">
|
||||||
<text class="preview-index">{{ index + 1 }}</text>
|
<text class="preview-index">{{ index + 1 }}</text>
|
||||||
<text class="preview-name">{{ student.name }}</text>
|
<text class="preview-name">{{ student.name }}</text>
|
||||||
<view class="preview-remove" @click.stop="removeSelectedStudent(student.id)">
|
<view class="preview-remove" @click.stop="removeSelectedStudent(student.studentId)">
|
||||||
<u-icon name="close" size="14" color="#ff4d4f"></u-icon>
|
<u-icon name="close" size="14" color="#ff4d4f"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -150,7 +144,9 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="" style="width: 100%; height: 80rpx;">
|
||||||
|
|
||||||
|
</view>
|
||||||
<!-- 底部按钮区域 -->
|
<!-- 底部按钮区域 -->
|
||||||
<view class="bottom-actions">
|
<view class="bottom-actions">
|
||||||
<view class="action-buttons">
|
<view class="action-buttons">
|
||||||
@@ -164,29 +160,24 @@
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
import { studentService } from '@/Service/swimming/studentService'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 学生类型
|
|
||||||
interface Student {
|
|
||||||
id : string
|
|
||||||
name : string
|
|
||||||
gender : string
|
|
||||||
age : string
|
|
||||||
school ?: string
|
|
||||||
address ?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 课程数据
|
// 课程数据
|
||||||
const courseData = ref({
|
const courseData = ref({
|
||||||
projectName: '',
|
projectName: '',
|
||||||
startType: 'together', // together | interval
|
startType: 'together', // together | interval
|
||||||
intervalSeconds: '',
|
intervalSeconds: '',
|
||||||
laneType: 'single', // single | multi
|
groupPersonCount: '',
|
||||||
laneCount: '',
|
segmentDistance: '',
|
||||||
lanePersonCount: ''
|
segmentCount: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 所有学生列表
|
// 所有学生列表
|
||||||
const allStudents = ref<Student[]>([])
|
const allStudents = ref<Array<any>>([])
|
||||||
|
|
||||||
// 已选学生ID列表
|
// 已选学生ID列表
|
||||||
const selectedStudentIds = ref<string[]>([])
|
const selectedStudentIds = ref<string[]>([])
|
||||||
@@ -194,60 +185,62 @@
|
|||||||
// 加载状态
|
// 加载状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
onLoad(() => {
|
let type = ref('')
|
||||||
|
let planId=ref('')
|
||||||
|
onLoad((data : any) => {
|
||||||
|
type.value = data.type
|
||||||
|
if(data.id){
|
||||||
|
planId.value=data.id
|
||||||
|
getPlanInfo()
|
||||||
|
}
|
||||||
getStudentList()
|
getStudentList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 获取计划详情
|
||||||
|
const getPlanInfo = () => {
|
||||||
|
PlanService.GetPlanInfo(planId.value).then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
selectedStudentIds.value=res.data.plan.users.map((item:any)=>{
|
||||||
|
return item.studentId
|
||||||
|
})
|
||||||
|
courseData.value.groupPersonCount=res.data.plan.groupInt
|
||||||
|
courseData.value.segmentDistance=res.data.plan.subsectionDistance
|
||||||
|
courseData.value.segmentCount=res.data.plan.subsectionInt
|
||||||
|
courseData.value.projectName=res.data.plan.name
|
||||||
|
courseData.value.startType = res.data.plan.departType == '间隔出发' ? 'interval' : 'together'
|
||||||
|
courseData.value.intervalSeconds = res.data.plan.interval
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 是否全选
|
// 是否全选
|
||||||
const allSelected = computed(() => {
|
const allSelected = computed(() => {
|
||||||
return allStudents.value.length > 0 && allStudents.value.every(s => selectedStudentIds.value.includes(s.id))
|
return allStudents.value.length > 0 && allStudents.value.every(s => selectedStudentIds.value.includes(s.studentId))
|
||||||
})
|
})
|
||||||
|
|
||||||
// 已选学生列表(按选择顺序排序)
|
// 已选学生列表(按选择顺序排序)
|
||||||
const selectedStudents = computed(() => {
|
const selectedStudents = computed(() => {
|
||||||
return selectedStudentIds.value
|
return selectedStudentIds.value
|
||||||
.map(id => allStudents.value.find(s => s.id === id))
|
.map(id => allStudents.value.find(s => s.studentId === id))
|
||||||
.filter((s) : s is Student => s !== undefined)
|
.filter((s) : s is Student => s !== undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 选择单泳道
|
|
||||||
const selectSingleLane = () => {
|
|
||||||
courseData.value.laneType = 'single'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择多泳道
|
|
||||||
const selectMultiLane = () => {
|
|
||||||
courseData.value.laneType = 'multi'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取学生列表(模拟接口)
|
// 获取学生列表(模拟接口)
|
||||||
const getStudentList = async () => {
|
const getStudentList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
|
||||||
// TODO: 实际项目中应从接口获取
|
|
||||||
// const res = await Service.Request('/api/students', 'GET', {})
|
|
||||||
// allStudents.value = res.data
|
|
||||||
|
|
||||||
// 模拟接口延迟
|
studentService.GetStudentList().then(res => {
|
||||||
await new Promise(resolve => setTimeout(resolve, 500))
|
if (res.code == 0) {
|
||||||
|
allStudents.value = res.data
|
||||||
// 假数据
|
loading.value = false
|
||||||
allStudents.value = [
|
} else {
|
||||||
{ id: '001', name: '张三', gender: '男', age: '12', school: '第一小学' },
|
Service.Msg(res.msg)
|
||||||
{ id: '002', name: '李四', gender: '女', age: '13', school: '第二小学' },
|
}
|
||||||
{ id: '003', name: '王五', gender: '男', age: '11', school: '第一小学' },
|
})
|
||||||
{ id: '004', name: '赵六', gender: '女', age: '12', school: '第三小学' },
|
|
||||||
{ id: '005', name: '钱七', gender: '男', age: '14', school: '第二小学' },
|
|
||||||
{ id: '006', name: '孙八', gender: '女', age: '10', school: '第一小学' },
|
|
||||||
{ id: '007', name: '周九', gender: '男', age: '13', school: '第四小学' },
|
|
||||||
{ id: '008', name: '吴十', gender: '女', age: '12', school: '第二小学' }
|
|
||||||
]
|
|
||||||
} catch (error) {
|
|
||||||
Service.Msg('获取学生列表失败')
|
|
||||||
console.error(error)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换学生选中状态
|
// 切换学生选中状态
|
||||||
@@ -267,7 +260,7 @@
|
|||||||
selectedStudentIds.value = []
|
selectedStudentIds.value = []
|
||||||
} else {
|
} else {
|
||||||
// 全选
|
// 全选
|
||||||
selectedStudentIds.value = allStudents.value.map(s => s.id)
|
selectedStudentIds.value = allStudents.value.map(s => s.studentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,11 +272,70 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 保存项目
|
// 保存项目
|
||||||
const confirmCreate = () => {
|
const confirmCreate = () => {
|
||||||
|
if (!courseData.value.projectName) {
|
||||||
|
Service.Msg('请输入项目名称!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (courseData.value.startType == 'interval' && !courseData.value.intervalSeconds) {
|
||||||
|
Service.Msg('请输入正确时间间隔!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!courseData.value.groupPersonCount && type.value=='1') {
|
||||||
|
Service.Msg('请输入每组人数!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!courseData.value.segmentDistance && type.value=='2') {
|
||||||
|
Service.Msg('请输入分段距离')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!courseData.value.segmentCount && type.value=='2') {
|
||||||
|
Service.Msg('请输入分段数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let users=[]
|
||||||
|
selectedStudents.value.map((item:any)=>{
|
||||||
|
users.push({
|
||||||
|
studentId:item.studentId,
|
||||||
|
name:item.name
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
planId: planId.value,
|
||||||
|
name: courseData.value.projectName,
|
||||||
|
planType:type.value=='1'?'计时项目':'分段项目',
|
||||||
|
departType: courseData.value.startType=='together'?'一起出发':'间隔出发',
|
||||||
|
interval: courseData.value.startType=='together'?0:courseData.value.intervalSeconds,
|
||||||
|
groupInt: courseData.value.groupPersonCount?courseData.value.groupPersonCount:'',
|
||||||
|
subsectionDistance: courseData.value.segmentDistance?courseData.value.segmentDistance:'',
|
||||||
|
subsectionInt: courseData.value.segmentCount?courseData.value.segmentCount:'',
|
||||||
|
users: JSON.stringify(users),
|
||||||
|
project: '',
|
||||||
|
group: '',
|
||||||
|
}
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
PlanService.AddPlan(data).then(res=>{
|
||||||
|
if(res.code==0){
|
||||||
|
Service.Msg('添加成功!')
|
||||||
|
setTimeout(()=>{
|
||||||
|
Service.GoPageBack()
|
||||||
|
},1000)
|
||||||
|
}else{
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -446,15 +498,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 多泳道选项 */
|
/* 组别选项 */
|
||||||
.multi-lane-options {
|
.group-options {
|
||||||
padding-top: 20rpx;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lane-input-wrapper,
|
.group-input-wrapper,
|
||||||
.person-input-wrapper {
|
.person-input-wrapper {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
@@ -463,7 +514,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.lane-label,
|
.group-label,
|
||||||
.person-label {
|
.person-label {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -471,7 +522,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lane-input,
|
.group-input,
|
||||||
.person-input {
|
.person-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-left: 24rpx;
|
margin-left: 24rpx;
|
||||||
@@ -495,6 +546,54 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 分段设置选项 */
|
||||||
|
.segment-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment-input-wrapper,
|
||||||
|
.segment-count-wrapper {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.segment-label,
|
||||||
|
.segment-count-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment-input,
|
||||||
|
.segment-count-input {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
|
||||||
|
.number-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-left: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 学生列表头部 */
|
/* 学生列表头部 */
|
||||||
.student-header {
|
.student-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -785,7 +884,7 @@
|
|||||||
|
|
||||||
.cancel-btn,
|
.cancel-btn,
|
||||||
.confirm-btn {
|
.confirm-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 88rpx;
|
height: 88rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -1,902 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="course-container">
|
|
||||||
<!-- 表单区域 -->
|
|
||||||
<view class="form-section">
|
|
||||||
<!-- 项目名称 -->
|
|
||||||
<view class="form-card">
|
|
||||||
<view class="form-title">项目信息</view>
|
|
||||||
<view class="form-group">
|
|
||||||
<text class="form-label">项目名称</text>
|
|
||||||
<input class="form-input" v-model="courseData.projectName" placeholder="请输入项目名称" placeholder-class="input-placeholder" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 出发方式 -->
|
|
||||||
<view class="form-card">
|
|
||||||
<view class="form-title">出发方式</view>
|
|
||||||
<view class="radio-group">
|
|
||||||
<view class="radio-item" :class="{ active: courseData.startType === 'together' }" @click="courseData.startType = 'together'">
|
|
||||||
<view class="radio-icon">
|
|
||||||
<view v-if="courseData.startType === 'together'" class="radio-inner"></view>
|
|
||||||
</view>
|
|
||||||
<text>一起出发</text>
|
|
||||||
</view>
|
|
||||||
<view class="radio-item" :class="{ active: courseData.startType === 'interval' }" @click="courseData.startType = 'interval'">
|
|
||||||
<view class="radio-icon">
|
|
||||||
<view v-if="courseData.startType === 'interval'" class="radio-inner"></view>
|
|
||||||
</view>
|
|
||||||
<text>间隔出发</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view v-if="courseData.startType === 'interval'" class="interval-input-wrapper">
|
|
||||||
<text class="interval-label">间隔时间</text>
|
|
||||||
<view class="interval-input">
|
|
||||||
<input class="number-input" v-model="courseData.intervalSeconds" type="digit" placeholder="请输入秒数" />
|
|
||||||
<text class="unit-text">秒</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 泳道设置 -->
|
|
||||||
<view class="form-card">
|
|
||||||
<view class="form-title">泳道设置</view>
|
|
||||||
<view class="radio-group">
|
|
||||||
<view class="radio-item" :class="{ active: courseData.laneType === 'single' }" @click="selectSingleLane">
|
|
||||||
<view class="radio-icon">
|
|
||||||
<view v-if="courseData.laneType === 'single'" class="radio-inner"></view>
|
|
||||||
</view>
|
|
||||||
<text>一个泳道</text>
|
|
||||||
</view>
|
|
||||||
<view class="radio-item" :class="{ active: courseData.laneType === 'multi' }" @click="selectMultiLane">
|
|
||||||
<view class="radio-icon">
|
|
||||||
<view v-if="courseData.laneType === 'multi'" class="radio-inner"></view>
|
|
||||||
</view>
|
|
||||||
<text>多个泳道</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view v-if="courseData.laneType === 'multi'" class="multi-lane-options">
|
|
||||||
<view class="sub-option-item" :class="{ active: courseData.multiLaneMode === 'onePerson' }" @click="courseData.multiLaneMode = 'onePerson'">
|
|
||||||
<view class="sub-option-icon">
|
|
||||||
<view v-if="courseData.multiLaneMode === 'onePerson'" class="option-inner"></view>
|
|
||||||
</view>
|
|
||||||
<text>一个泳道一个人</text>
|
|
||||||
</view>
|
|
||||||
<view class="sub-option-item" :class="{ active: courseData.multiLaneMode === 'multiPerson' }" @click="courseData.multiLaneMode = 'multiPerson'">
|
|
||||||
<view class="sub-option-icon">
|
|
||||||
<view v-if="courseData.multiLaneMode === 'multiPerson'" class="option-inner"></view>
|
|
||||||
</view>
|
|
||||||
<text>一个泳道几个人</text>
|
|
||||||
</view>
|
|
||||||
<view v-if="courseData.multiLaneMode === 'multiPerson'" class="multi-person-input-wrapper">
|
|
||||||
<text class="multi-label">每个泳道人数</text>
|
|
||||||
<view class="multi-input">
|
|
||||||
<input class="number-input" v-model="courseData.lanePersonCount" type="number" placeholder="请输入人数" />
|
|
||||||
<text class="unit-text">人</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 学生列表 -->
|
|
||||||
<view class="form-card">
|
|
||||||
<view class="student-header">
|
|
||||||
<view class="header-left">
|
|
||||||
<text class="form-title">选择学生</text>
|
|
||||||
<text class="student-count">已选({{ selectedStudentIds.length }}/{{ allStudents.length }})</text>
|
|
||||||
</view>
|
|
||||||
<view class="header-actions">
|
|
||||||
<view class="select-all-btn" @click="toggleSelectAll">
|
|
||||||
<view class="checkbox-icon" :class="{ checked: allSelected }">
|
|
||||||
<u-icon v-if="allSelected" name="checkmark" size="14" color="#fff"></u-icon>
|
|
||||||
</view>
|
|
||||||
<text class="select-all-text">{{ allSelected ? '取消全选' : '全选' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="loading" class="loading-state">
|
|
||||||
<view class="loading-spinner"></view>
|
|
||||||
<text class="loading-text">加载中...</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-else-if="allStudents.length === 0" class="empty-student-state">
|
|
||||||
<view class="empty-icon">
|
|
||||||
<u-icon name="account" size="48" color="#ddd"></u-icon>
|
|
||||||
</view>
|
|
||||||
<text class="empty-text">暂无学生</text>
|
|
||||||
<text class="empty-desc">请先在学员管理中添加学生</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-else class="student-list">
|
|
||||||
<view v-for="student in allStudents" :key="student.id" class="student-item" @click="toggleStudentSelect(student.id)">
|
|
||||||
<view class="student-checkbox" :class="{ checked: selectedStudentIds.includes(student.id) }">
|
|
||||||
<u-icon v-if="selectedStudentIds.includes(student.id)" name="checkmark" size="14" color="#fff"></u-icon>
|
|
||||||
</view>
|
|
||||||
<view class="student-avatar">
|
|
||||||
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="student-info">
|
|
||||||
<view class="student-name">{{ student.name }}</view>
|
|
||||||
<view class="student-meta">
|
|
||||||
<text class="gender-badge" :class="student.gender === '男' ? 'male' : 'female'">
|
|
||||||
{{ student.gender }}
|
|
||||||
</text>
|
|
||||||
<text class="age-text">{{ student.age }}岁</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 已选学生预览 -->
|
|
||||||
<view v-if="selectedStudents.length > 0" class="selected-preview">
|
|
||||||
<view class="preview-header">
|
|
||||||
<text class="preview-title">已选学生</text>
|
|
||||||
</view>
|
|
||||||
<view class="preview-list">
|
|
||||||
<view v-for="(student, index) in selectedStudents" :key="student.id" class="preview-item">
|
|
||||||
<text class="preview-index">{{ index + 1 }}</text>
|
|
||||||
<text class="preview-name">{{ student.name }}</text>
|
|
||||||
<view class="preview-remove" @click.stop="removeSelectedStudent(student.id)">
|
|
||||||
<u-icon name="close" size="14" color="#ff4d4f"></u-icon>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部按钮区域 -->
|
|
||||||
<view class="bottom-actions">
|
|
||||||
<view class="action-buttons">
|
|
||||||
<button class="cancel-btn" @click="goBack">取消</button>
|
|
||||||
<button class="confirm-btn" @click="confirmCreate">创建项目</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, onMounted } from 'vue'
|
|
||||||
import { Service } from '@/Service/Service'
|
|
||||||
|
|
||||||
// 学生类型
|
|
||||||
interface Student {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
gender: string
|
|
||||||
age: string
|
|
||||||
school?: string
|
|
||||||
address?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 课程数据
|
|
||||||
const courseData = ref({
|
|
||||||
projectName: '',
|
|
||||||
startType: 'together', // together | interval
|
|
||||||
intervalSeconds: '',
|
|
||||||
intervalSeconds: '',
|
|
||||||
laneType: 'single', // single | multi
|
|
||||||
multiLaneMode: 'onePerson', // onePerson | multiPerson
|
|
||||||
lanePersonCount: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 所有学生列表
|
|
||||||
const allStudents = ref<Student[]>([])
|
|
||||||
|
|
||||||
// 已选学生ID列表
|
|
||||||
const selectedStudentIds = ref<string[]>([])
|
|
||||||
|
|
||||||
// 加载状态
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
// 是否全选
|
|
||||||
const allSelected = computed(() => {
|
|
||||||
return allStudents.value.length > 0 && allStudents.value.every(s => selectedStudentIds.value.includes(s.id))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 已选学生列表
|
|
||||||
const selectedStudents = computed(() => {
|
|
||||||
return allStudents.value.filter(s => selectedStudentIds.value.includes(s.id))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 选择单泳道
|
|
||||||
const selectSingleLane = () => {
|
|
||||||
courseData.value.laneType = 'single'
|
|
||||||
courseData.value.multiLaneMode = 'onePerson'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择多泳道
|
|
||||||
const selectMultiLane = () => {
|
|
||||||
courseData.value.laneType = 'multi'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取学生列表(模拟接口)
|
|
||||||
const getStudentList = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
// TODO: 实际项目中应从接口获取
|
|
||||||
// const res = await Service.Request('/api/students', 'GET', {})
|
|
||||||
// allStudents.value = res.data
|
|
||||||
|
|
||||||
// 模拟接口延迟
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500))
|
|
||||||
|
|
||||||
// 假数据
|
|
||||||
allStudents.value = [
|
|
||||||
{ id: '001', name: '张三', gender: '男', age: '12', school: '第一小学' },
|
|
||||||
{ id: '002', name: '李四', gender: '女', age: '13', school: '第二小学' },
|
|
||||||
{ id: '003', name: '王五', gender: '男', age: '11', school: '第一小学' },
|
|
||||||
{ id: '004', name: '赵六', gender: '女', age: '12', school: '第三小学' },
|
|
||||||
{ id: '005', name: '钱七', gender: '男', age: '14', school: '第二小学' },
|
|
||||||
{ id: '006', name: '孙八', gender: '女', age: '10', school: '第一小学' },
|
|
||||||
{ id: '007', name: '周九', gender: '男', age: '13', school: '第四小学' },
|
|
||||||
{ id: '008', name: '吴十', gender: '女', age: '12', school: '第二小学' }
|
|
||||||
]
|
|
||||||
} catch (error) {
|
|
||||||
Service.Msg('获取学生列表失败')
|
|
||||||
console.error(error)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换学生选中状态
|
|
||||||
const toggleStudentSelect = (id: string) => {
|
|
||||||
const index = selectedStudentIds.value.indexOf(id)
|
|
||||||
if (index > -1) {
|
|
||||||
selectedStudentIds.value.splice(index, 1)
|
|
||||||
} else {
|
|
||||||
selectedStudentIds.value.push(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全选/取消全选
|
|
||||||
const toggleSelectAll = () => {
|
|
||||||
if (allSelected.value) {
|
|
||||||
// 取消全选
|
|
||||||
selectedStudentIds.value = []
|
|
||||||
} else {
|
|
||||||
// 全选
|
|
||||||
selectedStudentIds.value = allStudents.value.map(s => s.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除已选学生
|
|
||||||
const removeSelectedStudent = (id: string) => {
|
|
||||||
const index = selectedStudentIds.value.indexOf(id)
|
|
||||||
if (index > -1) {
|
|
||||||
selectedStudentIds.value.splice(index, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回
|
|
||||||
const goBack = () => {
|
|
||||||
Service.GoPageBack()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建项目
|
|
||||||
const confirmCreate = () => {
|
|
||||||
if (!courseData.value.projectName.trim()) {
|
|
||||||
Service.Msg('请输入项目名称')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (courseData.value.startType === 'interval' && !courseData.value.intervalSeconds.trim()) {
|
|
||||||
Service.Msg('请输入间隔秒数')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (courseData.value.laneType === 'multi' && courseData.value.multiLaneMode === 'multiPerson' && !courseData.value.lanePersonCount.trim()) {
|
|
||||||
Service.Msg('请输入每个泳道人数')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedStudentIds.value.length === 0) {
|
|
||||||
Service.Msg('请至少选择一位学生')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 调用API创建项目
|
|
||||||
console.log('创建项目数据:', {
|
|
||||||
course: courseData.value,
|
|
||||||
studentIds: selectedStudentIds.value,
|
|
||||||
students: selectedStudents.value
|
|
||||||
})
|
|
||||||
|
|
||||||
Service.Msg('创建成功', 'success')
|
|
||||||
setTimeout(() => {
|
|
||||||
Service.GoPageBack()
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载时获取学生列表
|
|
||||||
onMounted(() => {
|
|
||||||
getStudentList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
page {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.course-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
padding-bottom: 140rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表单区域 */
|
|
||||||
.form-section {
|
|
||||||
padding: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-card {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
padding: 32rpx 28rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
||||||
|
|
||||||
.form-title {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 28rpx;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表单输入 */
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 32rpx;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
display: block;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 14rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input {
|
|
||||||
width: 100%;
|
|
||||||
height: 88rpx;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 0 24rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
background-color: #fff;
|
|
||||||
box-shadow: 0 0 0 4rpx rgba(24, 144, 255, 0.15);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 单选按钮组 */
|
|
||||||
.radio-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 20rpx;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
|
|
||||||
.radio-item {
|
|
||||||
flex: 1;
|
|
||||||
height: 88rpx;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 12rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #666;
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
border: 2rpx solid transparent;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.96);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
font-weight: 600;
|
|
||||||
background-color: #e6f7ff;
|
|
||||||
border-color: #1890ff;
|
|
||||||
color: #1890ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-icon {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 3rpx solid #d9d9d9;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active .radio-icon {
|
|
||||||
border-color: #1890ff;
|
|
||||||
background-color: #1890ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-inner {
|
|
||||||
width: 12rpx;
|
|
||||||
height: 12rpx;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 间隔时间输入 */
|
|
||||||
.interval-input-wrapper {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 24rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.interval-label {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.interval-input {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 24rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
padding: 0 20rpx;
|
|
||||||
height: 72rpx;
|
|
||||||
|
|
||||||
.number-input {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unit-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-left: 12rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 多泳道选项 */
|
|
||||||
.multi-lane-options {
|
|
||||||
padding-top: 20rpx;
|
|
||||||
|
|
||||||
.sub-option-item {
|
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
border: 2rpx solid transparent;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
font-weight: 600;
|
|
||||||
background-color: #e6f7ff;
|
|
||||||
border-color: #1890ff;
|
|
||||||
color: #1890ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-option-icon {
|
|
||||||
width: 28rpx;
|
|
||||||
height: 28rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2rpx solid #d9d9d9;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active .sub-option-icon {
|
|
||||||
border-color: #1890ff;
|
|
||||||
background-color: #1890ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-inner {
|
|
||||||
width: 10rpx;
|
|
||||||
height: 10rpx;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 多泳道人数输入 */
|
|
||||||
.multi-person-input-wrapper {
|
|
||||||
margin-top: 20rpx;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 24rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.multi-label {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multi-input {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 24rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
padding: 0 20rpx;
|
|
||||||
height: 72rpx;
|
|
||||||
|
|
||||||
.number-input {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unit-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-left: 12rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 学生列表头部 */
|
|
||||||
.student-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
|
|
||||||
.header-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
|
|
||||||
.student-count {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions {
|
|
||||||
.select-all-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
padding: 8rpx 16rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.96);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-icon {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2rpx solid #d9d9d9;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&.checked {
|
|
||||||
border-color: #1890ff;
|
|
||||||
background-color: #1890ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-all-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载状态 */
|
|
||||||
.loading-state {
|
|
||||||
padding: 80rpx 40rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
border: 4rpx solid #f0f0f0;
|
|
||||||
border-top-color: #1890ff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 空状态 */
|
|
||||||
.empty-student-state {
|
|
||||||
padding: 60rpx 40rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-desc {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 学生列表 */
|
|
||||||
.student-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12rpx;
|
|
||||||
|
|
||||||
.student-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16rpx;
|
|
||||||
padding: 20rpx;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
background-color: #e8e8e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-checkbox {
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2rpx solid #d9d9d9;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&.checked {
|
|
||||||
border-color: #1890ff;
|
|
||||||
background-color: #1890ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-avatar {
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
|
||||||
border-radius: 14rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
.avatar-text {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
.student-name {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 6rpx;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12rpx;
|
|
||||||
|
|
||||||
.gender-badge {
|
|
||||||
font-size: 22rpx;
|
|
||||||
padding: 4rpx 10rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&.male {
|
|
||||||
color: #1890ff;
|
|
||||||
background-color: #e6f7ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.female {
|
|
||||||
color: #fa8c16;
|
|
||||||
background-color: #fff7e6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.age-text {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 已选学生预览 */
|
|
||||||
.selected-preview {
|
|
||||||
margin-top: 32rpx;
|
|
||||||
padding-top: 24rpx;
|
|
||||||
border-top: 1rpx solid #f0f0f0;
|
|
||||||
|
|
||||||
.preview-header {
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
|
|
||||||
.preview-title {
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-list {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
padding: 10rpx 16rpx;
|
|
||||||
background-color: #e6f7ff;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
|
|
||||||
.preview-index {
|
|
||||||
width: 24rpx;
|
|
||||||
height: 24rpx;
|
|
||||||
background-color: #1890ff;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 18rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-name {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #1890ff;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-remove {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 底部操作按钮 */
|
|
||||||
.bottom-actions {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 20rpx 30rpx;
|
|
||||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|
||||||
border-top: 1rpx solid #f0f0f0;
|
|
||||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
|
|
||||||
z-index: 99;
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn,
|
|
||||||
.confirm-btn {
|
|
||||||
flex: 1;
|
|
||||||
height: 88rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
border: none;
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
color: #666;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: #e8e8e8;
|
|
||||||
transform: scale(0.96);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-btn {
|
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
|
|
||||||
color: #fff;
|
|
||||||
box-shadow: 0 6rpx 16rpx rgba(24, 144, 255, 0.35);
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.96);
|
|
||||||
box-shadow: 0 3rpx 8rpx rgba(24, 144, 255, 0.25);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 动画 */
|
|
||||||
@keyframes spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -27,18 +27,18 @@
|
|||||||
<view class="student-details">
|
<view class="student-details">
|
||||||
<view class="detail-row">
|
<view class="detail-row">
|
||||||
<text class="student-name">{{ student.name }}</text>
|
<text class="student-name">{{ student.name }}</text>
|
||||||
<view class="student-gender" :class="student.gender === '男' ? 'male' : 'female'">
|
<view class="student-gender" :class="student.sex === '男' ? 'male' : 'female'">
|
||||||
<u-icon :name="student.gender === '男' ? 'man' : 'woman'" size="14"></u-icon>
|
<u-icon :name="student.sex === '男' ? 'man' : 'woman'" size="14"></u-icon>
|
||||||
{{ student.gender }}
|
{{ student.sex }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="detail-row">
|
<view class="detail-row">
|
||||||
<text class="detail-label">出生日期:</text>
|
<text class="detail-label">出生日期:</text>
|
||||||
<text class="detail-value">{{ student.birthDate }}</text>
|
<text class="detail-value">{{ student.birthday }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="detail-row">
|
<view class="detail-row">
|
||||||
<text class="detail-label">年龄:</text>
|
<text class="detail-label">年龄:</text>
|
||||||
<text class="detail-value">{{ student.age }}岁</text>
|
<text class="detail-value">{{ calculateAge(student.birthday) }}岁</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="student.school" class="detail-row">
|
<view v-if="student.school" class="detail-row">
|
||||||
<text class="detail-label">学校:</text>
|
<text class="detail-label">学校:</text>
|
||||||
@@ -54,14 +54,14 @@
|
|||||||
<view class="action-btn edit-btn" @click="openEditModal(student)">
|
<view class="action-btn edit-btn" @click="openEditModal(student)">
|
||||||
<u-icon name="edit-pen" size="18" color="#1890ff"></u-icon>
|
<u-icon name="edit-pen" size="18" color="#1890ff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<view class="action-btn delete-btn" @click="confirmDelete(student)">
|
|
||||||
<u-icon name="trash" size="18" color="#ff4d4f"></u-icon>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="" style="width: 100%; height: 100rpx;" >
|
||||||
|
|
||||||
|
</view>
|
||||||
<!-- 底部添加按钮 -->
|
<!-- 底部添加按钮 -->
|
||||||
<view class="bottom-add-btn">
|
<view class="bottom-add-btn">
|
||||||
<button class="add-btn" @click="showAddModal = true">
|
<button class="add-btn" @click="showAddModal = true">
|
||||||
@@ -143,54 +143,37 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onShow, onLoad } from "@dcloudio/uni-app"
|
import { onShow, onLoad, onReachBottom } from "@dcloudio/uni-app"
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
|
import { studentService } from '@/Service/swimming/studentService'
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
|
||||||
|
|
||||||
// 定义学员类型
|
|
||||||
interface Student {
|
|
||||||
id : string
|
// 分页相关
|
||||||
name : string
|
let page = ref(1)
|
||||||
gender : string
|
let status = ref('loadmore')
|
||||||
age : string
|
|
||||||
birthDate : string
|
|
||||||
school : string
|
|
||||||
address : string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 学员列表
|
// 学员列表
|
||||||
const students = ref<Student[]>([
|
const students = ref<Array<any>>([])
|
||||||
{ id: '001', name: '张三', gender: '男', age: '12', birthDate: '2012-05-15', school: '第一小学', address: '北京市朝阳区' },
|
|
||||||
{ id: '002', name: '李四', gender: '女', age: '13', birthDate: '2011-08-20', school: '', address: '' },
|
|
||||||
{ id: '003', name: '王五', gender: '男', age: '11', birthDate: '2013-03-10', school: '第二小学', address: '' },
|
|
||||||
{ id: '004', name: '赵六', gender: '女', age: '12', birthDate: '2012-11-25', school: '', address: '上海市浦东新区' }
|
|
||||||
])
|
|
||||||
|
|
||||||
// 弹窗状态
|
// 弹窗状态
|
||||||
const showAddModal = ref(false)
|
const showAddModal = ref(false)
|
||||||
const showEditModal = ref(false)
|
const showEditModal = ref(false)
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const formData = ref({
|
const formData = ref<any>({})
|
||||||
id: '',
|
|
||||||
name: '',
|
|
||||||
gender: '男',
|
|
||||||
age: '',
|
|
||||||
birthDate: '',
|
|
||||||
school: '',
|
|
||||||
address: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 当前编辑的学员
|
// 当前编辑的学员
|
||||||
const editingStudent = ref<Student | null>(null)
|
const editingStudent = ref<any>({})
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
|
getData()
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onReachBottom(() => {
|
||||||
|
getList()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计算年龄
|
// 计算年龄
|
||||||
@@ -213,14 +196,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 打开编辑弹窗
|
// 打开编辑弹窗
|
||||||
const openEditModal = (student : Student) => {
|
const openEditModal = (student :any) => {
|
||||||
editingStudent.value = student
|
editingStudent.value = student
|
||||||
formData.value = {
|
formData.value = {
|
||||||
id: student.id,
|
id: student.studentId,
|
||||||
name: student.name,
|
name: student.name,
|
||||||
gender: student.gender,
|
gender: student.sex?student.sex: '男',
|
||||||
age: student.age,
|
birthDate: student.birthday,
|
||||||
birthDate: student.birthDate,
|
|
||||||
school: student.school,
|
school: student.school,
|
||||||
address: student.address
|
address: student.address
|
||||||
}
|
}
|
||||||
@@ -243,9 +225,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取学员列表数据
|
||||||
|
const getData = () => {
|
||||||
|
students.value = []
|
||||||
|
page.value = 1
|
||||||
|
status.value = 'loadmore'
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取学员列表
|
||||||
|
const getList = () => {
|
||||||
|
if (status.value == 'loading' || status.value == 'nomore') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status.value = 'loading'
|
||||||
|
studentService.GetStudentListPage(page.value.toString()).then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
students.value = [...students.value, ...res.data]
|
||||||
|
status.value = res.data.length == 10 ? 'loadmore' : 'nomore'
|
||||||
|
page.value++
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 保存学员
|
// 保存学员
|
||||||
const saveStudent = () => {
|
const saveStudent = () => {
|
||||||
if (!formData.value.name.trim()) {
|
if (!formData.value.name) {
|
||||||
Service.Msg('请输入学员姓名')
|
Service.Msg('请输入学员姓名')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -255,35 +262,26 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showAddModal.value) {
|
// 构造接口请求数据
|
||||||
// 添加新学员
|
const requestData = {
|
||||||
const newStudent : Student = {
|
studentId:formData.value.id,
|
||||||
id: Date.now().toString().slice(-6),
|
name: formData.value.name,
|
||||||
name: formData.value.name.trim(),
|
sex: formData.value.gender,
|
||||||
gender: formData.value.gender,
|
birthday: formData.value.birthDate,
|
||||||
age: formData.value.age,
|
school: formData.value.school ,
|
||||||
birthDate: formData.value.birthDate,
|
address: formData.value.address
|
||||||
school: formData.value.school.trim(),
|
|
||||||
address: formData.value.address.trim()
|
|
||||||
}
|
|
||||||
students.value.push(newStudent)
|
|
||||||
Service.Msg('添加成功', 'success')
|
|
||||||
} else if (showEditModal.value && editingStudent.value) {
|
|
||||||
// 编辑学员
|
|
||||||
const index = students.value.findIndex(s => s.id === editingStudent.value!.id)
|
|
||||||
if (index !== -1) {
|
|
||||||
students.value[index] = {
|
|
||||||
...students.value[index],
|
|
||||||
name: formData.value.name.trim(),
|
|
||||||
gender: formData.value.gender,
|
|
||||||
age: formData.value.age,
|
|
||||||
birthDate: formData.value.birthDate,
|
|
||||||
school: formData.value.school.trim(),
|
|
||||||
address: formData.value.address.trim()
|
|
||||||
}
|
|
||||||
Service.Msg('修改成功', 'success')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
console.log(requestData);
|
||||||
|
|
||||||
|
// 调用添加学员接口
|
||||||
|
studentService.Add(requestData).then((content) => {
|
||||||
|
if (content.code == 0) {
|
||||||
|
Service.Msg('添加成功')
|
||||||
|
getData()
|
||||||
|
} else {
|
||||||
|
Service.Msg(content.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
closeModal()
|
closeModal()
|
||||||
}
|
}
|
||||||
@@ -304,8 +302,14 @@
|
|||||||
|
|
||||||
// 删除学员
|
// 删除学员
|
||||||
const deleteStudent = (id : string) => {
|
const deleteStudent = (id : string) => {
|
||||||
students.value = students.value.filter(s => s.id !== id)
|
studentService.Delete(id).then(res => {
|
||||||
Service.Msg('删除成功', 'success')
|
if (res.code == 0) {
|
||||||
|
students.value = students.value.filter(s => s.id !== id)
|
||||||
|
Service.Msg('删除成功', 'success')
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -793,8 +797,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 20rpx 30rpx;
|
padding: 20rpx 20rpx;
|
||||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|
||||||
border-top: 1rpx solid #f0f0f0;
|
border-top: 1rpx solid #f0f0f0;
|
||||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
|
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user