ltx d17f43135d feat: 完善多角色赛事服务功能
- 新增参赛者、裁判、检录员角色识别及角色化首页与个人中心
- 新增裁判注册、身份认证提示、赛事执裁报名及报名记录查询
- 新增检录工作台,支持检录任务、轮次状态与运动员检录操作
- 新增赛事成绩查询页面及成绩详情展示
- 封装成绩、检录和裁判报名接口,补充请求参数清理逻辑
- 新增检录 Tab 图标、页面路由及本地开发配置
- 新增 Git 忽略规则,排除依赖、构建缓存、日志和本地工具文件
2026-08-07 11:28:38 +08:00

443 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { API, getBaseUrl } = require('./constants');
const { request } = require('./http');
/**
* 上传文件到 /api/file/upload,返回文件 URL 字符串
*/
function uploadFile(filePath) {
return new Promise((resolve, reject) => {
const app = getApp();
const baseUrl = getBaseUrl();
const accessToken =
(app && app.globalData && app.globalData.accessToken) ||
wx.getStorageSync('accessToken') ||
'';
wx.uploadFile({
url: baseUrl + API.fileUpload,
filePath,
name: 'file',
header: accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
success(res) {
try {
const body = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
// 接口返回数组 [{name, url}] 或 RESTfulResult 包装
let url = '';
if (Array.isArray(body) && body[0] && body[0].url) {
url = body[0].url;
} else if (body && body.data && Array.isArray(body.data) && body.data[0]) {
url = body.data[0].url || '';
} else if (body && body.data && typeof body.data === 'string') {
url = body.data;
}
if (url) resolve(url);
else reject(new Error('上传失败:未获取到文件URL'));
} catch (e) {
reject(new Error('上传响应解析失败'));
}
},
fail(err) {
reject(new Error((err && err.errMsg) || '文件上传失败'));
}
});
});
}
/**
* OCR 识别身份证,返回 IdCardRecognitionResult
* { success, name, gender, idCardNumber, birthDate }
*/
async function ocrIdCard(imageUrl) {
return request({
path: API.ocrIdCard,
method: 'POST',
needAuth: true,
data: { imageUrl },
contentType: 'application/json'
});
}
/**
* OCR 识别护照,返回 PassportRecognitionResult
* { success, name, gender, passportNumber, nationality, birthDate, issueDate, expiryDate, ... }
*/
async function ocrPassport(imageUrl) {
return request({
path: API.ocrPassport,
method: 'POST',
needAuth: true,
data: { imageUrl },
contentType: 'application/json'
});
}
/**
* OCR 识别户口本,返回 HouseholdRecognitionResult
* { success, name, gender, birthDate, idCardNumber, relation, ethnicity, ... }
*/
async function ocrHousehold(imageUrl) {
return request({
path: API.ocrHousehold,
method: 'POST',
needAuth: true,
data: { imageUrl },
contentType: 'application/json'
});
}
function pickString(obj, key) {
return obj && typeof obj[key] === 'string' ? obj[key] : '';
}
let refreshPromise = null;
async function refreshAccessToken() {
if (refreshPromise) return refreshPromise;
const app = getApp();
const storedRefreshToken = wx.getStorageSync('refreshToken') || '';
const globalRefreshToken = app && app.globalData ? app.globalData.refreshToken : '';
const refreshToken = typeof globalRefreshToken === 'string' && globalRefreshToken.length > 0 ? globalRefreshToken : storedRefreshToken;
if (!refreshToken) throw new Error('refreshAccessToken: missing refreshToken');
refreshPromise = (async () => {
const tokenData = await request({
path: API.oauthRefreshToken,
method: 'POST',
needAuth: false,
data: { refreshToken },
contentType: 'application/json'
});
const accessToken = pickString(tokenData, 'accessToken');
const nextRefreshToken = pickString(tokenData, 'refreshToken') || refreshToken;
if (!accessToken) throw new Error('refreshAccessToken: missing accessToken');
app.setAuth({
accessToken,
refreshToken: nextRefreshToken
});
return {
accessToken,
refreshToken: nextRefreshToken,
tokenData
};
})().finally(() => {
refreshPromise = null;
});
return refreshPromise;
}
async function loginWithMobilePassword({ mobile, password }) {
const loginData = await request({
path: API.wechatLogin,
method: 'POST',
needAuth: false,
data: {
mobile,
password: password != null ? password : ''
},
contentType: 'application/json'
});
const app = getApp();
const accessToken = pickString(loginData, 'accessToken');
const refreshToken = pickString(loginData, 'refreshToken');
const userInfo =
loginData && loginData.userInfo && typeof loginData.userInfo === 'object'
? loginData.userInfo
: null;
const openid = userInfo ? pickString(userInfo, 'openid') : '';
app.setAuth({
accessToken,
refreshToken,
openid,
userProfile: userInfo
});
let myProfile = null;
try {
myProfile = await request({
path: API.myProfile,
method: 'GET',
needAuth: true
});
} catch (e) {
myProfile = null;
}
if (myProfile && typeof myProfile === 'object') {
app.setAuth({
userProfile: myProfile
});
}
return {
loginData,
myProfile
};
}
async function loginWithMobileCode({ mobile, verificationCode }) {
const loginData = await request({
path: API.loginByCode,
method: 'POST',
needAuth: false,
data: {
mobile,
verificationCode
},
contentType: 'application/json'
});
const app = getApp();
const accessToken = pickString(loginData, 'accessToken');
const refreshToken = pickString(loginData, 'refreshToken');
const userInfo =
loginData && loginData.userInfo && typeof loginData.userInfo === 'object'
? loginData.userInfo
: null;
const openid = userInfo ? pickString(userInfo, 'openid') : '';
app.setAuth({
accessToken,
refreshToken,
openid,
userProfile: userInfo
});
let myProfile = null;
try {
myProfile = await request({
path: API.myProfile,
method: 'GET',
needAuth: true
});
} catch (e) {
myProfile = null;
}
if (myProfile && typeof myProfile === 'object') {
app.setAuth({
userProfile: myProfile
});
}
return {
loginData,
myProfile
};
}
async function sendVerificationCode(phone) {
return request({
path: API.sendVerificationCode,
method: 'POST',
needAuth: false,
data: { phone },
contentType: 'application/json'
});
}
async function registerWithVerification({ mobile, password, verificationCode, nickName, avatar, role }) {
const data = {
mobile,
password,
verificationCode
};
if (nickName) data.nickName = nickName;
if (avatar) data.avatar = avatar;
if (role) {
// 同时保留语义值与数值枚举,联调时可按后端契约删去其中一个字段。
const roleTypeMap = { participant: 1, referee: 2 };
data.role = role;
data.roleType = roleTypeMap[role] || 1;
}
return request({
path: API.oauthRegister,
method: 'POST',
needAuth: false,
data,
contentType: 'application/json'
});
}
async function resetPassword({ mobile, password, verificationCode }) {
return request({
path: API.oauthResetPassword,
method: 'POST',
needAuth: false,
data: { mobile, newPassword: password, verificationCode },
contentType: 'application/json'
});
}
async function fetchMyProfile() {
return request({
path: API.myProfile,
method: 'GET',
needAuth: true
});
}
async function updateProfile(data) {
return request({
path: API.updateProfile,
method: 'POST',
needAuth: true,
data: data,
contentType: 'application/json'
});
}
async function fetchMyAuth() {
return request({
path: API.myAuth,
method: 'GET',
needAuth: true
});
}
async function submitMyAuth(payload) {
// 兼容旧调用方式:submitMyAuth({ name, mobile, idCardNumber, idCardImageUrl, idCardBackImageUrl })
// 新页面会直接把表单对象透传进来(如 authType、passport 字段等),后端若不识别会忽略。
const p = payload && typeof payload === 'object' ? payload : {};
return request({
path: API.myAuth,
method: 'POST',
needAuth: true,
data: p,
contentType: 'application/json'
});
}
async function fetchMyRegistrations({ pageNo = 1, pageSize = 10 } = {}) {
return request({
path: API.myRegistrations,
method: 'GET',
needAuth: true,
data: {
PageNo: pageNo,
PageSize: pageSize
}
});
}
async function fetchMyPoints({
pageNo = 1,
pageSize = 10,
// 新接口参数(按 swagger):1=获取,2=消耗,不传查全部
type,
// 单位认证ID筛选,不传查全部
merchantCertificationId,
// 兼容旧调用方:也可能传 Type / MerchantCertificationId
Type,
MerchantCertificationId
} = {}) {
const t = type != null ? type : Type;
const mcId = merchantCertificationId != null ? merchantCertificationId : MerchantCertificationId;
const data = {
PageNo: pageNo,
PageSize: pageSize
};
if (t != null && t !== '') data.Type = t;
if (mcId != null && mcId !== '') data.MerchantCertificationId = mcId;
return request({
path: API.myPoints,
method: 'GET',
needAuth: true,
data
});
}
async function fetchMyTeams({ pageNo = 1, pageSize = 20 } = {}) {
return request({
path: API.myTeams,
method: 'GET',
needAuth: true,
data: {
PageNo: pageNo,
PageSize: pageSize
}
});
}
async function fetchMyTeamDetail(id) {
if (id == null || id === '') throw new Error('缺少团队ID');
return request({
path: `${API.myTeams}/${id}`,
method: 'GET',
needAuth: true
});
}
async function fetchMyOrders({ pageNo = 1, pageSize = 10 } = {}) {
return request({
path: API.myOrders,
method: 'GET',
needAuth: true,
data: {
PageNo: pageNo,
PageSize: pageSize
}
});
}
async function fetchMyOrderDetail(id) {
if (id == null || id === '') throw new Error('缺少订单ID');
return request({
path: `${API.myOrders}/${id}`,
method: 'GET',
needAuth: true
});
}
async function fetchMyCertificates({ pageNo = 1, pageSize = 10, type } = {}) {
const data = {
PageNo: pageNo,
PageSize: pageSize
};
// type: 1=个人 2=团队 (若后端支持)
if (type != null) data.Type = type;
return request({
path: API.myCertificates,
method: 'GET',
needAuth: true,
data
});
}
module.exports = {
loginWithMobilePassword,
loginWithMobileCode,
sendVerificationCode,
registerWithVerification,
resetPassword,
fetchMyProfile,
updateProfile,
fetchMyAuth,
submitMyAuth,
fetchMyRegistrations,
fetchMyPoints,
fetchMyTeams,
fetchMyTeamDetail,
fetchMyOrders,
fetchMyOrderDetail,
fetchMyCertificates,
refreshAccessToken,
uploadFile,
ocrIdCard,
ocrPassport,
ocrHousehold
};