Skip to content

Commit e2f5aad

Browse files
committed
Support wechat oauth login
1 parent 0a751ad commit e2f5aad

File tree

18 files changed

+660
-44
lines changed

18 files changed

+660
-44
lines changed

components.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ declare module '@vue/runtime-core' {
3232
ElRadio: typeof import('element-plus/es')['ElRadio']
3333
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
3434
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
35+
ElResult: typeof import('element-plus/es')['ElResult']
3536
ElRow: typeof import('element-plus/es')['ElRow']
3637
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
3738
ElSelect: typeof import('element-plus/es')['ElSelect']
@@ -41,6 +42,7 @@ declare module '@vue/runtime-core' {
4142
ElTabPane: typeof import('element-plus/es')['ElTabPane']
4243
ElTabs: typeof import('element-plus/es')['ElTabs']
4344
ElTag: typeof import('element-plus/es')['ElTag']
45+
ElText: typeof import('element-plus/es')['ElText']
4446
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
4547
ElTooltip: typeof import('element-plus/es')['ElTooltip']
4648
FontSelector: typeof import('./src/components/FontSelector.vue')['default']
@@ -50,6 +52,7 @@ declare module '@vue/runtime-core' {
5052
SolarDatePicker: typeof import('./src/components/SolarDatePicker.vue')['default']
5153
TimePicker: typeof import('./src/components/TimePicker.vue')['default']
5254
TimePickerDialog: typeof import('./src/components/TimePickerDialog.vue')['default']
55+
UserAvatar: typeof import('./src/components/UserAvatar.vue')['default']
5356
}
5457
export interface ComponentCustomProperties {
5558
vLoading: typeof import('element-plus/es')['ElLoadingDirective']

eslint.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@ export default antfu({
2020
'vue/space-unary-ops': 'off',
2121
'vue/comma-dangle': ['error', 'only-multiline'],
2222
'node/no-callback-literal': 'off',
23+
'ts/no-wrapper-object-types': 'off',
2324
'import/namespace': 'off',
25+
'ts/no-empty-object-type': 'off',
2426
'eqeqeq': 'off',
2527
'import/default': 'off',
2628
'import/no-named-as-default': 'off',
2729
'import/no-named-as-default-member': 'off',
30+
'style/max-statements-per-line': ['error', {
31+
max: 1,
32+
}],
2833
'curly': ['error', 'multi-line'],
2934
'max-statements-per-line': ['error', {
3035
max: 1,

src/api/supabase.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { createClient } from '@supabase/supabase-js'
2+
3+
const anonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzY0MDAwMDAwLCJleHAiOjE5MjE3NjY0MDB9.3nGFAW2q2bzxWmx1T-ycnmklITh9OcEvA1kZPXz4dBs'
4+
const supabaseUrl = 'https://supabase.widgetjs.cn'
5+
const supabase = createClient(supabaseUrl, anonKey)
6+
7+
function getStorageLink(fullPath: string) {
8+
return `https://supabase.widgetjs.cn/storage/v1/object/public/${fullPath}`
9+
}
10+
export { getStorageLink, supabase }

src/assets/css/common.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ body, *, view {
99

1010
body {
1111
margin: 0;
12+
overflow: hidden;
1213
}
1314

1415
button {
@@ -23,3 +24,4 @@ button:active {
2324
opacity: 0.6;
2425
}
2526

27+

src/components/UserAvatar.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import { User } from '@icon-park/vue-next'
3+
import { useUser } from '@/composition/useUser'
4+
5+
defineProps({
6+
size: {
7+
type: String,
8+
default: 'default',
9+
},
10+
})
11+
const { avatar } = useUser()
12+
</script>
13+
14+
<template>
15+
<el-avatar :src="avatar" :size="size">
16+
<User size="24" />
17+
</el-avatar>
18+
</template>
19+
20+
<style scoped lang="scss">
21+
22+
</style>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import consola from 'consola'
2+
import { supabase } from '@/api/supabase'
3+
4+
export function useSupabaseChannel(channelName: string, onCallback: (payload: any) => void) {
5+
const channel = supabase.channel(channelName)
6+
channel.on('broadcast', { event: 'wechat-login' }, (payload) => {
7+
onCallback(payload)
8+
}).subscribe((status) => {
9+
consola.log('Supabase channel subscription status:', status)
10+
})
11+
return { channel, teardown: channel.teardown }
12+
}

src/composition/useUser.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { User } from '@supabase/auth-js'
2+
import { UserApi } from '@widget-js/core'
3+
import consola from 'consola'
4+
import { computed, ref } from 'vue'
5+
import { supabase } from '@/api/supabase'
6+
7+
const user = ref<User | null>(null)
8+
9+
supabase.auth.onAuthStateChange((event, session) => {
10+
consola.info('onAuthStateChange', event, session)
11+
if (event === 'SIGNED_OUT') {
12+
user.value = null
13+
UserApi.logout()
14+
}
15+
else if (event === 'USER_UPDATED') {
16+
user.value = session!.user
17+
UserApi.updateUser(session!.user)
18+
}
19+
else if (event === 'SIGNED_IN') {
20+
user.value = session!.user
21+
supabase.auth.startAutoRefresh()
22+
UserApi.login(session!)
23+
}
24+
else if (event === 'TOKEN_REFRESHED') {
25+
UserApi.updateSession(session!)
26+
}
27+
})
28+
29+
export function useUser(onload?: (user?: User) => void) {
30+
const loading = ref(false)
31+
const nickname = computed(() => {
32+
if (user.value) {
33+
if (user.value.user_metadata.nickname) {
34+
return user.value.user_metadata.nickname
35+
}
36+
if (user.value.email) {
37+
return user.value.email.split('@')[0]
38+
}
39+
return 'User'
40+
}
41+
return '未登录'
42+
})
43+
44+
const userId = computed(() => {
45+
return user.value?.id || ''
46+
})
47+
48+
const avatar = computed(() => {
49+
return user.value?.user_metadata?.avatar || ''
50+
})
51+
52+
function refreshUser() {
53+
loading.value = true
54+
supabase.auth.getUser().then(({ data }) => {
55+
user.value = data.user
56+
onload?.(data.user || undefined)
57+
}).finally(() => {
58+
loading.value = false
59+
})
60+
}
61+
62+
refreshUser()
63+
return { user, refreshUser, loading, nickname, avatar, userId }
64+
}

src/index.css

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/main.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import { createApp } from 'vue'
1+
import { MotionPlugin } from '@vueuse/motion'
2+
import { WidgetJsPlugin } from '@widget-js/vue3'
23
import dayjs from 'dayjs'
4+
import ElementPlus from 'element-plus'
5+
import { createPinia } from 'pinia'
6+
import { createApp } from 'vue'
7+
import { i18n } from '@/i18n/i18n'
8+
import App from './App.vue'
9+
import router from './router'
310
import 'animate.css'
4-
import { WidgetJsPlugin } from '@widget-js/vue3'
511
import '@widget-js/vue3/dist/style.css'
612
import 'element-plus/dist/index.css'
7-
import { createPinia } from 'pinia'
8-
import { MotionPlugin } from '@vueuse/motion'
9-
import ElementPlus from 'element-plus'
10-
import './index.css'
11-
import router from './router'
1213
import 'dayjs/locale/zh-cn.js'
13-
import App from './App.vue'
1414
import 'virtual:uno.css'
15+
import '@/assets/css/common.css'
1516
import '@icon-park/vue-next/styles/index.css'
1617
import 'element-plus/theme-chalk/src/message-box.scss'
1718
import 'element-plus/theme-chalk/src/message.scss'
18-
import 'element-plus/theme-chalk/src/loading.scss'
1919

20-
import { i18n } from '@/i18n/i18n'
20+
import 'element-plus/theme-chalk/src/loading.scss'
2121

2222
dayjs.locale('cn')
2323

src/router/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@ const routes: RouteRecordRaw[] = [
4747
name: 'add',
4848
component: () => import('../views/add/AddWidgetView.vue'),
4949
},
50+
{
51+
path: '/auth/login',
52+
name: 'login',
53+
component: () => import('../views/auth/LoginView.vue'),
54+
},
55+
{
56+
path: '/auth/register',
57+
name: 'register',
58+
component: () => import('../views/auth/RegisterView.vue'),
59+
},
60+
{
61+
path: '/auth/reset',
62+
name: 'reset_password',
63+
component: () => import('../views/auth/ResetPasswordView.vue'),
64+
},
65+
{
66+
path: '/user/profile',
67+
name: 'profile',
68+
component: () => import('../views/user/ProfileView.vue'),
69+
},
5070
{
5171
path: '/failed',
5272
name: 'failed',

0 commit comments

Comments
 (0)