yeg
2026-09-28 85bfb9351803d97a33329d507f30a2f78bf1fc75
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
<template>
    <view class="page">
        <view class="bg-gradient"></view>
 
        <view class="hero">
            <view class="hero-text">
                <text class="welcome-text">欢迎使用</text>
                <text class="platform-title">攀枝花市</text>
                <text class="platform-title">东区人才服务平台</text>
            </view>
            <image class="mascot" src="/static/login-mascot.png" mode="aspectFit"></image>
        </view>
 
        <view class="card">
            <view class="field-heading">
                <text class="field-label">账号</text>
                <text v-if="accountError.length > 0" class="field-error">{{accountError}}</text>
            </view>
            <view class="input-box">
                <image class="input-icon" src="/static/icons/user.svg" mode="aspectFit"></image>
                <input class="input-field" type="text" maxlength="32" v-model="account" placeholder="请输入账号" :placeholder-style="placeholderStyle" @input="clearAccountError" />
            </view>
 
            <view class="field-heading">
                <text class="field-label">密码</text>
                <text v-if="passwordError.length > 0" class="field-error">{{passwordError}}</text>
            </view>
            <view class="input-box">
                <image class="input-icon" src="/static/icons/lock.svg" mode="aspectFit"></image>
                <input class="input-field" type="text" :password="true" maxlength="64" v-model="password" placeholder="请输入登录密码" :placeholder-style="placeholderStyle" @input="clearPasswordError" />
            </view>
 
            <text v-if="loginError.length > 0" class="login-error">{{loginError}}</text>
 
            <text v-if="agreementError.length > 0" class="agreement-error">{{agreementError}}</text>
 
            <view class="agreement" @tap="toggleAgree">
                <view class="checkbox" :class="agreed ? 'checked' : (agreementError.length > 0 ? 'input-invalid' : '')">
                    <text v-if="agreed" class="check-mark">✓</text>
                </view>
                <text class="agree-text">我已阅读并同意</text>
                <text class="agree-link" @tap.stop="showAgreement">《商户服务协议》</text>
                <text class="agree-link" @tap.stop="showPrivacy">《隐私政策》</text>
            </view>
 
            <button class="login-btn" :loading="submitting" :disabled="submitting" @tap="handleLogin"><text class="login-btn-text">登录</text></button>
        </view>
    </view>
</template>
 
<script setup lang="uts">
    import { loginRemote } from '../../services/auth.uts'
    import { hasActiveSession } from '../../services/session.uts'
    import { apiEnvironment, skipMerchantLoginInDevelopment } from '../../config/api.uts'
    const account = ref('')
    const password = ref('')
    const agreed = ref(false)
    const accountError = ref('')
    const passwordError = ref('')
    const loginError = ref('')
    const agreementError = ref('')
    const submitting = ref(false)
    const placeholderStyle = 'color:#C0C0C0;font-size:28rpx;'
 
    onLoad(() => {
        if (apiEnvironment == 'development' && skipMerchantLoginInDevelopment) {
            uni.reLaunch({ url: '/pages/index/index' })
        }
    })
 
    const toggleAgree = () => {
        agreed.value = !agreed.value
        if (agreed.value) {
            agreementError.value = ''
        }
    }
 
    const clearAccountError = () => {
        accountError.value = ''
        loginError.value = ''
    }
 
    const clearPasswordError = () => {
        passwordError.value = ''
        loginError.value = ''
    }
 
    const handleLogin = () => {
        if (submitting.value) return
        accountError.value = ''
        passwordError.value = ''
        loginError.value = ''
        agreementError.value = ''
        account.value = account.value.trim()
        let hasError = false
        if (account.value.length == 0) {
            accountError.value = '账号未录入,请联系管理员'
            hasError = true
        }
        if (password.value.length == 0) {
            passwordError.value = '请输入登录密码'
            hasError = true
        }
        if (!agreed.value) {
            agreementError.value = '请先阅读并同意相关协议'
            hasError = true
        }
        if (hasError) {
            return
        }
        submitting.value = true
        uni.showLoading({ title: '正在登录', mask: true })
        loginRemote(account.value, password.value, () => {
            uni.hideLoading()
            submitting.value = false
            password.value = ''
            uni.reLaunch({ url: '/pages/index/index' })
        }, (error: any) => {
            uni.hideLoading()
            submitting.value = false
            if (error.code == 'UNAUTHORIZED') {
                loginError.value = error.message != null && error.message.length > 0 ? error.message : '账号或密码错误'
                return
            }
            uni.showToast({ title: error.message != null ? error.message : '登录失败,请稍后重试', icon: 'none' })
        })
    }
 
    onShow(() => {
        if (hasActiveSession()) uni.reLaunch({ url: '/pages/index/index' })
    })
 
    const showAgreement = () => {
        uni.navigateTo({ url: '/pages/agreement-detail/agreement-detail?type=user&source=login' })
    }
 
    const showPrivacy = () => {
        uni.navigateTo({ url: '/pages/agreement-detail/agreement-detail?type=privacy&source=login' })
    }
</script>
 
<style>
    .page { display: flex; flex: 1; flex-direction: column; width: 100%; min-height: 100vh; position: relative; overflow: hidden; background-color: #FFFFFF; }
    .bg-gradient { position: absolute; top: 0; left: 0; right: 0; height: 660rpx; background-image: linear-gradient(112deg, #FF4B36 0%, #FF8B25 100%); }
 
    .hero { display: flex; flex-direction: row; align-items: flex-end; height: 576rpx; padding: 0 50rpx 72rpx; position: relative; z-index: 1; box-sizing: border-box; }
    .hero-text { display: flex; flex: 1; flex-direction: column; padding-bottom: 6rpx; position: relative; z-index: 1; }
    .welcome-text { color: #FFFFFF; font-size: 30rpx; line-height: 42rpx; font-weight: 600; margin-bottom: 18rpx; }
    .platform-title { color: #FFFFFF; font-size: 50rpx; line-height: 62rpx; font-weight: 700; letter-spacing: 1rpx; }
    .mascot { position: absolute; right: 28rpx; bottom: 58rpx; width: 322rpx; height: 266rpx; }
 
    .card { display: flex; flex: 1; flex-direction: column; margin-top: -8rpx; padding: 62rpx 50rpx 56rpx; background-color: #FFFFFF; border-radius: 48rpx 48rpx 0 0; position: relative; z-index: 2; box-sizing: border-box; }
    .field-heading { display: flex; flex-direction: row; align-items: center; min-height: 40rpx; margin-bottom: 16rpx; }
    .field-label { color: #313744; font-size: 28rpx; line-height: 40rpx; font-weight: 700; }
    .field-error { color: #F06A66; font-size: 24rpx; line-height: 34rpx; font-weight: 500; margin-left: 20rpx; }
 
    .input-box { display: flex; flex-direction: row; align-items: center; height: 104rpx; background-color: #FFFFFF; border-radius: 30rpx; padding: 0 30rpx; margin-bottom: 34rpx; box-shadow: 0 14rpx 28rpx rgba(43, 44, 52, 0.10); box-sizing: border-box; }
    .input-icon { width: 34rpx; height: 34rpx; margin-right: 20rpx; flex-shrink: 0; }
    .input-field { flex: 1; height: 104rpx; font-size: 28rpx; color: #313744; }
 
    .login-error { color: #F06A66; font-size: 24rpx; line-height: 34rpx; margin-top: -18rpx; margin-bottom: 20rpx; }
 
    .agreement-error { color: #F06A66; font-size: 24rpx; line-height: 34rpx; margin-top: -18rpx; margin-bottom: 12rpx; }
 
    .agreement { display: flex; flex-direction: row; align-items: center; margin-top: 2rpx; margin-bottom: 32rpx; min-height: 32rpx; }
    .checkbox { display: flex; width: 28rpx; height: 28rpx; background-color: #FFFFFF; border-width: 2rpx; border-style: solid; border-color: #B8C0CC; border-radius: 6rpx; justify-content: center; align-items: center; margin-right: 14rpx; flex-shrink: 0; }
    .checkbox.checked { background-color: #FF7028; border-color: #FF7028; }
    .checkbox.input-invalid { border-color: #F05243; }
    .check-mark { color: #FFFFFF; font-size: 20rpx; line-height: 24rpx; font-weight: 700; }
    .agree-text { color: #596170; font-size: 24rpx; line-height: 32rpx; }
    .agree-link { color: #F05243; font-size: 24rpx; line-height: 32rpx; }
 
    .login-btn { display: flex; margin: 0; height: 100rpx; border-radius: 50rpx; background-image: linear-gradient(90deg, #FF8A24 0%, #FF4B36 100%); justify-content: center; align-items: center; line-height: 100rpx; box-shadow: 0 14rpx 26rpx rgba(255, 101, 48, 0.22); }
    .login-btn::after { border-width: 0; }
    .login-btn-text { color: #FFFFFF; font-size: 30rpx; line-height: 42rpx; font-weight: 700; }
</style>