release: prepare August 1 Android RC
This commit is contained in:
Vendored
+5
@@ -1,7 +1,12 @@
|
||||
/// <reference types="@dcloudio/types" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly DEV: boolean;
|
||||
readonly VITE_API_BASE?: string;
|
||||
readonly VITE_APP_TERMS_URL?: string;
|
||||
readonly VITE_APP_PRIVACY_URL?: string;
|
||||
readonly VITE_DEV_SMS_HINT_ENABLED?: string;
|
||||
readonly VITE_DEV_SMS_HINT_VALUE?: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"appid" : "__UNI__B36D8BE",
|
||||
"description" : "帮道员工端",
|
||||
"vueVersion" : "3",
|
||||
"versionName" : "0.1.5",
|
||||
"versionCode" : "105",
|
||||
"versionName" : "0.1.6",
|
||||
"versionCode" : "106",
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
},
|
||||
@@ -59,6 +59,7 @@
|
||||
},
|
||||
"splashscreen" : {
|
||||
"iosStyle" : "default",
|
||||
"useOriginalMsgbox" : true,
|
||||
"ios" : {
|
||||
"iphone" : {
|
||||
"retina40" : "src/static/app/splash/ios-iphone-retina40.png",
|
||||
@@ -87,7 +88,14 @@
|
||||
},
|
||||
"android" : {
|
||||
"packagename" : "com.yincheng.wygj",
|
||||
"targetSdkVersion" : 35,
|
||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ],
|
||||
"permissionExternalStorage" : {
|
||||
"request" : "none"
|
||||
},
|
||||
"permissionPhoneState" : {
|
||||
"request" : "none"
|
||||
},
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>"
|
||||
|
||||
@@ -74,9 +74,9 @@
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
|
||||
<view class="dev-hint-pill" @click="fillDevCredentials">
|
||||
<view v-if="showDevSmsHint" class="dev-hint-pill" @click="fillDevCredentials">
|
||||
<uni-icons type="info-filled" size="14" color="#d97706" />
|
||||
<text class="dev-hint-text">测试验证码: 123456</text>
|
||||
<text class="dev-hint-text">测试验证码: {{ devSmsHintValue }}</text>
|
||||
<text class="dev-fill-btn">快捷填入</text>
|
||||
</view>
|
||||
|
||||
@@ -93,12 +93,25 @@
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="agreement-row" @click="agreed = !agreed">
|
||||
<view class="checkbox-box" :class="{ checked: agreed }">
|
||||
<uni-icons v-if="agreed" type="checkmarkempty" size="12" color="#ffffff" />
|
||||
<view class="agreement-row">
|
||||
<view
|
||||
class="agreement-consent"
|
||||
role="checkbox"
|
||||
:aria-checked="agreed"
|
||||
@click="agreed = !agreed"
|
||||
>
|
||||
<view class="checkbox-box" :class="{ checked: agreed }">
|
||||
<uni-icons v-if="agreed" type="checkmarkempty" size="12" color="#ffffff" />
|
||||
</view>
|
||||
<text class="agreement-text">已阅读并同意</text>
|
||||
</view>
|
||||
<text class="agreement-text">已阅读并同意《服务协议》与《隐私政策》</text>
|
||||
<text class="agreement-link" @click.stop="openLegalDocument('terms')">《服务协议》</text>
|
||||
<text class="agreement-text">与</text>
|
||||
<text class="agreement-link" @click.stop="openLegalDocument('privacy')">《隐私政策》</text>
|
||||
</view>
|
||||
<text v-if="!legalLinksReady" class="agreement-error">
|
||||
协议地址尚未完成法务配置,当前暂停验证码发送与登录。
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -109,6 +122,11 @@ import { onHide, onShow, onUnload } from '@dcloudio/uni-app';
|
||||
import type { LoginResult } from '@/types/api';
|
||||
import { apiRequest } from '@/services/api';
|
||||
import { consumeLoginRedirect, getAuth, isLoggedIn, phoneKey, saveAuth } from '@/services/auth';
|
||||
import {
|
||||
legalDocumentUrl,
|
||||
legalLinksReady,
|
||||
type LegalDocumentKind
|
||||
} from '@/services/legal';
|
||||
import { resetPageScroll } from '@/services/navigation';
|
||||
import { goToMobileTarget, nextUserRouteAfterLogin } from '@/services/position';
|
||||
|
||||
@@ -135,8 +153,14 @@ const formRules = {
|
||||
const sending = ref(false);
|
||||
const loggingIn = ref(false);
|
||||
const countdown = ref(0);
|
||||
const agreed = ref(true);
|
||||
const agreed = ref(false);
|
||||
const message = ref('');
|
||||
const devSmsHintValue = import.meta.env.DEV
|
||||
? String(import.meta.env.VITE_DEV_SMS_HINT_VALUE || '').replace(/\D/g, '').slice(0, 6)
|
||||
: '';
|
||||
const showDevSmsHint = import.meta.env.DEV
|
||||
&& import.meta.env.VITE_DEV_SMS_HINT_ENABLED === 'true'
|
||||
&& /^\d{4,6}$/.test(devSmsHintValue);
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const inputStyles = {
|
||||
@@ -166,8 +190,9 @@ const startCountdown = () => {
|
||||
};
|
||||
|
||||
const fillDevCredentials = () => {
|
||||
formData.smsCode = '123456';
|
||||
message.value = '已自动填入测试验证码 123456';
|
||||
if (!showDevSmsHint) return;
|
||||
formData.smsCode = devSmsHintValue;
|
||||
message.value = '已自动填入本地测试验证码';
|
||||
};
|
||||
|
||||
const normalizeDigits = (value: string | number, maxLength: number) =>
|
||||
@@ -183,6 +208,33 @@ const onSmsCodeInput = (value: string | number) => {
|
||||
formData.smsCode = normalizeDigits(value, 6);
|
||||
};
|
||||
|
||||
const ensureLegalConsent = () => {
|
||||
if (!legalLinksReady) {
|
||||
message.value = '服务协议和隐私政策地址尚未配置,暂不能发送验证码或登录';
|
||||
return false;
|
||||
}
|
||||
if (!agreed.value) {
|
||||
message.value = '请先阅读并勾选同意《服务协议》与《隐私政策》';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const openLegalDocument = (kind: LegalDocumentKind) => {
|
||||
const url = legalDocumentUrl(kind);
|
||||
if (!url) {
|
||||
message.value = '协议地址尚未完成法务配置';
|
||||
return;
|
||||
}
|
||||
if (typeof plus !== 'undefined') {
|
||||
plus.runtime.openURL(url);
|
||||
return;
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
};
|
||||
|
||||
const goAfterLogin = async () => {
|
||||
const redirect = consumeLoginRedirect() || '/pages/user/today/index';
|
||||
goToMobileTarget(await nextUserRouteAfterLogin(redirect));
|
||||
@@ -190,6 +242,7 @@ const goAfterLogin = async () => {
|
||||
|
||||
const sendCode = async () => {
|
||||
if (sending.value || countdown.value > 0) return;
|
||||
if (!ensureLegalConsent()) return;
|
||||
try {
|
||||
await loginForm.value?.validateField(['phone']);
|
||||
} catch {
|
||||
@@ -215,10 +268,7 @@ const sendCode = async () => {
|
||||
|
||||
const login = async () => {
|
||||
if (loggingIn.value) return;
|
||||
if (!agreed.value) {
|
||||
message.value = '请先勾选同意《服务协议》与《隐私政策》';
|
||||
return;
|
||||
}
|
||||
if (!ensureLegalConsent()) return;
|
||||
try {
|
||||
await loginForm.value?.validate();
|
||||
} catch {
|
||||
@@ -616,9 +666,16 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 2px;
|
||||
max-width: 720px;
|
||||
margin: 18px auto 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.agreement-consent {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -647,6 +704,24 @@ onUnmounted(() => {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: var(--employee-primary, #e60012);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.agreement-error {
|
||||
display: block;
|
||||
max-width: 720px;
|
||||
margin: 8px auto 0;
|
||||
color: #b45309;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 375px) {
|
||||
.login-shell {
|
||||
padding-right: 20px;
|
||||
|
||||
@@ -426,7 +426,7 @@ const submit = async () => {
|
||||
form.requestId = '';
|
||||
form.content = '';
|
||||
voiceHint.value = '';
|
||||
submitMessage.value = '反馈已提交,可在“我的反馈”查看处理结果。';
|
||||
submitMessage.value = '已收到,当前状态为“待处理”;可在“我的反馈”查看人工正式回复。';
|
||||
await loadMine(true);
|
||||
} catch (error) {
|
||||
submitFailed.value = true;
|
||||
|
||||
@@ -517,7 +517,11 @@ type ChatMsg = UserMsg | MasterMsg | SystemMsg | PendingMsg;
|
||||
|
||||
const positionProfile = ref(getSelectedPositionProfile());
|
||||
const positionLabel = ref(getSelectedPosition());
|
||||
const quickQuestions = computed(() => positionProfile.value.quickQuestions);
|
||||
const policyQuickQuestion = '问制度:请说明公司请假和考勤的适用规定';
|
||||
const quickQuestions = computed(() => [
|
||||
policyQuickQuestion,
|
||||
...positionProfile.value.quickQuestions.filter((item) => item !== policyQuickQuestion)
|
||||
].slice(0, 4));
|
||||
const quickQuestionPlaceholder = computed(() => `例如: ${quickQuestions.value[0] || '岗位SOP怎么处理?'}`);
|
||||
|
||||
const messages = ref<ChatMsg[]>([]);
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
export type LegalDocumentKind = 'terms' | 'privacy';
|
||||
|
||||
const placeholderPattern = /__|change[-_ ]?me|your[-_ ]?|example\.(com|org|net)/i;
|
||||
const reservedHostnamePattern = /(?:^|\.)(?:example|invalid|test)$/i;
|
||||
|
||||
const ipv4Parts = (hostname: string) => {
|
||||
const parts = hostname.split('.').map(Number);
|
||||
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
||||
return null;
|
||||
}
|
||||
return parts;
|
||||
};
|
||||
|
||||
const isReservedIpv4 = (parts: number[]) => {
|
||||
return parts[0] === 0
|
||||
|| parts[0] === 10
|
||||
|| parts[0] === 127
|
||||
|| (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127)
|
||||
|| (parts[0] === 169 && parts[1] === 254)
|
||||
|| (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
|
||||
|| (parts[0] === 192 && parts[1] === 168)
|
||||
|| (parts[0] === 192 && parts[1] === 0 && (parts[2] === 0 || parts[2] === 2))
|
||||
|| (parts[0] === 198 && (parts[1] === 18 || parts[1] === 19))
|
||||
|| (parts[0] === 198 && parts[1] === 51 && parts[2] === 100)
|
||||
|| (parts[0] === 203 && parts[1] === 0 && parts[2] === 113)
|
||||
|| parts[0] >= 224;
|
||||
};
|
||||
|
||||
const normalizePublicHttpsUrl = (rawValue?: string) => {
|
||||
const value = String(rawValue || '').trim();
|
||||
if (!value || placeholderPattern.test(value)) return '';
|
||||
if (/[\u0000-\u0020\u007f]/.test(value)) return '';
|
||||
|
||||
const match = /^https:\/\/([^/?#]+)(?:[/?#]|$)/i.exec(value);
|
||||
if (!match || match[1].includes('@')) return '';
|
||||
const authority = match[1];
|
||||
let hostname = '';
|
||||
if (authority.startsWith('[')) {
|
||||
const end = authority.indexOf(']');
|
||||
if (end <= 1 || (authority.slice(end + 1) && !/^:\d+$/.test(authority.slice(end + 1)))) return '';
|
||||
hostname = authority.slice(1, end).toLowerCase();
|
||||
} else {
|
||||
const hostAndPort = authority.toLowerCase();
|
||||
const colon = hostAndPort.lastIndexOf(':');
|
||||
if (colon >= 0) {
|
||||
if (!/^\d+$/.test(hostAndPort.slice(colon + 1)) || hostAndPort.slice(0, colon).includes(':')) return '';
|
||||
hostname = hostAndPort.slice(0, colon);
|
||||
} else {
|
||||
hostname = hostAndPort;
|
||||
}
|
||||
}
|
||||
|
||||
const numericIpv4 = /^[0-9.]+$/.test(hostname);
|
||||
const parsedIpv4 = numericIpv4 ? ipv4Parts(hostname) : null;
|
||||
if (
|
||||
!hostname
|
||||
|| reservedHostnamePattern.test(hostname)
|
||||
|| hostname === 'localhost'
|
||||
|| hostname.endsWith('.localhost')
|
||||
|| hostname === '::1'
|
||||
|| (numericIpv4 && (!parsedIpv4 || isReservedIpv4(parsedIpv4)))
|
||||
|| (hostname.includes(':') && (!/^[0-9a-f:]+$/i.test(hostname) || /^f[cd]/.test(hostname) || hostname.startsWith('fe80:')))
|
||||
|| (!numericIpv4 && !hostname.includes(':') && (
|
||||
!hostname.includes('.')
|
||||
|| !/^[a-z0-9.-]+$/i.test(hostname)
|
||||
|| hostname.startsWith('.')
|
||||
|| hostname.endsWith('.')
|
||||
))
|
||||
) return '';
|
||||
return value;
|
||||
};
|
||||
|
||||
const termsUrl = normalizePublicHttpsUrl(import.meta.env.VITE_APP_TERMS_URL);
|
||||
const privacyUrl = normalizePublicHttpsUrl(import.meta.env.VITE_APP_PRIVACY_URL);
|
||||
|
||||
export const legalLinksReady = Boolean(termsUrl && privacyUrl && termsUrl !== privacyUrl);
|
||||
|
||||
export const legalDocumentUrl = (kind: LegalDocumentKind) => {
|
||||
if (!legalLinksReady) return '';
|
||||
return kind === 'terms' ? termsUrl : privacyUrl;
|
||||
};
|
||||
Reference in New Issue
Block a user