feat: 登录页密码+验证码双模式 / 首页岗位优化 / 法律页面 / 后端接口完善
- 前端:登录页重构,支持密码登录、验证码登录、注册三种模式 - 前端:首页热门岗位添加「参考示例」标签,去虚构数据 - 前端:面试页顶部优化,岗位名+状态标签展示 - 前端:新增用户协议、隐私政策页面及免责声明 - 后端:新增 POST /api/user/register 注册接口 - 后端:新增 POST /api/user/set-password 设置密码接口 - 后端:修复 user.schema.ts unique 索引导致 null 冲突问题 - 后端:新增 payment-order.schema、positions.schema、site-config.schema - 后端:package.json 新增 postbuild 脚本自动复制证书 - 管理后台:新增订单管理 Tab
This commit is contained in:
@@ -8,105 +8,285 @@
|
||||
</view>
|
||||
|
||||
<view class="form-section">
|
||||
<!-- 登录方式切换 -->
|
||||
<!-- 主 Tab:登录 / 注册 / 微信 -->
|
||||
<view class="tab-bar">
|
||||
<text class="tab" :class="{ active: mode === 'email' }" @click="mode='email'">邮箱登录</text>
|
||||
<text class="tab" :class="{ active: mode === 'wechat' }" @click="mode='wechat'" v-if="isMp">微信登录</text>
|
||||
<text class="tab" :class="{ active: mainTab === 'login' }" @click="mainTab='login'">登录</text>
|
||||
<text class="tab" :class="{ active: mainTab === 'register' }" @click="mainTab='register'">注册</text>
|
||||
<text class="tab" :class="{ active: mainTab === 'wechat' }" @click="mainTab='wechat'" v-if="isMp">微信登录</text>
|
||||
</view>
|
||||
|
||||
<!-- 邮箱登录 -->
|
||||
<view class="card" v-if="mode === 'email'">
|
||||
<text class="card-title">邮箱登录</text>
|
||||
<!-- ========== 登录 ========== -->
|
||||
<view class="card" v-if="mainTab === 'login'">
|
||||
<!-- 子 Tab:密码 / 验证码 -->
|
||||
<view class="sub-tab-bar">
|
||||
<text class="sub-tab" :class="{ active: loginMode === 'password' }" @click="loginMode='password'">密码登录</text>
|
||||
<text class="sub-tab" :class="{ active: loginMode === 'code' }" @click="loginMode='code'">验证码登录</text>
|
||||
</view>
|
||||
|
||||
<!-- 密码登录 -->
|
||||
<view v-if="loginMode === 'password'">
|
||||
<view class="field">
|
||||
<text class="field-label">邮箱</text>
|
||||
<input class="input" type="text" v-model="email" placeholder="请输入邮箱" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="field-label">密码</text>
|
||||
<input class="input" type="password" v-model="password" placeholder="请输入密码" @confirm="doPasswordLogin" />
|
||||
</view>
|
||||
<button class="login-btn" :disabled="!canPasswordLogin || pwdLoading" @click="doPasswordLogin">
|
||||
{{ pwdLoading ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
<view class="switch-hint" @click="loginMode='code'">忘记密码?使用验证码登录</view>
|
||||
</view>
|
||||
|
||||
<!-- 验证码登录 -->
|
||||
<view v-else>
|
||||
<!-- 调试信息(发布前删掉) -->
|
||||
<view class="debug-info" v-if="true">debug: emailSent={{emailSent}} cooldown={{cooldown}}</view>
|
||||
<view class="field">
|
||||
<text class="field-label">邮箱</text>
|
||||
<view class="inline-row">
|
||||
<input class="input inline-input" type="text" v-model="email" placeholder="请输入邮箱" />
|
||||
<button class="code-btn" :disabled="cooldown > 0 || !email" @click="sendEmailCode">
|
||||
{{ cooldown > 0 ? cooldown + 's' : (emailSent ? '重新获取' : '获取验证码') }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="field" v-if="emailSent">
|
||||
<text class="field-label">验证码</text>
|
||||
<input class="input" type="number" maxlength="6" v-model="emailCode" placeholder="请输入6位验证码" />
|
||||
</view>
|
||||
<button class="login-btn" :disabled="!emailSent || !emailCode || emailLoading" @click="doEmailLogin">
|
||||
{{ emailLoading ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
<view class="switch-hint" @click="loginMode='password'">已有密码?使用密码登录</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ========== 注册 ========== -->
|
||||
<view class="card" v-if="mainTab === 'register'">
|
||||
<text class="card-title">创建账号</text>
|
||||
<text class="card-sub">注册后享受 AI 面试模拟服务</text>
|
||||
<view class="field">
|
||||
<text class="field-label">邮箱</text>
|
||||
<input class="input" type="text" v-model="email" placeholder="请输入邮箱" @confirm="sendEmailCode" />
|
||||
<input class="input" type="text" v-model="email" placeholder="请输入邮箱" />
|
||||
</view>
|
||||
|
||||
<view class="field" v-if="emailSent">
|
||||
<text class="field-label">验证码</text>
|
||||
<view class="code-row">
|
||||
<input class="input code-input" type="number" maxlength="6" v-model="emailCode" placeholder="6位验证码" @confirm="doEmailLogin" />
|
||||
<button class="code-btn" :disabled="cooldown > 0" @click="sendEmailCode">
|
||||
{{ cooldown > 0 ? cooldown + 's' : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="field-label">密码</text>
|
||||
<input class="input" type="password" v-model="password" placeholder="至少6位密码" />
|
||||
</view>
|
||||
|
||||
<button class="login-btn" v-if="!emailSent" @click="sendEmailCode">{{ emailSending ? '发送中...' : '获取验证码' }}</button>
|
||||
<button class="login-btn" v-else :disabled="!emailCode" @click="doEmailLogin">{{ emailLoading ? '登录中...' : '登录' }}</button>
|
||||
<view class="field">
|
||||
<text class="field-label">确认密码</text>
|
||||
<input class="input" type="password" v-model="confirmPassword" placeholder="再次输入密码" @confirm="doRegister" />
|
||||
</view>
|
||||
<button class="login-btn" :disabled="!canRegister || regLoading" @click="doRegister">
|
||||
{{ regLoading ? '注册中...' : '注册' }}
|
||||
</button>
|
||||
<view class="switch-hint" @click="mainTab='login'">已有账号?去登录</view>
|
||||
</view>
|
||||
|
||||
<!-- 微信登录(仅小程序) -->
|
||||
<view class="card" v-if="mode === 'wechat' && isMp">
|
||||
<!-- ========== 微信一键登录 ========== -->
|
||||
<view class="card" v-if="mainTab === 'wechat' && isMp">
|
||||
<text class="card-title">微信一键登录</text>
|
||||
<text class="card-sub">授权后自动创建账号</text>
|
||||
<button class="login-btn wx-btn" @click="doWxLogin">{{ wxLoading ? '登录中...' : '微信一键登录' }}</button>
|
||||
</view>
|
||||
|
||||
<!-- 法律声明 -->
|
||||
<view class="legal">
|
||||
<text class="legal-text">登录即表示同意</text>
|
||||
<text class="legal-link" @click="goAgreement">《用户协议》</text>
|
||||
<text class="legal-text">和</text>
|
||||
<text class="legal-link" @click="goPrivacy">《隐私政策》</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设置密码弹窗(验证码登录后引导) -->
|
||||
<view class="overlay" v-if="showSetPwd" @click="showSetPwd=false"></view>
|
||||
<view class="pwd-modal" v-if="showSetPwd">
|
||||
<text class="modal-title">设置登录密码</text>
|
||||
<text class="modal-desc">设置密码后,下次可直接用密码登录,无需等待验证码</text>
|
||||
<input class="input" type="password" v-model="newPassword" placeholder="至少6位密码" />
|
||||
<view class="modal-btns">
|
||||
<text class="modal-btn skip" @click="skipSetPwd">暂不设置</text>
|
||||
<text class="modal-btn confirm" @click="doSetPassword">确认设置</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { api } from '../../config'
|
||||
|
||||
const mode = ref('email')
|
||||
const mainTab = ref('login')
|
||||
const loginMode = ref('password') // 'password' | 'code'
|
||||
const isMp = ref(false)
|
||||
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const emailCode = ref('')
|
||||
const emailSent = ref(false)
|
||||
const emailSending = ref(false)
|
||||
const emailLoading = ref(false)
|
||||
const pwdLoading = ref(false)
|
||||
const regLoading = ref(false)
|
||||
const wxLoading = ref(false)
|
||||
const cooldown = ref(0)
|
||||
let timer = null
|
||||
|
||||
// 设置密码弹窗
|
||||
const showSetPwd = ref(false)
|
||||
const newPassword = ref('')
|
||||
|
||||
const canPasswordLogin = computed(() => email.value.trim() && password.value.length >= 6 && !pwdLoading.value)
|
||||
const canRegister = computed(() => email.value.trim() && password.value.length >= 6 && password.value === confirmPassword.value && !regLoading.value)
|
||||
|
||||
onMounted(() => {
|
||||
// #ifdef MP-WEIXIN
|
||||
isMp.value = true
|
||||
mode.value = 'wechat'
|
||||
mainTab.value = 'wechat'
|
||||
// #endif
|
||||
})
|
||||
|
||||
// 邮箱验证码
|
||||
const sendEmailCode = async () => {
|
||||
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!re.test(email.value)) { uni.showToast({ title: '请输入正确的邮箱', icon: 'none' }); return }
|
||||
emailSending.value = true
|
||||
onBeforeUnmount(() => { if (timer) clearInterval(timer) })
|
||||
|
||||
// 辅助
|
||||
const showToast = (title, icon = 'none') => uni.showToast({ title, icon })
|
||||
const loginSuccess = (data) => {
|
||||
uni.setStorageSync('token', data.token)
|
||||
if (data.user) uni.setStorageSync('userInfo', JSON.stringify(data.user))
|
||||
showToast('登录成功', 'success')
|
||||
setTimeout(() => uni.navigateBack(), 500)
|
||||
}
|
||||
|
||||
// ====== 密码登录 ======
|
||||
const doPasswordLogin = async () => {
|
||||
if (!canPasswordLogin.value) return
|
||||
pwdLoading.value = true
|
||||
try {
|
||||
const res = await uni.request({ url: api('/user/send-email-code'), method: 'POST', data: { email: email.value } })
|
||||
if (res.statusCode === 200) {
|
||||
emailSent.value = true
|
||||
uni.showToast({ title: '验证码已发送', icon: 'success' })
|
||||
startCooldown()
|
||||
} else { uni.showToast({ title: res.data?.message || '发送失败', icon: 'none' }) }
|
||||
} catch { uni.showToast({ title: '网络错误', icon: 'none' }) }
|
||||
finally { emailSending.value = false }
|
||||
const res = await uni.request({
|
||||
url: api('/user/password-login'), method: 'POST',
|
||||
header: { 'Content-Type': 'application/json' },
|
||||
data: { email: email.value.trim(), password: password.value },
|
||||
})
|
||||
if (res.statusCode === 200 && res.data?.token) {
|
||||
loginSuccess(res.data)
|
||||
} else {
|
||||
showToast(res.data?.message || '登录失败')
|
||||
}
|
||||
} catch { showToast('网络错误') }
|
||||
finally { pwdLoading.value = false }
|
||||
}
|
||||
|
||||
// ====== 验证码 ======
|
||||
const sendEmailCode = () => {
|
||||
if (cooldown.value > 0) { showToast('请稍后再试'); return }
|
||||
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!re.test(email.value)) { showToast('请输入正确的邮箱'); return }
|
||||
console.log('[sendEmailCode] 发送中,email:', email.value)
|
||||
uni.request({
|
||||
url: api('/user/send-email-code'),
|
||||
method: 'POST',
|
||||
header: { 'Content-Type': 'application/json' },
|
||||
data: { email: email.value },
|
||||
success: (res) => {
|
||||
console.log('[sendEmailCode] success res:', JSON.stringify(res))
|
||||
if (res.statusCode === 200) {
|
||||
emailSent.value = true
|
||||
console.log('[sendEmailCode] emailSent 设为 true')
|
||||
showToast('验证码已发送', 'success')
|
||||
startCooldown()
|
||||
} else {
|
||||
const msg = (res.data && res.data.message) || '发送失败'
|
||||
showToast(msg)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('[sendEmailCode] fail:', err)
|
||||
showToast('网络错误')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const startCooldown = () => {
|
||||
cooldown.value = 60
|
||||
if (timer) clearInterval(timer)
|
||||
timer = setInterval(() => { if (--cooldown.value <= 0) { clearInterval(timer); timer = null } }, 1000)
|
||||
if (timer) clearTimeout(timer)
|
||||
const tick = () => {
|
||||
cooldown.value--
|
||||
if (cooldown.value <= 0) {
|
||||
timer = null
|
||||
return
|
||||
}
|
||||
timer = setTimeout(tick, 1000)
|
||||
}
|
||||
timer = setTimeout(tick, 1000)
|
||||
}
|
||||
|
||||
// 邮箱登录
|
||||
const doEmailLogin = async () => {
|
||||
if (!emailCode.value) return
|
||||
emailLoading.value = true
|
||||
try {
|
||||
const res = await uni.request({ url: api('/user/email-login'), method: 'POST', data: { email: email.value, code: emailCode.value } })
|
||||
const res = await uni.request({
|
||||
url: api('/user/email-login'), method: 'POST',
|
||||
header: { 'Content-Type': 'application/json' },
|
||||
data: { email: email.value.trim(), code: emailCode.value },
|
||||
})
|
||||
if (res.statusCode === 200 && res.data?.token) {
|
||||
uni.setStorageSync('token', res.data.token)
|
||||
if (res.data.user) uni.setStorageSync('userInfo', JSON.stringify(res.data.user))
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 500)
|
||||
} else { uni.showToast({ title: res.data?.message || '登录失败', icon: 'none' }) }
|
||||
} catch { uni.showToast({ title: '登录失败', icon: 'none' }) }
|
||||
loginSuccess(res.data)
|
||||
// 新用户(isNew)且没有密码 → 引导设置密码
|
||||
if (res.data.isNew || !res.data.hasPassword) {
|
||||
setTimeout(() => { showSetPwd.value = true; newPassword.value = '' }, 800)
|
||||
}
|
||||
} else {
|
||||
showToast(res.data?.message || '登录失败')
|
||||
}
|
||||
} catch { showToast('网络错误') }
|
||||
finally { emailLoading.value = false }
|
||||
}
|
||||
|
||||
// 微信静默登录
|
||||
// ====== 注册 ======
|
||||
const doRegister = async () => {
|
||||
if (!canRegister.value) return
|
||||
regLoading.value = true
|
||||
try {
|
||||
const res = await uni.request({
|
||||
url: api('/user/register'), method: 'POST',
|
||||
header: { 'Content-Type': 'application/json' },
|
||||
data: { email: email.value.trim(), password: password.value },
|
||||
})
|
||||
if (res.statusCode === 200 && res.data?.token) {
|
||||
loginSuccess(res.data)
|
||||
} else if (res.statusCode === 409) {
|
||||
showToast('该邮箱已注册,请直接登录')
|
||||
mainTab.value = 'login'
|
||||
} else {
|
||||
showToast(res.data?.message || '注册失败')
|
||||
}
|
||||
} catch { showToast('网络错误') }
|
||||
finally { regLoading.value = false }
|
||||
}
|
||||
|
||||
// ====== 设置密码 ======
|
||||
const doSetPassword = async () => {
|
||||
if (newPassword.value.length < 6) { showToast('密码至少6位'); return }
|
||||
const token = uni.getStorageSync('token')
|
||||
try {
|
||||
const res = await uni.request({
|
||||
url: api('/user/set-password'), method: 'POST',
|
||||
header: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
||||
data: { password: newPassword.value },
|
||||
})
|
||||
if (res.statusCode === 200 || res.statusCode === 201) {
|
||||
showToast('密码设置成功', 'success')
|
||||
showSetPwd.value = false
|
||||
}
|
||||
} catch { showToast('设置失败') }
|
||||
}
|
||||
|
||||
const skipSetPwd = () => { showSetPwd.value = false }
|
||||
|
||||
// ====== 微信登录 ======
|
||||
const doWxLogin = async () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
wxLoading.value = true
|
||||
@@ -114,17 +294,16 @@ const doWxLogin = async () => {
|
||||
const { code } = await uni.login()
|
||||
const res = await uni.request({ url: api('/user/wx-login'), method: 'POST', data: { code } })
|
||||
if (res.statusCode === 200 && res.data?.token) {
|
||||
uni.setStorageSync('token', res.data.token)
|
||||
if (res.data.user) uni.setStorageSync('userInfo', JSON.stringify(res.data.user))
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 500)
|
||||
} else { uni.showToast({ title: '微信登录失败', icon: 'none' }) }
|
||||
} catch { uni.showToast({ title: '微信登录失败', icon: 'none' }) }
|
||||
loginSuccess(res.data)
|
||||
} else { showToast('微信登录失败') }
|
||||
} catch { showToast('微信登录失败') }
|
||||
finally { wxLoading.value = false }
|
||||
// #endif
|
||||
}
|
||||
|
||||
const wxLoading = ref(false)
|
||||
// ====== 法律页面 ======
|
||||
const goAgreement = () => uni.navigateTo({ url: '/pages/agreement/agreement' })
|
||||
const goPrivacy = () => uni.navigateTo({ url: '/pages/privacy/privacy' })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -134,23 +313,50 @@ const wxLoading = ref(false)
|
||||
.brand-tagline { font-size: 24rpx; color: var(--color-text-tertiary); margin-top: 8rpx; display: block; }
|
||||
.form-section { padding: 0 32rpx; flex: 1; }
|
||||
|
||||
/* Tab */
|
||||
/* ===== Main Tab ===== */
|
||||
.tab-bar { display: flex; gap: 0; margin-bottom: 24rpx; background: #FFFFFF; border-radius: var(--radius-md); padding: 4rpx; }
|
||||
.tab { flex: 1; text-align: center; padding: 16rpx; font-size: 26rpx; color: var(--color-text-secondary); border-radius: var(--radius-sm); }
|
||||
.tab { flex: 1; text-align: center; padding: 16rpx; font-size: 26rpx; color: var(--color-text-secondary); border-radius: var(--radius-sm); transition: all 0.2s;}
|
||||
.tab.active { background: var(--color-primary); color: #FFFFFF; font-weight: 600; }
|
||||
|
||||
/* Form */
|
||||
/* ===== Sub Tab ===== */
|
||||
.sub-tab-bar { display: flex; gap: 0; margin-bottom: 24rpx; background: #F9FAFB; border-radius: var(--radius-sm); padding: 4rpx; }
|
||||
.sub-tab { flex: 1; text-align: center; padding: 12rpx; font-size: 24rpx; color: var(--color-text-tertiary); border-radius: var(--radius-sm); transition: all 0.2s;}
|
||||
.sub-tab.active { background: #FFFFFF; color: var(--color-text); font-weight: 600; box-shadow: 0 1rpx 4rpx rgba(0,0,0,0.06); }
|
||||
|
||||
/* ===== Card ===== */
|
||||
.card { background: #FFFFFF; border-radius: var(--radius-lg); padding: 32rpx; box-shadow: var(--shadow-sm); }
|
||||
.card-title { font-size: 30rpx; font-weight: 700; color: var(--color-text); display: block; }
|
||||
.card-sub { font-size: 22rpx; color: var(--color-text-tertiary); margin-top: 6rpx; margin-bottom: 24rpx; display: block; }
|
||||
|
||||
/* ===== Fields ===== */
|
||||
.field { margin-bottom: 20rpx; }
|
||||
.field-label { font-size: 22rpx; color: var(--color-text-secondary); margin-bottom: 8rpx; display: block; }
|
||||
.input { width: 100%; height: 72rpx; background: #F9FAFB; border: 2rpx solid var(--color-border); border-radius: var(--radius-sm); padding: 0 20rpx; font-size: 26rpx; box-sizing: border-box; }
|
||||
.code-row { display: flex; gap: 12rpx; align-items: center; }
|
||||
.code-input { flex: 1; }
|
||||
.code-btn { height: 72rpx; padding: 0 24rpx; background: #F3F4F6; border: 2rpx solid var(--color-border); border-radius: var(--radius-sm); font-size: 24rpx; color: var(--color-primary); white-space: nowrap; flex-shrink: 0; }
|
||||
.code-btn:disabled { color: var(--color-text-tertiary); }
|
||||
.inline-row { display: flex; gap: 12rpx; align-items: center; }
|
||||
.inline-input { flex: 1; }
|
||||
.code-btn { height: 72rpx; padding: 0 20rpx; background: var(--color-primary); color: #FFFFFF; border: none; border-radius: var(--radius-sm); font-size: 24rpx; white-space: nowrap; flex-shrink: 0; line-height: 72rpx; }
|
||||
.code-btn:disabled { background: #D1D5DB; color: #9CA3AF; }
|
||||
|
||||
/* ===== Buttons ===== */
|
||||
.login-btn { width: 100%; height: 80rpx; background: linear-gradient(135deg, var(--color-gradient-start), var(--color-gradient-mid)); color: #FFFFFF; border-radius: var(--radius-md); font-size: 28rpx; font-weight: 600; border: none; margin-top: 16rpx; display: flex; align-items: center; justify-content: center; }
|
||||
.login-btn:disabled { opacity: 0.5; }
|
||||
.wx-btn { background: linear-gradient(135deg, #07C160, #06AD56); }
|
||||
|
||||
/* ===== Switch Hint ===== */
|
||||
.switch-hint { text-align: center; font-size: 22rpx; color: var(--color-primary); padding: 20rpx 0 4rpx; }
|
||||
|
||||
/* ===== Legal ===== */
|
||||
.legal { display: flex; justify-content: center; align-items: center; gap: 4rpx; margin-top: 24rpx; flex-wrap: wrap; }
|
||||
.legal-text { font-size: 22rpx; color: var(--color-text-tertiary); }
|
||||
.legal-link { font-size: 22rpx; color: var(--color-primary); }
|
||||
|
||||
/* ===== Password Modal ===== */
|
||||
.overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 100; }
|
||||
.pwd-modal { position: fixed; left: 32rpx; right: 32rpx; top: 50%; transform: translateY(-50%); background: #FFFFFF; border-radius: var(--radius-lg); padding: 40rpx 32rpx; z-index: 101; }
|
||||
.modal-title { font-size: 30rpx; font-weight: 700; color: var(--color-text); display: block; text-align: center; }
|
||||
.modal-desc { font-size: 22rpx; color: var(--color-text-tertiary); text-align: center; display: block; margin: 12rpx 0 24rpx; line-height: 1.5; }
|
||||
.modal-btns { display: flex; gap: 16rpx; margin-top: 24rpx; }
|
||||
.modal-btn { flex: 1; text-align: center; padding: 20rpx; font-size: 26rpx; border-radius: var(--radius-sm); }
|
||||
.modal-btn.skip { background: #F3F4F6; color: var(--color-text-secondary); }
|
||||
.modal-btn.confirm { background: linear-gradient(135deg, var(--color-gradient-start), var(--color-gradient-mid)); color: #FFFFFF; font-weight: 600; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user