Commit 719b00ea by chunhong.mu

feat(qqqf-mp): 实现微信小程序登录

parent 20c75a3a
<script lang="ts" setup>
import { ClientId, PassportTokenController, UICore, uniAdapter } from "@common/sdk"
import { onLaunch } from "@dcloudio/uni-app"
import { sdk } from "@/api/http"
onLaunch(() => {
console.log("App Launch")
sdkInit()
})
function sdkInit() {
UICore.setupSdk(sdk.core, {
client: ClientId.Hrs100WechatMiniApp,
config: uniAdapter,
})
const token = PassportTokenController.hasToken()
if (token) {
sdk.core.loginByToken({
token: token as string,
})
}
}
</script>
<style lang="scss">
......
/**
* 登录服务
* 提供微信一键登录、短信验证码登录、密码登录等接口调用
*/
import { sdk } from "@/api/http"
import { config } from "@/config"
export interface LoginResult {
jwt: string
user_id?: string
mobile?: string
}
export interface RegisterChannel {
app?: string
scene?: string
channel?: string
app_store?: string
[key: string]: any
}
/**
* 获取微信登录 code
*/
export function getWxLoginCode(): Promise<string> {
return new Promise((resolve, reject) => {
uni.login({
provider: "weixin",
success: res => resolve(res.code),
fail: err => reject(err),
})
})
}
/**
* 微信小程序绑定手机号登录
*/
export function miniProgramBindPhone(
appid: string,
code: string,
clientId: string,
encryptedData: string,
iv: string,
registerChannel: RegisterChannel = {},
): Promise<LoginResult> {
return sdk.domainServicePost<
any,
any,
LoginResult
>(
"bind_phone",
{
data: {
appid,
code,
client_id: clientId,
encryptedData,
iv,
...registerChannel,
},
},
true, // 匿名接口
"uniplat_base",
"system.wechat",
)
}
/**
* 发送短信验证码
*/
export function sendSmsCode(
mobile: string,
imgCode: string,
): Promise<void> {
return sdk.domainServicePost<
any,
any,
void
>(
"send_verifycode",
{
data: {
mobile,
img_code: imgCode,
client_id: config.clientId,
},
},
true,
"uniplat_base",
"anonymous/system.user",
)
}
/**
* 短信验证码登录
*/
export function smsLogin(
mobile: string,
verifycode: string,
registerChannel: RegisterChannel = {},
): Promise<LoginResult> {
return sdk.domainServicePost<
any,
any,
LoginResult
>(
"login_with_mobile",
{
data: {
mobile,
verifycode,
client_id: config.clientId,
...registerChannel,
},
},
true,
"uniplat_base",
"anonymous/system.user",
)
}
/**
* 密码登录
*/
export function passwordLogin(
username: string,
password: string,
registerChannel: RegisterChannel = {},
): Promise<LoginResult> {
return sdk.domainServicePost<
any,
any,
LoginResult
>(
"login_with_password",
{
data: {
username,
password,
client_id: config.clientId,
...registerChannel,
},
},
true,
"uniplat_base",
"anonymous/system.user",
)
}
/**
* 获取图形验证码
*/
export function getImageCode(): Promise<string> {
return sdk.domainServiceGet<
any,
{ img_code: string }
>(
"get_image_code",
{},
true,
"uniplat_base",
"system.user",
).then(res => res.img_code)
}
......@@ -13,10 +13,18 @@ export interface MpConfig extends SdkConfig {
appName?: string
appSourceId?: string
officialAccountName?: string
payUrl?: string
passportUrl?: string
passportApi?: string
passportOrg?: string
userCenter?: string
uniplatApi: string
qqxbH5Url?: string
xbUrl?: string
h5Url?: string
collectionUrl?: string
selfH5?: string
selfJobH5?: string
imgBaseUrl: string
clientId: ClientId
uniplatSocketUrl: string
......@@ -48,6 +56,8 @@ export interface MpConfig extends SdkConfig {
get_lbs?: string
}
cityCodeLength: number | 6
reviewVersion?: number
registerConfig?: any
fastCity: any
testAccount?: string
testPassport?: string
......@@ -55,33 +65,69 @@ export interface MpConfig extends SdkConfig {
path: string
id: string
}[]
registerConfig?: any
hideshareBy?: boolean
qqxbH5Url?: string
applicationKey?: any
[key: string]: any
}
export const config: MpConfig = {
appName: "亲亲企服",
uniplatApi: import.meta.env.VITE_API_BASE_URL || "https://api.example.com",
rootEntrance: "qqqf-mp",
imgBaseUrl: `${import.meta.env.VITE_APP_LAND_PAGE || ""}/img/`,
clientId: import.meta.env.VITE_APP_CLIENT_ID || "qqqf-mp-client",
uniplatSocketUrl: import.meta.env.VITE_APP_UNIPLAT_WEBSOCKET_URI || "",
clientSecret: import.meta.env.VITE_APP_CLIENT_SECRET || "",
logEnv: import.meta.env.VITE_ENV || "development",
version: packageConfig.version,
payUrl:
import.meta.env.VITE_APP_PAY_URL || "https://payment-api.qqqf.com",
passportUrl:
import.meta.env.VITE_APP_WWW_WORK_APP_URL ||
"https://passport.qqqf.com",
passportApi:
import.meta.env.VITE_APP_API_WORK_APP_URL ||
"https://userapi.qqqf.com",
passportOrg:
import.meta.env.VITE_APP_API_WORK_ORG_URL ||
"https://userapi.qqqf.com",
userCenter:
import.meta.env.VITE_APP_USER_CENTER || "http://tmxlogin.qqqf.com",
uniplatApi:
import.meta.env.VITE_APP_UNIPLAT || "https://api-hro.qqqf.com",
qqxbH5Url:
import.meta.env.VITE_APP_QQXB || "http://test-qqxb-h5.qqqf.com",
rootEntrance: "统一业务平台",
domainService: {
subProjectName: "qqqf",
serviceName: "api",
subProjectName: "welfare_v2",
serviceName: "smart_app_api",
},
xbUrl:
import.meta.env.VITE_APP_XB_URL || "https://qqxb-h5.qqqf.com",
h5Url:
import.meta.env.VITE_APP_H5_URL ||
"https://static.qqqf.com/flb-mp",
imgBaseUrl: `${import.meta.env.VITE_APP_LAND_PAGE || "https://static.qqqf.com/flb-mp"}/img/`,
clientId: import.meta.env.VITE_APP_CLIENT_ID || "hrs100_wechat_mini_app",
uniplatSocketUrl:
import.meta.env.VITE_APP_UNIPLAT_WEBSOCKET_URI ||
"wss://channel.qqqf.com/ws",
clientSecret: import.meta.env.VITE_APP_CLIENT_SECRET || "qqxb#teammix#2019",
logEnv: +(import.meta.env.VITE_APP_LOG_ENV || "1") as Environment,
version: packageConfig.version,
mpAccountAppid: "wx67ad4269eb98b531",
mpAppid: "wx71116f4df50d7f93",
officialAccountMpH5:
"https://mp.weixin.qq.com/s?__biz=Mzg3NjcyMDkwNw==&mid=2247483658&idx=1&sn=11c01272402f190d4ddc07e5f66c21e5",
testAccount: import.meta.env.VITE_APP_TEST_ACCOUNT || "",
testPassport: import.meta.env.VITE_APP_TEST_PASSPORT || "",
mapKey: "CQLBZ-RHTCO-3VKWA-S5NTZ-V4ALV-6OBRZ",
shareConfig: {
title: "亲亲企服",
path: "/pages/home/index",
img: `${import.meta.env.VITE_APP_LAND_PAGE || "https://static.qqqf.com/flb-mp"}/img/qqqf-share.png`,
path: "/pages/login/index",
},
selfJobH5: import.meta.env.VITE_APP_SELF_JOB_URL || "",
selfH5: "",
cityCodeLength: 6,
fastCity: {},
reviewVersion: 126,
registerConfig: {
app: "Reg_MiniPro",
scene: "wxapp_scene_Myqqxb",
app_store: "qq_hehuoren_minreg",
} as any,
}
export const noNeedAuthPages = [
......
......@@ -43,8 +43,10 @@
</template>
<script lang="ts" setup>
import { PassportTokenController, wechatLoginService } from "@common/sdk"
import { onLoad } from "@dcloudio/uni-app"
import { ref } from "vue"
import { config } from "@/config"
const agree = ref(false)
......@@ -65,7 +67,7 @@ function showAgreement(type: "service" | "privacy") {
})
}
function doWxLogin(e: any) {
async function doWxLogin(e: any) {
if (!agree.value) {
uni.showToast({ title: "需同意《服务协议》《隐私政策》", icon: "none" })
return
......@@ -84,15 +86,34 @@ function doWxLogin(e: any) {
}
uni.showLoading({ title: "登录中...", mask: true })
// TODO: 调用微信登录接口
setTimeout(() => {
try {
// 获取微信登录 code
const code = await new Promise<string>((resolve, reject) => {
uni.login({
provider: "weixin",
success: res => resolve(res.code),
fail: err => reject(err),
})
})
// 调用绑定手机号登录接口
const result = await wechatLoginService.miniProgramBindPhone(
config.mpAppid || "",
code,
e.detail.encryptedData,
e.detail.iv,
)
// 保存 token
PassportTokenController.saveToken2Storage(result.jwt)
uni.hideLoading()
uni.showToast({ title: "登录成功", icon: "success" })
setTimeout(() => {
const redirect = uni.getStorageSync("loginRedirect") || "/pages/home/index"
uni.reLaunch({ url: redirect })
}, 1500)
}, 1500)
} catch (err: any) {
uni.hideLoading()
uni.showToast({ title: err?.msg || err?.message || "登录失败,请重试", icon: "none" })
}
}
function back() {
......
......@@ -38,9 +38,13 @@
placeholder-style="color: #bdbdc7;"
type="text"
/>
<view class="code-placeholder" @click="getVerifyCodeImg">
图形验证码
</view>
<image
v-if="imageCodeModel"
:src="imageCodeModel.img"
mode="aspectFit"
class="code-img"
@click="getVerifyCodeImg"
/>
</view>
<view class="form-item form-item-row">
<input
......@@ -136,6 +140,8 @@
</template>
<script lang="ts" setup>
import type { VerifyImageResult } from "@common/sdk"
import { loginService, PassportTokenController, verifyService } from "@common/sdk"
import { onLoad } from "@dcloudio/uni-app"
import { computed, reactive, ref } from "vue"
......@@ -167,9 +173,10 @@ const codeCounting = ref(false)
const codeCountdown = ref(60)
let codeTimer: ReturnType<typeof setInterval> | null = null
const imageCodeModel = ref<VerifyImageResult | null>(null)
function getVerifyCodeImg() {
// TODO: 调用图形验证码接口
uni.showToast({ title: "图形验证码功能待实现", icon: "none" })
imageCodeModel.value = verifyService.generateImage()
}
function startCodeCountdown() {
......@@ -199,7 +206,7 @@ function isPhone(val: string) {
return /^1[3-9]\d{9}$/.test(val)
}
function getCode() {
async function getCode() {
if (!smsModel.mobile) {
uni.showToast({ title: "请输入手机号", icon: "none" })
return
......@@ -217,15 +224,22 @@ function getCode() {
return
}
uni.showLoading({ title: "正在获取验证码", mask: true })
// TODO: 调用发送验证码接口
setTimeout(() => {
try {
await verifyService.sendVerifyCode(
smsModel.mobile,
imageCodeModel.value?.seed || "",
smsModel.imgCode,
)
uni.hideLoading()
uni.showToast({ title: "验证码已发送", icon: "success" })
startCodeCountdown()
}, 1000)
} catch (err: any) {
uni.hideLoading()
uni.showToast({ title: err?.msg || err?.message || "发送失败", icon: "none" })
}
}
function smsLogin() {
async function smsLogin() {
if (!smsAgree.value) {
uni.showToast({ title: "需同意《服务协议》《隐私政策》", icon: "none" })
return
......@@ -240,18 +254,22 @@ function smsLogin() {
return uni.showToast({ title: "请输入验证码", icon: "none" })
}
smsLoading.value = true
// TODO: 调用验证码登录接口
setTimeout(() => {
smsLoading.value = false
try {
const result = await loginService.verifyCodeLogin(smsModel.mobile, smsModel.code)
PassportTokenController.saveToken2Storage(result.jwt)
uni.showToast({ title: "登录成功", icon: "success" })
setTimeout(() => {
const redirect = uni.getStorageSync("loginRedirect") || "/pages/home/index"
uni.reLaunch({ url: redirect })
}, 1500)
}, 1500)
} catch (err: any) {
uni.showToast({ title: err?.msg || err?.message || "登录失败", icon: "none" })
} finally {
smsLoading.value = false
}
}
function passwordLogin() {
async function passwordLogin() {
if (!pwdAgree.value) {
uni.showToast({ title: "需同意《服务协议》《隐私政策》", icon: "none" })
return
......@@ -263,15 +281,19 @@ function passwordLogin() {
return uni.showToast({ title: "请输入密码", icon: "none" })
}
pwdLoading.value = true
// TODO: 调用密码登录接口
setTimeout(() => {
pwdLoading.value = false
try {
const result = await loginService.login(passwordModel.username, passwordModel.password)
PassportTokenController.saveToken2Storage(result.jwt)
uni.showToast({ title: "登录成功", icon: "success" })
setTimeout(() => {
const redirect = uni.getStorageSync("loginRedirect") || "/pages/home/index"
uni.reLaunch({ url: redirect })
}, 1500)
}, 1500)
} catch (err: any) {
uni.showToast({ title: err?.msg || err?.message || "登录失败", icon: "none" })
} finally {
pwdLoading.value = false
}
}
function goForget() {
......@@ -296,6 +318,7 @@ function back() {
onLoad(() => {
uni.setNavigationBarTitle({ title: "验证码登录" })
getVerifyCodeImg()
})
</script>
......@@ -366,6 +389,13 @@ onLoad(() => {
color: #93939e;
}
.code-img {
width: 160rpx;
height: 60rpx;
margin-left: 20rpx;
flex-shrink: 0;
}
.code-text {
font-size: 28rpx;
white-space: nowrap;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment